Anatomy of a Game Engine by Richard Baldwin - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

Image 1 . Graphic output near the beginning of the simulation.

Image 2 . Graphic output near the middle of the simulation.

Image 3 . Graphic output near the end of the simulation.

Image 4 . Output for the harmless blue sprite scenario.

Listings

Listing 1 . Beginning of the class named Slick0210.

Listing 2 . Beginning of the init method.

Listing 3 . Add a red sprite to the ArrayList object.

Listing 4 . Populate the ArrayList object.

Listing 5 . Beginning of the update method.

Listing 6 . Test for a collision.

Listing 7 . The isCollision method of the Sprite01 class.

Listing 8 . Process a collision

Listing 9 . Remove dead objects from the ArrayList object.

Listing 10 . The render method.

Listing 11 . Source code for the program named Slick0210.

Listing 12 . Source code for the class named Sprite01.

12.3. Preview

In an earlier module titled Slick0200: Developing a sprite class , we encountered a baseball coach that had been attacked by a swarm of vicious ladybug sprites. I promised you that we would

later find a way to save the coach. That time has come.

In this module, I will explain a program that uses the Sprite01 class from the earlier module to

produce a simulation program with the output shown in Image 1 , Image 2 , and Image 3 .

A swarm of insects

index-221_1.jpg

Once again, the coach has been attacked by a swarm of 1000 insects. However, in this case, the

ladybug sprites have been replaced by vicious green beetle sprites.

A red predator beetle

Fortunately for the coach, a red predator beetle sprite with a taste for green beetles has come along

and is gobbling up green beetles as fast as he can collide with them. (According to the text at the

top of Image 1 , 152 of the 1000 beetles had been consumed by the time the screen shot in Image 1

was taken.)

Figure 12.1. Image 1. Graphic output near the beginning of the simulation.

Image 1. Graphic output near the beginning of the simulation.

A fat and happy predator beetle

Image 2 shows the situation some time later when all but 173 of the green beetles had been eaten.

Note that the process of eating those nutritious beetles has caused the red beetle to gain some

weight in Image 2 .

index-222_1.jpg

index-222_2.jpg

Figure 12.2. Image 2. Graphic output near the middle of the simulation.

Image 2. Graphic output near the middle of the simulation.

Cleaning up the scraps

Image 3 shows the situation with only 36 green beetles remaining. Collisions between the beetles is rare at this point, so quite a bit more time will probably be required before the red beetle can

collide with and eat the remaining green beetles.

Figure 12.3. Image 3. Graphic output near the end of the simulation.

Image 3. Graphic output near the end of the simulation.

What you have learned

In the previous module, you learned how to develop a sprite class from which you can instantiate

and animate swarms of sprite objects.

What you will learn

In this module, you will learn how to use the Sprite01 class developed in the earlier module to write a predator/prey simulation program involving thousands of sprites, collision detection, and

sound effects.

12.4. General background information

Actually, it may have been more appropriate to describe this program in terms of jellyfish, (which

eat on the basis of opportunity) instead of beetles, (which are more deliberate in their actions) .

In this program, the red sprite consumes a green sprite only when the two happen to collide by

chance. The sprites are not attracted to one another. (That would be a good exercise for a student

project - attraction plus collision.)

Two scenarios

A baseball coach is attacked by a swarm of fierce green flying sprites. Fortunately, a red predator

sprite comes along and attacks the green sprites just in time to save the coach.

There are two scenarios that can be simulated by setting the variable named dieOnCollision (see

Listing 1 ) to either true or false .

Harmless blue sprites

In one scenario ( dieOnCollision = false) , the vicious green sprites become harmless blue sprites

when they collide with the red sprite. A screen shot of this scenario is shown in Image 4 .

index-224_1.jpg

Figure 12.4. Image 4. Output for the harmless blue sprite scenario.

Image 4. Output for the harmless blue sprite scenario.

Green sprites get consumed

In the other scenario ( dieOnCollision = true) , the green sprites are consumed by the red sprite

upon contact and are removed from the population. This is the scenario shown in Image 3 .

Get fat and happy

In both scenarios, contact between a green sprite and the red sprite causes the red sprite to

increase in size.

If you allow the program to run long enough, the probability is high that all of the green sprites

will have collided with the red sprite and will either have turned blue or will have been consumed.

12.5. Discussion and sample code

The class named Sprite01

The class named Slick0210

The init method

The update method

The isCollision method of the Sprite01 class

The render method

The class named Sprite01

The class named Sprite01 is shown in Listing 12 . I will explain only those portions of that class that I use in this program that weren't explained in the earlier module.

The class named Slick0210

Will explain in fragments

A complete listing of the class named Slick0210 is provided in Listing 11 . I will break the code down and explain it in fragments.

Beginning of the class named Slick0210.

The beginning of the class named Slick0210 , down through the main method is shown in Listing

1 .

public class Slick0210 extends BasicGame{

//Set the value of this variable to true to cause the

// sprites to die on collision and to be removed from

// the population.

boolean dieOnCollision = true;

//Store references to Sprite01 objects here.

ArrayList <Sprite01> sprites =

new ArrayList<Sprite01>();

//Change this value and recompile to change the number

// of sprites.

int numberSprites = 1000;

//Populate these variables with references to Image

// objects later.

Image redBallImage;

Image greenBallImage;

Image blueBallImage;

//Populate this variable with a reference to a Sound

// object later.

Sound blaster;

//Populate these variables with information about the

// background image later.

Image background = null;

float backgroundWidth;

float backgroundHeight;

//This object is used to produce values for a variety

// of purposes.

Random random = new Random();

//Frame rate we would like to see and maximum frame

// rate we will allow.

int targetFPS = 60;

//----------------------------------------------------//

public Slick0210(){//constructor

//Set the title

super("Slick0210, baldwin");

}//end constructor

//----------------------------------------------------//

public static void main(String[] args)

throws SlickException{

AppGameContainer app = new AppGameContainer(

new Slick0210(),414,307,false);

app.start();

}//end main

Figure 12.5. Listing 1. Beginning of the class named Slick0210.

Listing 1. Beginning of the class named Slick0210.

ArrayList

There are two things that are new in Listing 1 . First there is the instantiation of an ArrayList object in place of the array object used in the program in the earlier module.

The use of an ArrayList instead of an array provides more flexibility in managing a collection of

Sprite01 objects. If you are unfamiliar with the use of ArrayList objects, just Google the

keywords baldwin java ArrayList generics and I'm confident you will find explanatory material

that I have published on that topic.

Sound

The second new item in Listing 1 is the declaration of a reference variable of the Slick2D Sound class. That variable will be used to hold a reference to a Sound object, that will be played each

time the red sprite collides with a green sprite.

Otherwise, the code in Listing 1 is straightforward and shouldn't require further explanation.

The init method

The init method begins in Listing 2 .

public void init(GameContainer gc)

throws SlickException {

//Create Image objects that will be used to visually

// represent the sprites.

redBallImage = new Image("redball.png");

greenBallImage = new Image("greenball.png");

blueBallImage = new Image("blueball.png");

//Create a Sound object.

blaster = new Sound("blaster.wav");

//Create a background image and save information

// about it.

background = new Image("background.jpg");

backgroundWidth = background.getWidth();

backgroundHeight = background.getHeight();

Figure 12.6. Listing 2. Beginning of the init method.

Listing 2. Beginning of the init method.

An object of the Sound class

The only thing new in Listing 2 is the instantiation of the object of type Sound . As you can see, the syntax for instantiation of a Sound object is essentially the same as for instantiating an Image

object.

Add a red sprite

Listing 3 calls the add method of the ArrayList class to add a red sprite to the beginning of the ArrayList object. (Actually it add a reference to that object and not the object itself.)

You are already familiar with the constructor parameters (shown in Listing 3 ) for a Sprite01

object.

sprites.add(new Sprite01(

redBallImage,//image

backgroundWidth/2.0f,//initial position

backgroundHeight/2.0f,//initial position

(random.nextFloat() > 0.5) ? 1f : -1f,//direction

(random.nextFloat() > 0.5) ? 1f : -1f,//direction

0.1f+random.nextFloat()*2.0f,//step size

0.1f+random.nextFloat()*2.0f,//step size

0.5f+random.nextFloat()*1.5f,//scale

new Color(1.0f,1.0f,1.0f)));//color filter

Figure 12.7. Listing 3. Add a red sprite to the ArrayList object.

Listing 3. Add a red sprite to the ArrayList object.

Populate the ArrayList object

Listing 4 uses a for loop and the value of the variable named numberSprites (see Listing 1 ) to add 1000 green Sprite01 object references to the ArrayList object.

for(int cnt = 0;cnt < numberSprites;cnt++){

sprites.add(new Sprite01(

greenBallImage,//image

backgroundWidth*random.nextFloat(),//position

backgroundHeight*random.nextFloat(),//position

(random.nextFloat() > 0.5) ? 1f : -1f,//direction

(random.nextFloat() > 0.5) ? 1f : -1f,//direction

0.1f+random.nextFloat()*2.0f,//step size

0.1f+random.nextFloat()*2.0f,//step size

random.nextFloat()*1.0f,//scale

new Color(1.0f,1.0f,1.0f)));//color filter

}//end for loop

gc.setTargetFrameRate(targetFPS);//set frame rate

}//end init

Figure 12.8. Listing 4. Populate the ArrayList object.

Listing 4. Populate the ArrayList object.

Listing 4 also sets the target frame rate and signals the end of the init method.

The update method

The overall behavior of the update method is to use a for loop to process the red sprite against

each of the green sprites and to take appropriate actions when a collision between the red sprite

and a green sprite occurs.

The update method begins in Listing 5 .

public void update(GameContainer gc, int delta)

throws SlickException{

//Access to the first sprite in the ArrayList object.

Sprite01 redBallSprite = sprites.get(0);

//Do the following for every sprite in the ArrayList

// object

for(int cnt = 0;cnt < sprites.size();cnt++){

//Get a reference to the Sprite01 object.

Sprite01 thisSprite = sprites.get(cnt);

//Ask the sprite to move according to its properties

thisSprite.move();

//Ask the sprite to bounce off the edge if it is at

// an edge.

thisSprite.edgeBounce(

backgroundWidth,backgroundHeight);

Figure 12.9. Listing 5. Beginning of the update method.

Listing 5. Beginning of the update method.

Mostly same as before

The code in Listing 5 is mostly the same as code that you have seen before, so further explanation should not be necessary.

Test for a collision

The code in Listing 6 is new to this module. This code calls the isCollision method of the Sprite01 class to test for a collision between the current green sprite and the red sprite.

boolean collision =

thisSprite.isCollision(redBallSprite);

Figure 12.10. Listing 6. Test for a collision.

Listing 6. Test for a collision.

What is a collision?

There are many ways to define and implement collision detection in game and simulation

programming. In this program, a collision is deemed to have occurred if any portion of the

rectangular redBallImage overlaps any portion of the rectangular greenBallImage . (Even though

these images appear to be round, they are drawn on a transparent rectangular background.)

The isCollision method of the Sprite01 class

The isCollision method of the Sprite01 class is shown in Listing 7 .

public boolean isCollision(Sprite01 other){

//Create variable with meaningful names make the

// algorithm easier to understand. Can be eliminated

// to make the algorithm more efficient.

float thisTop = Y;

float thisBottom = thisTop + height*scale;

float thisLeft = X;

float thisRight = thisLeft + width*scale;

float otherTop = other.getY();

float otherBottom = otherTop + other.getHeight()*other.getScale();

float otherLeft = other.getX();

float otherRight = otherLeft + other.getWidth()*other.getScale();

if (thisBottom < otherTop) return(false);

if (thisTop > otherBottom) return(false);

if (thisRight < otherLeft) return(false);

if (thisLeft > otherRight) return(false);

return(true);

}//end isCollision

Figure 12.11. Listing 7. The isCollision method of the Sprite01 class.

Listing 7. The isCollision method of the Sprite01 class.

Methodology

This method detects a collision between the rectangular sprite object on which the method is

called and another rectangular sprite object.

The methodology is to test four cases where a collision could not possibly have occurred and to

assume that a collision has occurred if none of those cases are true.

Given that as background, you should be able to use a pencil and paper along with the code in

Listing 7 to draw some rectangles and understand how the code in Listing 7 works.

Although I can't guarantee that the method won't call a collision when no collision actually

occurred, I am pretty sure that it won't miss any collisions that do occur.

Process a collision

The code in Listing 8 is executed when the call to the isCollision method returns true. Therefore, this code processes a collision only when one has occurred.

The code excludes collisions between the red sprite and itself, (which is an artifact of the

algorithm) . It also excludes collisions between the red sprite and blue sprites (if they exist) .

if((collision == true)&&

(! thisSprite.getImage().equals(redBallImage)) &&

(! thisSprite.getImage().equals(blueBallImage))){

//A collision has occurred, change the color of

// this sprite to blue and maybe cause it to

// die and be removed from the population.

thisSprite.setImage(blueBallImage);

if(dieOnCollision){

thisSprite.setLife(0);

}//end if

//Cause the redBallSprite to change direction on

// a random basis.

redBallSprite.setXDirection(

(random.nextFloat() > 0.5) ? 1f : -1f);

redBallSprite.setYDirection(

(random.nextFloat() > 0.5) ? 1f : -1f);

//Cause the redBallSprite to change stepsize on a

// random basis.

redBallSprite.xStep =

0.1f+random.nextFloat()*2.0f;

redBallSprite.yStep =

0.1f+random.nextFloat()*2.0f;

//Cause the redBallSprite to grow larger

redBallSprite.setScale(redBallSprite.getScale() +

(redBallSprite.getScale()) * 0.001f);

//Play a sound to indicate that a collision has

// occurred.

blaster.play();

}//end if

}//end for loop

Figure 12.12. 8. Process a collision

8. Process a collision

Not complicated code

Although the code in Listing 8 is long and tedious, it isn't particularly complicated. It consists mainly of calls to the accessor methods of the two sprite objects involved in the collision to

modify their property values in some way.

Turn a green sprite into a blue sprite

For example, near the top of Listing 8 , there is a call to the setImage method of the green sprite to change it to a blue sprite.

Kill the green sprite

This is followed by a call to the setLife method to set the life of the (now blue) sprite object to 0,

but only if the dieOnCollision variable belonging to the object is true. Later on, all sprite objects

with a life property value of 0 will be removed from the population.

And so forth

I could continue down the page describing the calls to various other accessor methods, but that

shouldn't be necessary. The embedded comments should suffice for the explanation.

Play a sound

Finally near the end of the code in Listing 8 , there is a call to the play method belonging to Sound object referred to as blaster .

Each time there is a collision between the red sprite and a green sprite, the sound loaded earlier

from the file named "blaster.wav" is played.

The end of the for loop

Listing 8 signals the end of the for loop, but does not signal the end of the update method. There is one more task to complete before the update method terminates.

Remove dead objects from the ArrayList object

The code in Listing 9 uses an Iterator to remove all objects having a life property value that is less than or equal to zero from the ArrayList object.

//Remove dead objects from the ArrayList object

Iterator <Sprite01> iter = sprites.iterator();

while(iter.hasNext()){

Sprite01 theSprite = iter.next();

if(theSprite.getLife() <= 0){

iter.remove();

}//end if

}//end while loop

}//end update

Figure 12.13. Listing 9. Remove dead objects from the ArrayList object.

Listing 9. Remove dead objects from the ArrayList object