.Netterpillars: Artificial Intelligence and Sprites, Part 2 - Second Draft: Coding the Player Character
(Page 4 of 11 )
The next step in your code phase is to code the Netterpillar class and make all adjustments needed to your first draft for the main program and the game engine to allow the player character to be drawn on screen and be controlled by the player, using the keyboard navigation (arrow) keys. The next sections show and discuss the code to do this.
The Netterpillar Class
You’ll now look at the Netterpillar class and begin to code it. The main body of this class is shown here. You’ll look at the methods belonging to it in the subsequent code samples.
public class Netterpillar : Sprite {
private Bitmap NetterHeadN;
private Bitmap NetterHeadS;
private Bitmap NetterHeadE;
private Bitmap NetterHeadW;
public NetterBody[] NetterBody;
public int NetterBodyLength = 4;
public bool IsComputer = true; // Defaults to
AI netterpillar.
public bool IsDead = false; // Defaults to alive
netterpillar.
public Netterpillar(int x, int y,
Sprite.CompassDirections InitialDirection, bool
bIsComputer) {…}
public void EatAndMove(int x, int y, System.IntPtr
WinHandle) {…}
public void Move(int x, int y, System.IntPtr WinHandle)
{…}
public new void Draw(System.IntPtr WinHandle) {…}
}
When deriving the code interface from the class diagram, if you don’t have a detailed project, you usually start with few or even no parameters in the methods. The rich interface just shown, with many parameters in some methods, was created step by step when coding each of the methods. For example, the parameter IsComputerOpponent in the constructor is included later on in the coding process, when you discover that after each call to the constructor, you are setting the IsComputer property of the class, a clear indication that you should include this property as a parameter in the constructor.
Another surprise here is the NetterBody() array. When doing the class diagram, we mentioned something about having an array of “body parts” of the netterpillar. But what exactly is a body part in this case? It might be an array of Point objects, which would store the position to which the body bitmap must be drawn, for example. But then you would need to create a complex logic in the Netterpillar class to deal with the drawing of body parts. Instead, you should create a new class, NetterBody, that will be as simple as the Mushroom class (except that a different bitmap is used), so you can use the Location property and Draw method of the Sprite base class.
Is this the best choice for the implementation? There’s no right answer. The best option is the one that will be simpler for you to create and, most importantly, to debug and update.
As for the images of the netterpillar, besides four different bitmaps for the head (each one heading in a different direction) and one for the body, you’ll need two different sets of images to allow a visual contrast between the player-controlled netterpillar and the computer-controlled ones. Using the prefix “player” for the player bitmaps, follow the naming conventions shown in Figure 2-15.

Figure 2-15. The names for .Netterpillar Images
With these names in mind, you can create the NetterBody class and the constructor of the Netterpillar class.
public class NetterBody : Sprite {
public NetterBody(bool IsComputer) : base
(Application.StartupPath+”\\”+ IMAGE_PATH+”\\”+
(IsComputer ? “” : “Player”) +”NetterBody.gif”) {}
}
As you defined in the class properties, the default length of the body of the netterpillar (NetterBodyLength property) is four, so the netterpillar starts with a minimum size. Since the constructor will receive the initial direction for the netterpillar, you’ll use this direction to position the body parts behind the head (for example, if the netterpillar is heading east, the body parts will appear to the west of the head (lower values on the x axis). This code sample works out the position of the body relative to the head:
public Netterpillar(int x, int y, Sprite.CompassDirections
InitialDirection,bool IsComputerOpponent) {
// Start with a bigger length so you won’t need to resize
it so soon.
NetterBody = new NetterBody[25+1];
int incX=0, incY=0;
IsComputer = IsComputerOpponent;
NetterHeadN = Load
(Application.StartupPath+”\\”+IMAGE_PATH+”\\”+
(IsComputer ? “” : “Player”)+”NetterHeadN.gif”);
NetterHeadS = Load
(Application.StartupPath+”\\”+IMAGE_PATH+”\\”+
(IsComputer ? “” : “Player”)+”NetterHeadS.gif”);
NetterHeadE = Load
(Application.StartupPath+”\\”+IMAGE_PATH+”\\”+
(IsComputer ? “” : “Player”)+”NetterHeadE.gif”);
NetterHeadW = Load
(Application.StartupPath+”\\”+IMAGE_PATH+”\\”+
(IsComputer ? “” : “Player”)+”NetterHeadW.gif”);
for(i=0; i<NetterBodyLength; i++) {
NetterBody[i] = new NetterBody(IsComputer);
}
// Position the Netterpillar on the given point.
Direction = InitialDirection;
Location.X = x;
Location.Y = y;
// Position each of the body parts.
switch(Direction) {
case Sprite.CompassDirections.East:
incX = -1;
break;
case Sprite.CompassDirections.South:
incY = -1;
break;
case Sprite.CompassDirections.West:
incX = 1;
break;
case Sprite.CompassDirections.North:
incY = 1;
break;
}
for(int i=0; i<NetterBodyLength; i++) {
x += incX;
y += incY;
NetterBody[i].Location.X = x;
NetterBody[i].Location.Y = y;
}
}
Observe that you simply set the location of the netterpillar (the head) and the location of each of the body parts, but there’s no drawing yet. The drawing is done in the Draw procedure (shown in the next code listing), which considers the direction in which the netterpillar is heading in order to choose which bitmap will be used for the head, and then runs through the NetterBody array to draw the body parts.
public new void Draw(System.IntPtr WinHandle) {
switch(Direction) {
case Sprite.CompassDirections.East:
base.Draw(NetterHeadE, WinHandle);
break;
case Sprite.CompassDirections.South:
base.Draw(NetterHeadS, WinHandle);
break;
case Sprite.CompassDirections.West:
base.Draw(NetterHeadW, WinHandle);
break;
case Sprite.CompassDirections.North:
base.Draw(NetterHeadN, WinHandle);
break;
}
for(int i=0; i<NetterBodyLength; i++) {
NetterBody[i].Draw(WinHandle);
}
}
The last two methods of the Netterpillar class are very similar: Move and EatAndMove. The Move method will update the head location according to the new x and y values passed as parameters from the game engine, and then update all the body parts to move one step ahead. You could erase and draw everything, but since all the body parts look the same, you can just erase the last body part, copy the first body part over the head, and draw the head in the new position, which will be much quicker than redrawing the whole body.
public void Move(int x, int y, System.IntPtr WinHandle) {
// Erase the last part of the body.
NetterBody[NetterBodyLength-1].Erase(WinHandle);
// Update the whole body’s position and then the head
position.
for(int i=NetterBodyLength-1; i>=1; i-=1) {
NetterBody[i].Location = NetterBody[i-1].Location;
}
NetterBody[0].Location = Location;
Location = new Point(x, y);
// Redraw only the first part of the body and the head.
NetterBody[0].Draw(WinHandle);
//You don’t need to erase the netterpillar head, since
the body will cover it.
Draw(WinHandle);
// Reset the direction controller variable.
bDirectionSet = false;
}
The main difference between the EatAndMove method and the Move method is that in the first method the netterpillar is eating a mushroom and is getting bigger; so you’ll need to create a new body part (resizing the NetterBody array), set its position to the position of the last body part, and then reposition all other body parts, redrawing only the first one and the head. In the second method the netterpillar will only move, following a similar approach.
public void EatAndMove(int x, int y, System.IntPtr
WinHandle) {
// If the NetterBody array is full, allocate more space.
if (NetterBodyLength == NetterBody.Length) {
NetterBody [] tempNetterBody = new NetterBody
[NetterBody.Length+25+1];
NetterBody.CopyTo(tempNetterBody, 0);
NetterBody = tempNetterBody;
}
NetterBody[NetterBodyLength] = new NetterBod(IsComputer);
NetterBody[NetterBodyLength].Location =
NetterBody[NetterBodyLength-1].Location;
// Update the whole body’s position and then the head
position.
for(int i=NetterBodyLength-1; i>=1; i--) {
NetterBody[i].Location = NetterBody[i-1].Location;
}
NetterBody[0].Location = Location;
NetterBody[0].Draw(WinHandle);
NetterBodyLength++;
// Update the netterpillar head position.
Location = new Point(x, y);
//Clear the mushroom.
Erase(WinHandle);
// Draw the netterpillar head.
Draw(WinHandle);
// Reset the direction controller variable.
directionSet = false;
}
One extra detail here is that you need to erase the mushroom as you are eating it. You can do that by simply calling the Erase method before you call the Draw method of the Netterpillar class.
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: Main Program and GameEngine Class >>
More ASP.NET Articles
More By Apress Publishing