样本帮宝适系列-第4部分:2/3D太空游戏,简单的声音和线程(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/sample-pamper-series-part-4-2-3d-space-game-simple-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 10 分钟阅读 - 4655 个词 阅读量 0样本帮宝适系列-第4部分:2/3D太空游戏,简单的声音和线程(译文)
原文地址:https://www.codeproject.com/Articles/21230/Sample-Pamper-Series-Part-4-2-3D-Space-Game-Simple
原文作者:Windmiller
译文由本站 robot-v1.0 翻译
前言
This article is the final one in the series, and it will give you a 2/3D space game out of what we have learnt from previous articles. We will also apply simple sounds to it played in threads.
本文是该系列的最后一篇文章,它将为您提供2/3D太空游戏,这是我们从之前的文章中学到的.我们还将简单的声音应用到线程播放中.
- 下载SpaceBox2008.zip-91.7 KB(Download SpaceBox2008.zip - 91.7 KB)
- 下载SpaceBox2010.zip-91.7 KB(Download SpaceBox2010.zip - 91.7 KB)
介绍(Introduction)
这是Sample Pamper系列的第四个也是最后一个部分,我们将在一个名为Space Box的简单游戏中将所有东西放在一起.到目前为止,我们已经学到的所有东西都将应用于游戏,包括旋转,灯光和多边形绘制.本文将为您提供如何在对象中实现这些功能的线索,在这里,我将向您提示如何执行此操作.该游戏是基于旧时尚游戏" Asteroids"的,您将看到为什么玩它.如果您已经知道如何执行此操作,那么您就可以尽情玩乐了,那就走吧.(This is the fourth and final part of the Sample Pamper series where we are going to put all things together in a simple game called Space Box. All the things we have learnt this far will be applied to the game, with rotations, lights, and polygon painting. This article will give you a clue of how to implement these things in objects, and here, I’ll give you a hint to how to do this. This game is based on the old fashion game “Asteroids”, and you will see why when you play it; if you already know how to do this, then you can just have fun playing it, so let’s go.) 本文将描述…(This article will describe…)
需求(Requirement)
您将需要安装Visual Studio .NET才能运行并尝试本文的源代码.(You will need Visual Studio .NET installed to be able to run and try the source code for this article.) 回到顶部?(Back to top?)
游戏(The game)
在这张照片中,您可以看到三个敌人,每个敌人都是盒子形状;播放器是圆形对象.橙色文字" SHIELD"是一个盾牌,您可以使用鼠标右键激活它,但前提是您必须足够快才能在文字出现几秒钟时得到一个盾牌.此盾将杀死与之碰撞的敌人,然后消失.然后,您必须找到一个新敌人才能杀死另一个敌人.您也可以用小激光球射击敌人.在这张照片中,您可以看到一个敌人周围的一些圆圈,这是因为玩家刚刚射击了他.从播放器对象到鼠标指针的蓝线显示了对象推力的方向.效果就像在播放器对象和鼠标指针之间有橡皮筋一样.(In this picture, you can see three enemies, each in the shape of a box; the player is the circular object. The orange text “SHIELD” is a shield you can activate with the right mouse button, but only if you’re fast enough to get one when the text appears for a few seconds. This shield will kill an enemy colliding with it and then disappear. Then, you must find a new one to be able to kill another enemy. You can also shoot the enemies with a little laser ball. In this picture, you can see some circles around one of the enemies, and this is because the player has just shot him. The blue line drawn from the player object to the mouse pointer displays which way the thrust for the object will go. The effect is like if there was a rubber band between the player object and the mouse pointer.) 回到顶部?(Back to top?)
类图(Class diagram)
使用代码(Using the code)
由于包含了声音文件,因此可下载文件很大(大约700 KB),但是我将它们设置得尽可能小.我已经将所有东西都放在带有接口的对象中,但是有些东西像一些函数和对象一样被遗漏了.您可以测试自己的技能并重新安排他们,或者只是按原样玩游戏,这取决于您.从现在开始,我将解释该软件包中的所有部分.首先,我将向您展示接口和对象上的类图,以便您可以看到它们之间的连接.(The downloadable file is quite big (around 700 KB) because of the sound files included, but I’ve made them as small as possible. I’ve put everything in objects with interfaces, but there are things left out like some functions and objects. You can test your own skills and rearrange them, or just play the game like it is, it’s up to you. From now, I will explain all the parts in this package. First, I’ll start off by showing you the class diagrams over the interfaces and objects, so that you can see the connection between them.) 要使用此程序包,我们必须在游戏应用程序中键入以下行:(To use this package, we must type the following lines in the game application:)
using System.Threading;
using Engine3D;
using Engine3DVP;
第一行就在这里,因为我们使用线程以一种简单的方式在游戏中同时播放声音.(The first line is there because we are using threads to play sounds simultaneously in the game, in a simple way.) Engine3D
是我们将在其中找到引擎,播放器,敌人以及其他所需名称空间的名称空间.(is the namespace where we will find the engine, player, enemy, and others that we will need.) Engine3DVP
是价值包,包括游戏所需的所有界面和结构.我们并没有100%使用这些接口,但是我们正在使用它们,您应该完全使用函数中参数中的接口.现在,在游戏客户端应用程序中,我们有了声音引擎,将在游戏中使用它.这是该部分的代码片段.(is the value pack including all the interfaces and structures needed for the game. We are not using these interfaces to 100%, but we are using them, you should use the interfaces in the parameters in the functions all the way. Now, in the game client application, we have our sound engine, which we will use in the game. Here’s the code snippet for that part.)
回到顶部?(Back to top?)
声音引擎(Sound engine)
public class Sound
{
[Flags]
public enum SoundFlags : int
{
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_NODEFAULT = 0x0002,
SND_MEMORY = 0x0004,
SND_LOOP = 0x0008,
SND_NOSTOP = 0x0010,
SND_NOWAIT = 0x00002000,
SND_ALIAS = 0x00010000,
SND_ALIAS_ID = 0x00110000,
SND_FILENAME = 0x00020000,
SND_RESOURCE = 0x00040004
}
[DllImport("winmm.dll")]
public static extern bool PlaySound( string szSound,
IntPtr hMod, SoundFlags flags );
[DllImport("winmm.dll")]
public static extern long mciSendString(string strCommand,
string strReturn, int iReturnLength, IntPtr hwndCallback);
private bool mLoop;
private string mSound;
private string mName;
private void ThreadPlay()
{
mciSendString("open \""+
this.mSound+"\" type mpegvideo
alias "+this.mName,null,0,IntPtr.Zero);
if(this.mLoop)
mciSendString("play "+
this.mName+" repeat",null,0,IntPtr.Zero);
else
mciSendString("play "+this.mName,null,0,IntPtr.Zero);
}
public void PlayMulti( string strFileName, bool loop, string name)
{
this.mName = name;
this.mLoop = loop;
this.mSound = strFileName;
Thread mMThread = new Thread(new ThreadStart(ThreadPlay));
mMThread.Start();
mMThread.Join();
this.ThreadPlay();
}
public void PlaySingle( string strFileName, bool loop)
{
PlaySound(strFileName,IntPtr.Zero, SoundFlags.SND_ASYNC);
}
}
这个物件有一个(This object has a) PlaySingle
函数和一个用于多声音播放的函数,它将所有创建的线程留给垃圾收集器,因此它将在释放之前保留一些内存一段时间.不是最好的方法,但是它是可管理的.它播放Wave和MP3文件,我们将在此游戏中同时使用它们.现在,我们需要在客户端游戏应用程序中创建引擎的实例.(function and one for multi sound play, and it leaves all the created threads to the Garbage Collector, so it will leave some memory held for a while before being released. Not the best way to do it, but it is manageable. It plays Wave and MP3 files, we will use them both in this game. Now, we need to create an instance of the engine in the client game application.)
private Engine3DVP.IEngine3D eng =
new Engine3D.Engine3D(SCREEN_WIDTH,SCREEN_HEIGHT);
不幸的是,我们的名称空间名称与(Unfortunately, we have the same name of the namespace as the) Engine3D
对象,这绝对不应该做,但是我们还是在这里做.这将创建一个引擎来处理屏幕对象(游戏屏幕).现在,我告诉你(object, this should never be done, but we are doing it here anyway. This will create an engine to handle a screen object, the game screen. Now, I’ll show you) Engine3D
这样您就可以详细了解它的作用.(so that you can see what it does in detail.)
回到顶部?(Back to top?)
游戏引擎(Game engine)
using Engine3DVP;
namespace Engine3D
{
public class Engine3D: IEngine3D
{
private int SCREEN_HEIGHT;
private int SCREEN_WIDTH;
private Point mMouse; //Mouse coordinates from the client app.
private ArrayList mObjects;
public Engine3D(int screen_width, int screen_height)
{
//…..
}
public int Energy(int i)
{
return ((IObject3D)this.mObjects[i]).Energy;
}
功能(The function) Energy
将为您提供物体剩余的能量.(will give you the amount of energy the object has left to live.)
//Will get the degree of object i polygon m
//in the rotated mesh, this though the Object3D class.
public double GetDegree(int i, int m)
{
return ((IObject3D)this.mObjects[i]).GetDegree(m);
}
//Will set the object i polygon m. The degree value.
public void SetDegree(int i, int m, double degree)
{
((IObject3D)this.mObjects[i]).SetDegree(m,degree);
}
//Will create the polygon mesh in this object.
public void NewPlayer(ref ArrayList mesh, Poinx3D place,
double rotX, double rotY, int energy)
{
IObject3D temp = new Player(place, rotX, rotY, energy);
temp.SetMeshPoints(ref mesh);
this.mObjects.Add((IPlayer)temp); //Make it a player.
}
public void NewEnemy(ref ArrayList mesh, Poinx3D place,
double rotX, double rotY, double paseX, double paseY, int energy)
{
IObject3D temp = new Enemy(place, rotX, rotY, paseX, paseY, energy);
temp.SetMeshPoints(ref mesh);
this.mObjects.Add((IEnemy)temp); //Make it an enemy!
}
//Some other functions
private void MovePlayer(Player obj)
{
if(obj.Energy>0)
{
//Calculate the power of the speed.
obj.PaseX = ((double)this.mMouse.X- ((double)obj.ObjPos.X))/200.00;
obj.PaseY = ((double)this.mMouse.Y- ((double)obj.ObjPos.Y))/200.00;
//Speed Limit
if(obj.CountX>25)
obj.CountX=25;
else if(obj.CountX<-25)
obj.CountX=-25;
if(obj.CountY>25)
obj.CountY=25;
else if(obj.CountY<-25)
obj.CountY=-25;
//Counter.
obj.CountX += obj.PaseX;
obj.CountY += obj.PaseY;
//Give new position.
Poinx3D newpos = new Poinx3D();
newpos.X = (int)obj.CountX+obj.ObjPos.X;
newpos.Y = (int)obj.CountY+obj.ObjPos.Y;
newpos.Z = obj.ObjPos.Z;
//Screen limit detection.
TestForScreenLimits(ref newpos);
//Give the thrust we need.
obj.ObjPos = newpos;
//And rotate the player.
obj.SetRotX(obj.PaseY/40.0);
obj.SetRotY(obj.PaseX/40.0);
ColDetection(obj);
}
}
一个好主意是将所有值(例如/40)更改为常量.(A good idea is to change all the values like /40 to constants instead.)
private void TestEnemyPlayer(ref Enemy obj)
{
for(int i=0;i<this.mobjects.count;i++)
{
if(this.mObjects[i] is Player)
{
IObject3D pl = (Player)this.mObjects[i];
double distx = (obj.ObjPos.X-pl.ObjPos.X);
double disty = (obj.ObjPos.Y-pl.ObjPos.Y);
double dist = Math.Sqrt(Math.Abs(distx)*Math.Abs(distx)+
Math.Abs(disty)*Math.Abs(disty));
//Within range to player!
if(dist>0 && dist<140 && !obj.InWar && pl.Energy>0)
{
//Calculate degrees to target.
double cc4 = -Math.Cos((Math.Acos(distx/dist)))*10;
double cr4 = -Math.Sin((Math.Asin(disty/dist)))*10;
obj.PaseX = cc4;
obj.PaseY = cr4;
obj.InWar = true;
}
else if(dist>=80 && obj.InWar)
obj.InWar = false;
}
}
}
上面的此功能将测试敌人是否足够近以攻击玩家目标.(This function above will test if an enemy is close enough to attack a player object.)
private void MoveEnemy(Enemy obj)
{
if(!obj.IsDead)
{
TestEnemyPlayer(ref obj);
Poinx3D temp;
temp = obj.ObjPos;
temp.X += (int)(obj.PaseX);
temp.Y += (int)(obj.PaseY);
temp.Z = obj.ObjPos.Z;
//Screen limit detection.
TestForScreenLimits(ref temp);
obj.ObjPos = temp;
ColDetection(obj);
}
}
//Astroids kind of thingy
private void TestForScreenLimits(ref Poinx3D obj)
{
if(obj.X<-50)
obj.X = SCREEN_WIDTH;
if(obj.X>SCREEN_WIDTH)
obj.X = -50;
if(obj.Y<-50)
obj.Y = SCREEN_HEIGHT;
if(obj.Y>SCREEN_HEIGHT)
obj.Y = -50;
}
TestForScreenLimits
将使物体以一种方式从屏幕移出另一种方式进入屏幕,就像旧游戏<小行星>(我应该说旧经典游戏<小行星>)一样.(will make an object moving off screen in one way enter from the other, like in the old game Asteroids (I should say the old classic game Asteroids).)
//3D horizontal line
private void HorizontalLine3D(ref Bitmap bm, ref uint[][] zbuffer,
Poinx3D p1, Poinx3D p2, Color color, double intencity)
{
//Delta lengths
int dx = (int)(p2.X-p1.X);
int dz = (int)(p2.Z-p1.Z);
//Direction pointers.
int step_x = 0;
int step_z = 0;
//Moving right step +1 else -1
if(dx>=0)
step_x = 1;
else
{
step_x = -1;
dx = -dx;
}
if(dz>=0)
step_z = 1;
else
{
step_z = -1;
dz = -dz;
}
//You need this to make the err_term work.
//Because we are using integers we must multiply with 2
//otherwise we would just start with error 0.5
int dx2 = dx*2; //delta X * 2 instead of 0.5
int dz2 = dz*2; //delta Z * 2 ..
int err_termXZ = 0;
//Unified things.
int uni1D = 0, uni2D2 = 0;
int inc11 = 0, inc12 = 0, inc2 = 0;
int step1D2 = 0, step2D = 0;
int loopTo = 0, direction = 0;
if(dx>=dz)
{
err_termXZ = dz2 - dx;
direction = 0;
loopTo = dx;
uni1D = dx2;
inc11 = (int)p1.Y;
step1D2 = step_z;
inc12 = (int)p1.Z;
uni2D2 = dz2;
inc2 = (int)p1.X;
step2D = step_x;
}
else if(dz>dx)
{
err_termXZ = dx2 - dz;
direction = 2;
loopTo = dz;
uni1D = dz2;
inc11 = (int)p1.Y;
inc12 = (int)p1.X;
step1D2 = step_x;
uni2D2 = dx2;
inc2 = (int)p1.Z;
step2D = step_z;
}
//Step x direction by one until the end of width.
for(int i=0;i<=loopTo;i++)
{
Color cl = color;
if(direction==0&&inc2>0&&inc2<screen_width&&inc11>0&&
inc11<screen_height)
{
if(inc12>zbuffer[inc2][inc11])
{
bm.SetPixel(inc2,inc11,cl);
zbuffer[inc2][inc11] = Convert.ToUInt32(inc12);
}
}
if(direction==2&&inc12>0&&inc12<screen_width&&inc11>0&&
inc11<screen_height)
{
if(inc2>zbuffer[inc12][inc11])
{
bm.SetPixel(inc12,inc11,cl);
zbuffer[inc12][inc11]=Convert.ToUInt32(inc2);
}
}
//Adjust error_term
//This if it's time to do so.
if(err_termXZ>=0)
{
err_termXZ -= uni1D;
inc12 += step1D2;
}
err_termXZ += uni2D2;
inc2 += step2D;
}
}
回到顶部?(Back to top?) 这就是所有的计算功能.以下功能可帮助上述功能发挥作用:(That’s all about the calculation functions. The following functions are there to help the above functions do their work:)
public Segment3D Normalize(ref Segment3D normal)
{
double len = Math.Sqrt(normal.sX*normal.sX+normal.sY*normal.sY+
normal.sZ*normal.sZ);
normal.sX = ((double)normal.sX / len); //casting hell yeah right!
normal.sY = ((double)normal.sY / len); //casting hell yeah right!
normal.sZ = ((double)normal.sZ / len); //casting hell yeah right!
return normal; //Always return even if ref!
}
public Segment3D BuildSegment(Poinx3D start, Poinx3D end, ref Segment3D segm)
{
segm.sX = end.X-start.X;
segm.sY = end.Y-start.Y;
segm.sZ = end.Z-start.Z;
return segm;
}
BuildSegment
就是这样,它将建立一个细分.(is just that, it will build a segment.)
public double DotNormalized(Segment3D segm1, Segment3D segm2)
{
double x = segm1.sX * segm2.sX + segm1.sY * segm2.sY + segm1.sZ * segm2.sZ;
return x;
}
DotNormalized
从两个归一化的细分中得出点积.(will give the dot product out of two normalized segments.)
public Segment3D ExctractNormal(Segment3D segm1,
Segment3D segm2, ref Segment3D normal)
{
normal.sX = ((segm2.sY*segm1.sZ)-(segm2.sZ*segm1.sY));
normal.sY = ((segm2.sZ*segm1.sX)-(segm2.sX*segm1.sZ));
normal.sZ = ((segm2.sX*segm1.sY)-(segm2.sY*segm1.sX));
return normal;
}
ExctractNormal
将为您提供曲面中两个线段中的法线并返回法线.而已.我已经向您展示了该游戏中最重要的内容.下载并使用它,进行更改,但仅出于学习目的.您是否出于其他原因要使用它?您必须通过在本文论坛中给我打电话来告诉我.(will give you the normal out of two segments in a surface and return the normal one. That’s it. I’ve shown you the most important stuff in this game. Download it and play with it, change it but only for learning purposes. Do you want to use it for other reasons? You’ll have to tell me by giving me a call in this article forum.)
回到顶部?(Back to top?)
兴趣点(Points of interest)
这只是您可以从本系列早期文章中获得的信息中完成工作的一个示例.显示图形应该以其他方式完成,而不是使用(This is just an example of what you can accomplish out of the information you got from the earlier articles in this series. Displaying graphics should be done in other ways than manipulating with a) Bitmap
,就像我们在这里.它不是完美的,但还活着.(, like we are here. It’s not perfect, but alive.)
历史(History)
- 1.0版,于2007年11月8日上载-本文包含的C#版本.(Version 1.0, uploaded 8 November, 2007 - A version included for the article, for C#.)
- 版本*,于2007年11月14日更改-对本文进行了一些更改.(*Version *, changed 14 November, 2007 - Made some changes to the article.*)
- 1.0版,于2007年11月17日上载-与以前相同,但可用于Visual Studio 2005 .NET.(Version 1.0, uploaded 17 November, 2007 - Same as before, but available for Visual Studio 2005 .NET.)
- 版本*,于2007年11月25日更改-对本文进行了一些新更改.(*Version *, changed 25 November, 2007 - Made some new changes to the article.*) 回到顶部?(Back to top?)
参考文献(References)
链接(Links)
- 样品帮宝适器第1部分:3D光照,平面阴影和旋转(Sample Pamper Part 1: 3D-Light, flat-shading, and rotations)
- 样品帮宝适器第2部分:3D反射向量(Sample Pamper Part 2: 3D-Reflection Vectors)
- 样本纵容第3部分:3D多边形和线条(Sample Pamper Part 3: 3D-Polygons and Lines)
- 杰克`布莱森汉姆(Jack E. Bresenham)
- Bresenham画线算法(The Bresenham Line-Drawing Algorithm)
- 抗锯齿线图(Anti-Aliased Line Drawing)
- C#第IV部分(抗锯齿)中的简单光线跟踪,作者:Andalmeida(Simple Ray Tracing in C# Part IV (Anti-Aliasing), by Andalmeida) 回到顶部?(Back to top?)
免责声明(Disclaimer)
该软件按"原样"提供,没有任何明示或暗示的保证.作者概不对使用此软件引起的任何损失负责.授予任何人仅出于学习目的使用此软件的权限.而且,您永远不能说这是您的.(This software is provided ‘as-is’ without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for learning purpose only. And, never could you claim that it is yours.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C#2.0 .NET2.0 Windows .NET1.1 VS2005 VS.NET2003 Design Dev 新闻 翻译