.Netterpillars: Artificial Intelligence and Sprites, Part 2 - Third Draft: Coding the Game Engine and Collision Detection
(Page 6 of 11 )
In this last part of your coding, you’ll finish the GameEngine class and code the AI for the computer-controlled netterpillars. You’ll also add the code to allow the configuration screen to function properly.
The GameEngine Class
To code the interface of the GameEngine class, you must refer to the class diagram created in the game project phase and include the implementation details. The GameEngine class interface is presented in the following code listing:
public class GameEngine {
public int Width = 25;
public int Height = 25;
public static Image BackgroundImage;
// This array and enum controls the object collision.
protected static GameObjects[,] GameField;
protected enum GameObjects {
Mushroom = 0,
Empty = 1,
//Spider = 2
Branch = 3,
Netterpillar = 4
};
private System.IntPtr ScreenWinHandle;
// Game objects
private Branch[] objBranches;
private Mushroom objMushrooms;
private int MushroomNumber = 75;
public Netterpillar[] netterPillars = new Netterpillar
[4];
public int NetterpillarNumber = 1;
public Netterpillar Player1;
//Controls the game end.
public bool GameOver;
public bool Paused;
// These properties are defined as property procedures,
// and they use the enumerations above as property types.
public GameFieldSizes Size {…}
public MushroomSizes Mushrooms {…}
public void MoveNetterpillars() {…}
public void KillNetterPillar(Netterpillar Netterpillar)
{…}
public void Render() {…}
public void CreateGameField(System.IntPtr WinHandle) {…}
public void Redraw() {…}
}
Next, you start coding the collision detection, which is accomplished by making the GameField array hold all the game objects and, before moving the netterpillars (the only moving objects), checking to see whether there’s any collision.
You fill the array in the constructor, including some lines to set the array just after creating the objects. At this point, you can make a simple improvement in your new and Draw procedures: Instead of creating dozens of mushroom objects, you could create a single object and move it as needed to draw all the mushrooms on the game field. This will have no effect on your collision detection algorithms, since you’ll use GameField instead of the Mushroom object to test the collision.
You’ll just include the following lines in the new procedure you coded previously, starting with an initialization loop that will set all the objects in the array to Empty:
// Initialize the game array (for collision detection).
for(int x=0; x<WIDTH; { < P x++)
for(int y="0;" y<Height; y++) }
GameField[x, y]="GameObjects.Empty;
}
}
After creating the netterpillars, you insert the code for setting all the positions in the array (head and bodies) for each netterpillar to the Netterpillar GameObjects enumeration member.
// Populates the array with the netterpillars.
for(int i=0; i<NetterpillarNumber; i++) {
GameField[netterPillars[i].Location.X,
netterPillars[i].Location.Y] =
GameObjects.Netterpillar;
for(int j=0; j<netterPillars[i].NetterBodyLength; j++) {
GameField[netterPillars[i].NetterBody[j].Location.X,
netterPillars[i].NetterBody[j].Location.Y] =
GameObjects.Netterpillar;
}
}
Since the branches are just limiting your game field, you can simply do some loops that will set all the borders (the array elements with x = 0, y = 0, x = Width – 1, or y = Height – 1) to the Branch GameObjects enumeration member.
for(int x=0; x<Width; x++) {
GameField[x, 0] = GameObjects.Branch;
GameField[x, Height-1] = GameObjects.Branch;
}
for(int y=0; y<=Height; y++) {
GameField[0, y] = GameObjects.Branch;
GameField[Width-1, y] = GameObjects.Branch;
}
And as for the mushrooms, you just need to set the array position to the enumeration element Mushroom for each new mushroom added. You also need to make two more improvements to the code you used previously as a stub: First, let’s check whether the random array position chosen has no objects in it, and if it does, choose another position, until you find an Empty array slot. Second, as planned before, let’s save some memory by creating just one mushroom, and simply moving it from place to place when you need to draw the game field. The final code for the mushroom creation will be as follows:
objMushrooms = new Mushroom();
for(int i=0; i<MushroomNumber; i++) {
// Check to seek if you are not creating the mushrooms
over other objects.
do {
x = rand.Next(0, this.Width-2)+1;
y = rand.Next(0, this.Height-2)+1;
} while(GameField[x, y]!=GameObjects.Empty);
GameField[x, y] = GameObjects.Mushroom;
}
Note that you have to change the objMushrooms property definition to a variable, instead of an array. The code for drawing the mushrooms in the Redraw method will be as follows:
for(int x=0; x<Width; x++) {
for(int y=0; y<Height; y++) {
if (GameField[x, y]==GameObjects.Mushroom) {
objMushrooms.Location = new Point(x, y);
objMushrooms.Draw(ScreenWinHandle);
}
}
}
With these modifications, your constructor and Draw methods are filling the array that will help you with collision detection. You now need to change the MoveNetterpillars method to check for any collisions when the netterpillars move, and take the appropriate actions as follows:
- Kill the netterpillar if it hits an obstacle.
- Make the netterpillar bigger when it collides with a mushroom, calling the EatAndMove method of the Netterpillar class; at this point you should decrement the mushroom number counter in order to know when all the mushrooms have been eaten.
- Move the netterpillar when there’s no collision.
In each case, you’ll have to remember to empty the array in every position the netterpillar has visited previously to avoid ghost collisions. You’ll have to change the call of the Move method in MoveNetterpillars to a selection that takes into account the actions just mentioned. Remember, this code goes immediately after the code that will set the incX and incY variables; to point to the next position the netterpillar will occupy, you have to test the current position added to these increment variables.
switch(GameField[netterPillars[i].Location.X+incX,
netterPillars[i].Location.Y+incY]) {
case GameObjects.Empty:
// Update the Game Field - Empty the field after the
Netterpillar.
GameField[netterPillars[i].NetterBody[netterPillars[i].
NetterBodyLength-1].Location.X,
netterPillars[i].NetterBody[netterPillars[i].
NetterBodyLength-1].Location.Y] =
GameObjects.Empty;
// Move the Netterpillar.
netterPillars[i].Move(netterPillars[i].Location.X+incX,
netterPillars[i].Location.Y+incY, ScreenWinHandle);
// Update the Game Field - Sets the Netterpillar Head.
GameField[netterPillars[i].Location.X,
netterPillars[i].Location.Y] =
GameObjects.Netterpillar;
break;
case GameObjects.Mushroom:
// Decrement the number of Mushrooms.
MushroomNumber--;
netterPillars[i].EatAndMove(netterPillars
[i].Location.X+incX,
netterPillars[i].Location.Y+incY, ScreenWinHandle);
// Update the Game Field - Sets the Netterpillar Head.
GameField[netterPillars[i].Location.X,
netterPillars[i].Location.Y] =
GameObjects.Netterpillar;
break;
default:
KillNetterPillar(netterPillars[i]);
break;
}
All you need to do now to test your program is to code the KillNetterpillar method, which will erase the netterpillar from the game field and do the updates on the Netterpillar object and the array field.
public void KillNetterPillar(Netterpillar Netterpillar) {
Netterpillar.IsDead = true;
// Clears the game field.
GameField[Netterpillar.Location.X,
Netterpillar.Location.Y] =
GameObjects.Empty;
Netterpillar.Erase(ScreenWinHandle);
for(int i=0; i<Netterpillar.NetterBodyLength; i++) {
GameField[Netterpillar.NetterBody[i].Location.X,
Netterpillar.NetterBody[i].Location.Y] =
GameObjects.Empty;
Netterpillar.NetterBody[i].Erase(ScreenWinHandle);
}
}
In the previous code, you reset the array elements for the head and the bodies, called the Erase method of the Netterpillar class to remove it from sight, and finally set the IsDead property of the netterpillar to true.
At this point, after coding the KillNetterpillar method, you may have noticed that you forgot to do something on the methods you had already coded: You forgot to test whether the netterpillar is alive when moving it at the MoveNetterpillars method and when drawing it in the Redraw method! Okay, don’t panic, you can just add an if statement to solve this. The MoveNetterpillar method will become as follows:
public void MoveNetterpillars() {
…
for(i=0; i<NETTERPILLARNUMBER; i++) {
if (!netterPillars[i].IsDead) {
… // Put the “MoveNeterpillar” code here.
}
}
}
And here’s how the Redraw procedure will look:
…
for(int i=0; i<NETTERPILLARNUMBER; i++) {
if (!netterPillars[i].IsDead) {
netterPillars[i].Draw(ScreenWinHandle);
}
}
…
This will prevent the dead netterpillar from moving and being drawn again.
You can test your program now. The interface will be the same as in the second draft, but now you can effectively eat the mushrooms and get bigger, and die when you hit an obstacle.
This chapter is from Beginning .NET Game Programming in C# by Ellen Hatton et al. (Apress, 2004, ISBN: 1590593197). Check it out at your favorite bookstore today. Buy this book now.
|
Next: Fourth Draft: Coding the Config Screen and Game Over >>
More ASP.NET Articles
More By Apress Publishing