13,045,790 members (48,469 online)
alternative version

#### Stats

29.7K views
42 bookmarked
Posted 8 Nov 2007

# Sample Pamper Series - Part 4: 2/3D Space Game, Simple Sounds, and Threads

, 4 Apr 2012
 Rate this:
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.

## Introduction

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.

## Requirement

You will need Visual Studio .NET installed to be able to run and try the source code for this article.

## The game

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.

## Class diagram

### Using the code

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` 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.

### 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;

{
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;

}
public void PlaySingle( string strFileName, bool loop)
{
PlaySound(strFileName,IntPtr.Zero, SoundFlags.SND_ASYNC);
}
}```

This object has a `PlaySingle` 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.

### 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);

}
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);
}

//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);
}
}```

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)
{
{
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

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);
}
}

//This if it's time to do so.

if(err_termXZ>=0)
{
err_termXZ -= uni1D;
inc12 += step1D2;
}
err_termXZ += uni2D2;

inc2 += step2D;
}
}```

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.

## 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

• Version 1.0, uploaded 8 November, 2007 - A version included for the article, for C#.
• Version *, changed 14 November, 2007 - Made some changes to the article.
• Version 1.0, uploaded 17 November, 2007 - Same as before, but available for Visual Studio 2005 .NET.
• Version *, changed 25 November, 2007 - Made some new changes to the article.

## 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.

## Share

 Sweden
Professional programmer, degree in Informatics and Applied Systems Science.

## You may also be interested in...

 First Prev Next
 SpaceBox Steve Noll4-Apr-12 3:35 Steve Noll 4-Apr-12 3:35
 Re: SpaceBox Windmiller4-Apr-12 12:03 Windmiller 4-Apr-12 12:03
 :) Flamuri31-Mar-12 2:43 Flamuri 31-Mar-12 2:43
 Re: :) Windmiller4-Apr-12 12:04 Windmiller 4-Apr-12 12:04
 Thank you very much! Regards, Morgan The day I became a WhuShu coder..
 Last Visit: 31-Dec-99 18:00     Last Update: 22-Jul-17 22:10 Refresh 1