The Simulation Classes

Before we can dive into the game screen we need to create our simulation classes. We'll follow the same pattern as in Mr. Nom, with a class for each game object and an all-knowing superclass called World that ties together the loose ends and makes our game world tick. We'll need classes for

Bob, squirrels, and platforms can move, so we'll base their classes on the DynamicGameObject we created in the last chapter. Springs and coins are static, so those will derive from the GameObject class. The tasks of each of our simulation classes are as follows:

■ Store the position, velocity, and bounding shape of the object.

■ Store the state and length of time that the object has been in that state (state time) if needed.

■ Provide an update() method that will advance the object if needed according to its behavior.

■ Provide methods to change an object's state (e.g., tell Bob he's dead or hit a spring).

The World class will then keep track of multiple instances of these objects, update them each frame, check collisions between objects and Bob, and carry out the collision responses (e.g., let Bob die, collect a coin, etc.). We will go through each class, from simplest to most complex.

The Spring Class

Let's start with the Spring class in Listing 9-8. Listing 9-8. Spring.java, the Spring Class package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.GameObject;

public class Spring extends GameObject { public static float SPRING_WIDTH = 0.3f; public static float SPRING_HEIGHT = 0.3f;

public Spring(float x, float y) {

super(x, y, SPRING_WIDTH, SPRING_HEIGHT);

The Spring class derives from the GameObject class: we only need a position and bounding shape since a spring does not move.

Next we define two constants that are publically accessible: the spring width and height in meters. We already estimated those values previously, and we just reuse them here.

The final piece is the constructor, which takes the x- and y-coordinates of the spring's center. With this we call the constructor of the superclass GameObject, which takes the position as well as the width and height of the object to construct a bounding shape from (a Rectangle centered around the given position). With this information our Spring is fully defined, having a position and bounding shape to collide against.

The Coin Class

Next up is the class for coins in Listing 9-9.

Listing 9-9. Coin.java, the Coin Class package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.GameObject;

public class Coin extends GameObject {

public static final float COIN_WIDTH = 0.5f; public static final float COIN_HEIGHT = 0.8f; public static final int COIN_SCORE = 10;

float stateTime;

public Coin(float x, float y) {

super(x, y, COIN_WIDTH, COIN_HEIGHT); stateTime = 0;

public void update(float deltaTime) { stateTime += deltaTime;

The Coin class is pretty much the same as the Spring class, with only one difference: we keep track of the duration the coin has been alive already. This information is needed when we want to render the coin later on, using an Animation. We did the same thing for our cavemen in the last example of the last chapter. It is a technique we'll use for all our simulation classes. Given a state and a state time, we can select an Animation, as well as which keyframe of that Animation to use for rendering. The coin only has a single state, so we only need to keep track of the state time. For that we have the update() method, which will increase the state time by the delta time passed to it.

The constants defined at the top of the class specify a coin's width and height as we defined it before, as well as the number of points Bob earns if he hits a coin.

The Castle Class

Next up we have a class for the castle at the top of our world. Listing 9-10 shows the code.

Listing 9-10. Castle.java, the Castle Class package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.GameObject;

public class Castle extends GameObject { public static float CASTLE_WIDTH = 1.7f; public static float CASTLE_HEIGHT = 1.7f;

public Castle(float x, float y) {

super(x, y, CASTLE_WIDTH, CASTLE_HEIGHT);

Not too complex. All we need to store is the position and bounds of the castle. The size of a castle is defined by the constants CASTLE_WIDTH and CASTLE_HEIGHT, using the values we discussed earlier.

The Squirrel Class

Next is the Squirrel class in Listing 9-11. Listing 9-11. Squirrel.java, the Squirrel Class package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.DynamicGameObject;

public class Squirrel extends DynamicGameObject { public static final float SOUIRREL_WIDTH = 1; public static final float SOUIRREL_HEIGHT = 0.6f; public static final float SOUIRREL_VELOCITY = 3f;

float stateTime = 0;

public Squirrel(float x, float y) {

super(x, y, SOUIRREL_WIDTH, SOUIRREL_HEIGHT); velocity.set(SOUIRREL_VELOCITY, 0);

public void update(float deltaTime) {

position.add(velocity.x * deltaTime, velocity.y * deltaTime); bounds.lowerLeft.set(position).sub(SOUIRREL_WIDTH / 2, SOUIRREL_HEIGHT / 2);

if(position.x < SOUIRREL_WIDTH / 2 ) { position.x = SOUIRREL_WIDTH / 2; velocity.x = SOUIRREL_VELOCITY;

if(position.x > World.WORLD_WIDTH - SOUIRREL_WIDTH / 2) { position.x = World.WORLD_WIDTH - SOUIRREL_WIDTH / 2; velocity.x = - SOUIRREL_VELOCITY;

stateTime += deltaTime;

Squirrels are moving objects so we let the class derive from DynamicGameObject, which gives us a velocity and acceleration vector as well. The first thing we do is define a squirrel's size, as well as its velocity. Since a squirrel is animated we also keep track of its state time. A squirrel has a single state, like a coin: moving horizontally. Whether it moves to the left or right can be decided based on the velocity vector's x-component, so we don't need to store a separate state member for that.

In the constructor we of course call the superclass's constructor with the initial position and size of the squirrel. We also set the velocity vector to (SQUIRREL_VELOCITY,0). All squirrels will thus move to the right in the beginning.

The update() method updates the position and bounding shape of the squirrel based on the velocity and delta time. It's our standard Euler integration step, which we talked about and used a lot in the last chapter. We also check whether the squirrel hit the left or right edge of the world. If that's the case we simply invert its velocity vector so it starts moving in the oposite direction. Our world's width is fixed at a value of 10 meters, as discussed earlier. The last thing we do is update the state time based on the delta time so that we can decide which of the two animation frames we need to use for rendering that squirrel later on.

The Platform Class

The Platform class is shown in Listing 9-12. Listing 9-12. Platform.java, the Platform Class package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.DynamicGameObject;

public class Platform extends DynamicGameObject { public static final float PLATFORM_WIDTH = 2; public static final float PLATFORM_HEIGHT = 0.5f; public static final int PLATFORM_TYPE_STATIC = 0; public static final int PLATFORM_TYPE_MOVING = 1; public static final int PLATFORM_ STATE_NORMA L = 0; public static final int PLATFORM_STATE_PULVERIZING = 1; public static final float PLATFORM_PUL VERIZE_ TIME = 0.2f * 4; public static final float PLATFORM_ VELOCITY = 2;

Platforms are a little bit more complex, of course. Let's go over the constants defined in the class. The first two constants define the width and height of a platform, as discussed earlier. A platform has a type; it can be either a static platform or a moving platform. We denote this via the constants PLATFORM_TYPE_STATIC and PLATFORM_TYPE_MOVING. A platform can also be in one of two states: it can be in a normal state—that is, either sitting there statically or moving—or it can be pulverized. The state is encoded via one of the constants PLATFORM_STATE_NORMAL or PLATFORM_STATE_PULVERIZING. Pulverization is of course a process limited in time. We therefore define the time it takes for a platform to be completely pulverized, which is 0.8 seconds. This value is simply derived from the number of frames in the Animation of the platform and the duration of each frame—one of the little quirks we have to accept while trying to follow the MVC pattern. Finally we define the speed of moving platforms to be 2 m/s, as discussed earlier. A moving platform will behave exactly like a squirrel in that it just travels in one direction until it hits the world's horizontal boundaries, in which case it just inverts its direction.

int type; int state; float stateTime;

public Platform(int type, float x, float y) { super(x, y, PLATFORM_WIDTH, PLATFORM_HEIGHT); this.type = type;

this.state = PLATFORM_STATE_NORMAL; this.stateTime = 0; if (type == PLATFORM_TYPE_MOVING) { velocity.x = PLATFORM_VELOCITY;

To store the type, the state, and the state time of the Platform instance, we need three members. These get initialized in the constructor based on the type of the Platform, which is a parameter of the constructor, along with the platform center's position.

public void update(float deltaTime) { if (type == PLATFORM_TYPE_MOVING) {

position.add(velocity.x * deltaTime, 0);

bounds.lowerLeft.set(position).sub(PLATFORM_WIDTH / 2, PLATFORM_HEIGHT / 2);

if(position.x < PLATFORM_WIDTH / 2) { velocity.x = -velocity.x; position.x = PLATFORM_WIDTH / 2;

if(position.x > World.WORLD_WIDTH - PLATFORM_WIDTH / 2) { velocity.x = -velocity.x;

position.x = World.WORLD_WIDTH - PLATFORM_WIDTH / 2;

stateTime += deltaTime;

The update() method will move the platform and check for the out-of-world condition, acting accordingly by inverting the velocity vector. This is exactly the same thing we did in the Squirrel.update() method. We also update the state time at the end of the method.

public void pulverize() {

state = PLATFORM_STATE_PULVERIZING; stateTime = 0; velocity.x = 0;

The final method of this class is called pulverize(). It switches the state from PLATFORM_STATE_NORMAL to PLATFORM_STATE_PULVERIZING and resets the state time and velocity. This means that moving platforms will stop moving. The method will be called if the World class detects a collision between Bob and the Platform, and decides to pulverize the Platform based on a random number. We'll talk about that in a bit.

The Bob Class

First we need to talk about Bob. The Bob class is shown in Listing 9-13. Listing 9-13. Bob.java package com.badlogic.androidgames.jumper;

import com.badlogic.androidgames.framework.DynamicGameObject;

public class Bob extends DynamicGameObject{

public static final int BOB_STATE_JUMP = 0;

public static final int BOB_STATE_FALL = 1;

public static final int BOB_STATE_HIT = 2;

public static final float BOB_JUMP_VELOCITY = 11;

public static final float BOB_MOVE_VELOCITY = 20;

public static final float BOB_WIDTH = 0.8f;

public static final float BOB_HEIGHT = 0.8f;

We start with a couple of constants again. Bob can be in one of three states: jumping upward, falling downward, or being hit. He also has a vertical jump velocity, which is only applied on the y-axis, and a horizontal move velocity, which is only applied on the x-axis. The final two constants define Bob's width and height in the world. Of course, we also have to store Bob's state and state time.

int state; float stateTime;

public Bob(float x, float y) {

super(x, y, BOB_WIDTH, BOB_HEIGHT); state = BOB_STATE_FALL; stateTime = 0;

The constructor just calls the superclass's constructor so that Bob's center position and bounding shape are initialized correctly, and the initializes the state and stateTime member variables.

public void update(float deltaTime) {

velocity.add(World.gravity.x * deltaTime, World.gravity.y * deltaTime); position.add(velocity.x * deltaTime, velocity.y * deltaTime); bounds.lowerLeft.set(position).sub(bounds.width / 2, bounds.height / 2);

if (velocity.y > 0 && state != BOB_STATE_HIT) { if (state != BOB_STATE_JUMP) { state = BOB_STATE_JUMP; stateTime = 0;

if (velocity.y < 0 && state != BOB_STATE_HIT) { if (state != BOB_STATE_FALL) { state = BOB_STATE_FALL; stateTime = 0;

position.x = World.WORLD_WIDTH; if(position.x > World.WORLD_WIDTH) position.x = 0;

stateTime += deltaTime;

The update() method starts off by updating Bob's position and bounding shape based on gravity and his current velocity. Note that the velocity is a composite of the gravity and Bob's own movement due to jumping and moving horizontally. The next two big conditional blocks set Bob's state to either BOB_STATE_JUMPING or BOB_STATE_FALLING, and reinitialize his state time depending on the y component of his velocity. If it is greater than zero Bob is jumping, and if it is smaller than zero Bob is falling. We only do this if Bob hasn't been hit and if he isn't already in the correct state. Otherwise we'd always reset the state time to zero, which wouldn't play nice with Bob's animation later on. We also wrap Bob from one edge of the world to the other if he leaves the world to the left or right. Finally we update the stateTime member again.

Where does Bob get his velocity from apart from gravity? That's where the other methods come in.

public void hitSquirrel() { velocity.set(0,0); state = BOB_STATE_HIT; stateTime = 0;

public void hitPlatform() {

velocity.y = BOB_JUMP_VELOCITY; state = BOB_STATE_JUMP; stateTime = 0;

public void hitSpring() {

velocity.y = BOB_JUMP_VELOCITY * 1.5f; state = BOB_STATE_JUMP; stateTime = 0;

The method hitSquirrel() is called by the World class in case Bob hit a squirrel. If that's the case Bob stops moving by himself and enters the BOB_STATE_HIT state. Only gravity will apply to Bob from this point on; the player can't control him anymore and he doesn't interact with platforms anymore. That's similar to the behavior Super Mario exhibits when he gets hit by an enemy. He just falls down.

The hitPlatform() method is also called by the World class. It will be invoked when Bob hits a platform while falling downward. If that's the case, then we set his y velocity to BOB_JUMP_VELOCITY, and we also set his state and state time accordingly. From this point on Bob will move upward until gravity wins again, making Bob fall down.

The last method, hitSpring(), is invoked by the World class if Bob hits a spring. It does the same as the hitPlatform() method, with one exception: the initial upward velocity is set to 1.5 times BOB_JUMP_VELOCITY. This means that Bob will jump a little higher when hitting a spring compared to when hitting a platform.

The World Class

The last class we have to discuss is the World class. It's a little longer, so we'll split it up. Listing 9-14 shows the first part of the code.

Listing 9-14. Excerpt from World.java: Constants, Members, and Initialization package com.badlogic.androidgames.jumper;

import java.util.ArrayList; import java.util.List; import java.util.Random;

import com.badlogic.androidgames.framework.math.OverlapTester; import com.badlogic.androidgames.framework.math.Vector2;

public class World {

public interface WorldListener { public void jump(); public void highJump(); public void hit(); public void coin();

The first thing we define is an interface called WorldListener. What does it do? We need it to solve a little MVC problem: when do we play sound effects? We could just add invocations of Assets.playSound() to the respective simulation classes, but that's not very clean. Instead we'll let a user of the World class register a WorldListener, which will be called when Bob jumps from a platform, jumps from a spring, gets hit by a squirrel, or collects a coin. We will later register a listener that plays back the proper sound effects for each of those events, keeping the simulation classes clean from any direct dependencies on rendering and audio playback.

public static final float WORLD_WIDTH = 10; public static final float WORLD_HEIGHT = 15 * 20; public static final int WORLD_STATE_RUNNING = 0; public static final int WORLD_STATE_NEXT_LEVEL = 1; public static final int WORLD_STATE_GAME_OVER = 2; public static final Vector2 gravity = new Vector2(0, -12);

Next we define a couple of constants. The WORLD_WIDTH and WORLD_HEIGHT specify the extents of our world horizontally and vertically. Remember that our view frustum will show a region of 10x15 meters of our world. Given the constants defined here, our world will span 20 view frustums or screens vertically. Again, that's a value I came up with by tuning. We'll get back to it when we discuss how we generate a level. The world can also be in one of three states: running, waiting for the next level to start, or the game-over state—when Bob falls too far (outside of the view frustum). We also define our gravity acceleration vector as a constant here.

public final Bob bob;

public final List<Platform> platforms;

public final List<Spring> springs;

public final List<Squirrel> squirrels;

public final List<Coin> coins;

public Castle castle;

public final WorldListener listener;

public final Random rand;

public float heightSoFar; public int score; public int state;

Next up are all the members of the World class. It keeps track of Bob; all the Platforms, Springs, Squirrels, and Coins; and the Castle. Additionally it has a reference to a WorldListener and an instance of Random, which we'll use to generate random numbers for various purposes. The last three members keep track of the highest height Bob has reached so far, as well as the World's state and the score achieved.

public World(WorldListener listener) { this.bob = new Bob(5, 1); this.platforms = new ArrayList<Platform>(); this.springs = new ArrayList<Spring>(); this.squirrels = new ArrayList<Squirrel>(); this.coins = new ArrayList<Coin>(); this.listener = listener; rand = new Random(); generateLevel();

this.heightSoFar = 0; this.score = 0;

this.state = WORLD_STATE_RUNNING;

The constructor initializes all members and also stores the WorldListener passed as a parameter. Bob is placed in the middle of the world horizontally and a little bit above the ground at (5,1). The rest is pretty much self-explanatory, with one exception: the generateLevel() method.

Generating the World

You might have wondered already how we actually create and place the objects in our world. We use a method called procedural generation. We come up with a simple algorithm that will generate a random level for us. Listing 9-15 shows the code.

Listing 9-15. Exceprt from World.java: The generateLevel() Method private void generateLevel() {

float y = Platform.PLATFORM_HEIGHT / 2;

float maxJumpHeight = Bob.BOB_JUMP_VELOCITY * Bob.BOB_JUMP_VELOCITY / (2 * -gravity .y);

int type = rand.nextFloat() > 0.8f ? Platform. PLATFORM_TYPE_MOVING

: Platform. PLATFORM_TYPE_STATIC; float x = rand.nextFloat()

* (WORLD_WIDTH - Platform.PLATFORM_WIDTH) + Platform.PLATFORM_WIDTH / 2;

Platform platform = new Platform(type, x, y); platforms.add(platform);

&& type != Platform.PLATFORM_TYPE_MOVING) { Spring spring = new Spring(platform.position.x, platform.position.y + Platform.PLATFORM_HEIGHT / 2 + Spring.SPRING_HEIGHT / 2); springs.add(spring);

if (y > WORLD_HEIGHT / 3 && rand.nextFloat() > 0.8f) { Squirrel squirrel = new Squirrel(platform.position.x + rand.nextFloat(), platform.position.y + Squirrel.SOUIRREL_HEIGHT + rand.nextFloat() * 2); squirrels.add(squirrel);

Coin coin = new Coin(platform.position.x + rand.nextFloat(), platform.position.y + Coin.COIN_HEIGHT + rand.nextFloat() * 3);

coins.add(coin);

y += (maxJumpHeight - 0.5f); y -= rand.nextFloat() * (maxJumpHeight / 3);

Let me outline the general idea of the algorithm in plain words:

2. While we haven't reached the top of the world yet, do the following:

a. Create a platform, either moving or stationary at the current y position with a random x position.

b. Fetch a random number between 0 and 1, and if it is greater than 0.9 and if the platform is not moving, create a spring on top of the platform.

c. If we are above the first third of the level, fetch a random number, and if it is above 0.8, create a squirrel offset randomly from the platform's position.

d. Fetch a random number, and if it is greater than 0.6, create a coin offset randomly from the platform's position.

e. Increase y by the maximum normal jump height of Bob, decrease it a tiny bit randomly—but only so far that it doesn't fall below the last y value—and goto 2

3. Place the castle at the last y position, centered horizontally.

The big secret of this procedure is how we increase the y position for the next platform in step 2e. We have to make sure that each subsequent platform is reachable by Bob by jumping from the current platform. Bob can only jump as high as gravity allows, given his initial jump velocity of 11 m/s vertically. How can we calculate how high Bob will jump? With the following formula:

height = velocity x velocity / (2 x gravity) = 11 x 11 / (2 x 13) ~= 4.6m

This means we should have a distance of 4.6 meters vertically between each platform so that Bob can still reach it. To make sure all platforms are reachable, we use a value that's a little bit less than the maximum jump height. This guarantees that Bob will always be able to jump from one platform to the next. The horizontal placement of platforms is random again. Given Bob's horizontal movement speed of 20 m/s, we can be more than sure that he will not only be able to reach a platform vertically but also horizontally.

The other objects are created based on chance. The method Random.nextFloat() returns a random number between 0 and 1 on each invocation, where each number has the same probability of occuring. Squirrels are only generated when the random number we fetch from Random is greater than 0.8. This means that we'll generate a squirrel with a probability of 20 percent (1 - 0.8). The same is true for all other randomly created objects. By tuning this values we can have more or fewer objects in our world.

Updating the World

Once we have generated our world we can update all objects in it and check for collisions. Listing 9-16 shows the update methods of the World class.

Listing 9-16. Excerpt from World.java: The Update Methods public void update(float deltaTime, float accelX) { updateBob(deltaTime, accelX); updatePlatforms(deltaTime); updateSquirrels(deltaTime); updateCoins(deltaTime); if (bob.state != Bob.BOB_STATE_HIT)

checkCollisions(); checkGameOver();

The method update() is the one called by our game screen later on. It receives the delta time and acceleration on the x-axis of the accelerometer as an argument. It is responsible for calling the other update methods as well as performing the collision checks and gameover check. We have an update method for each object type in our world.

private void updateBob(float deltaTime, float accelX) {

if (bob.state != Bob.BOB_STATE_HIT && bob.position.y <= 0.5f)

bob.hitPlatform(); if (bob.state != Bob. BOB_STATE_HIT)

bob.velocity.x = -accelX / 10 * Bob. BOB_MOVE_VELOCITY; bob.update(deltaTime);

heightSoFar = Math.max(bob.position.y, heightSoFar);

The updateBob() method is responsible for updating Bob's state. The first thing it does is check whether Bob is hitting the bottom of the world, in which case Bob is instructed to jump. This means that at the start of each level Bob is allowed to jump off the ground of our world. As soon as the ground is out of sight, this won't work anymore, of course. Next we update Bob's horizontal velocity based on the value of the x-axis of the accelerometer we get as an argument. As discussed, we normalize this value from a range of -10 to 10 to a range of -1 to 1 (full left tilt to full right tilt), and the multiply it by Bob's standard movement velocity. Next we tell Bob to update himself by calling the Bob.update() method. The last thing we do is keep track of the highest y position Bob has reached so far. We need this to determine whether Bob has fallen too far later on.

private void updatePlatforms(float deltaTime) { int len = platforms.size(); for (int i = 0; i < len; i++) {

Platform platform = platforms.get(i); platform.update(deltaTime);

if (platform.state == Platform. PLATFORM_STATE_PULVERIZING

&& platform.stateTime > Platform.PLATFORM_PULVERIZE_TIME) { platforms.remove(platform); len = platforms.size();

Next we update all the platforms in updatePlatforms(). We loop through the list of platforms and call each platform's update() method with the current delta time. In case the platform is in the process of pulverization, we check for how long that has been going on. If the platform is in the PLATFORM_STATE_PULVERIZING state for more than PLATFORM_PULVERIZE_TIME, we simply remove the platform from our list of platforms.

private void updateSquirrels(float deltaTime) { int len = squirrels.size(); for (int i = 0; i < len; i++) {

Squirrel squirrel = squirrels.get(i); squirrel.update(deltaTime);

private void updateCoins(float deltaTime) { int len = coins.size(); for (int i = 0; i < len; i++) { Coin coin = coins.get(i); coin.update(deltaTime);

In the updateSquirrels() method we update each Squirrel instance via its update() method, passing in the current delta time. We do the same for coins in the updateCoins() method.

Collision Detection and Response

Looking back at our original World.update() method, we can see that the next thing we do is check for collisions between Bob and all the other objects he can collide with in the world. We only do this if Bob is in a state not equal to BOB_STATE_HIT, in which case he just continues to fall down due to gravity. Let's have a look at those collision-checking methods in Listing 9-17.

Listing 9-17. Excerpt from World.java: The Collision-Checking Methods private void checkCollisions() { checkPlatformCollisions(); checkSquirrelCollisions(); checkItemCollisions(); checkCastleCollisions();

The checkCollisions() method is more or less another master method, which simply invokes all the other collision-checking methods. Bob can collide with a couple of things in the world: platforms, squirrels, coins, springs, and the castle. For each of those object types, we have a separate collision-checking method. Remember that we invoke this method and the slave methods after we have updated the positions and bounding shapes of all objects in our world. Think of it as a snapshot of the state of our world at the given point in time. All we do is observe this still image and see whether anything overlaps. We can then take action and make sure that the objects that collide react to those overlaps or collisions in the next frame by manipulating their states, positions, velocities, and so on.

private void checkPlatformCollisions() { if (bob.velocity.y > 0) return;

int len = platforms.size(); for (int i = 0; i < len; i++) {

Platform platform = platforms.get(i); if (bob.position.y > platform.position.y) { if (OverlapTester

.overlapRectangles(bob.bounds, platform.bounds)) { bob.hitPlatform(); listener.jump(); if (rand.nextFloat() > 0.5f) { platform.pulverize();

break;

In the checkPlatformCollisions() method we test for overlap between Bob and any of the platforms in our world. We break out of that method early in case Bob is currently on his way up. This way Bob can pass through platforms from below. For Super Jumper that's good behavior; in a game like Super Mario Brothers we'd probably want Bob to fall down if he hits a block from below. Next we loop through all platforms and check whether Bob is above the current platform. If he is, we test whether his bounding rectangle overlaps the bounding rectangle of the platform, in which case we tell Bob that he hit a platform via a call to Bob.hitPlatform(). Looking back at that method, we see that it will trigger a jump and set Bob's states accordingly. Next we call the WorldListener.jump() method to inform the listener that Bob has just started to jump again. We'll use this later on to play back a corresponding sound effect in the listener. The last thing we do is fetch a random number, and if it is above 0.5 we tell the platform to pulverize itself. It will be alive for another PLATFORM_PULVERIZE_TIME seconds (0.8) and will then be removed in the updatePlatforms() method shown earlier. When we render that platform, we'll use its state time to determine which of the platform animation keyframes to play back.

private void checkSquirrelCollisions() { int len = squirrels.size(); for (int i = 0; i < len; i++) {

Squirrel squirrel = squirrels.get(i);

if (OverlapTester.overIapRectangIes(squirrel.bounds, bob.bounds)) { bob.hitSquirrel(); listener.hit();

The method checkSquirrelCollisions() tests Bob's bounding rectangle against the bounding rectangle of each squirrel. If Bob hits a squirrel, we tell him to enter the BOB_STATE_HIT state, which will make him fall down without the player being able to control him any further. We also tell the WorldListener about it so he can play back a sound effect, for example.

private void checkItemCollisions() { int len = coins.size(); for (int i = 0; i < len; i++) { Coin coin = coins.get(i);

if (OverlapTester.overIapRectangIes(bob.bounds, coin.bounds)) { coins.remove(coin); len = coins.size(); listener.coin(); score += Coin.COIN_SCORE;

len = springs.size(); for (int i = 0; i < len; i++) { Spring spring = springs.get(i);

if (bob.position.y > spring.position.y) {

if (OverlapTester.overlapRectangles(bob.bounds, spring.bounds)) { bob.hitSpring(); listener.highJump();

The checkItemCollisions() method checks Bob against all coins in the world and against all springs. In case Bob hits a coin we remove the coin from our world, tell the listener that a coin was collected, and increase the current score by COIN_SCORE. In case Bob is falling downward, we also check Bob against all springs in the world. In case he hit one we till him about it so that he'll perform a higher jump than usual. We also inform the listener of this event.

private void checkCastleCollisions() {

if (OverlapTester.overlapRectangles(castle.bounds, bob.bounds)) { state = WORLD_STATE_NEXT_LEVEL;

The final method checks Bob against the castle. If Bob hits it, we set the world's state to WORLD_STATE_NEXT_LEVEL, signaling any outside entity (such as our game screen) that we should transition to the next level, which will again be a randomly generated instance of World.

Game Over, Buddy!

The last method in the World class, which is invoked in the last line of the World.update() method, is shown in Listing 9-18.

Listing 9-18. The Rest of World.java: The Game Over-Checking Method private void checkGameOver() {

if (heightSoFar - 7.5f > bob.position.y) { state = WORLD_STATE_GAME_OVER;

Remember how we defined the game-over state: Bob must leave the bottom of the view frustum. The view frustum is of course governed by a Camera2D instance, which has a position. The y-coordinate of that position is always equal to the biggest y-coordinate Bob has had so far, so the camera will somewhat follow Bob on his way upward. Since we want to keep the rendering and simulation code separate, we don't have a reference to the camera in our world, though. We thus keep track of Bob's highest y-coordinate in updateBob() and store that value in heightSoFar. We know that our view frustum will have a height of 15 meters. Thus we also know that if Bob's y-coordinate is below heightSoFar - 7.5, then he has left the view frustum on the bottom edge. That's when Bob is declared to be dead. Of course, this is a tiny bit hackish, as it is based on the assumption that the view frustum's height will always be 15 meters and that the camera will always be at the highest y-coordinate Bob has been able to reach so far. If we'd allowed zooming or used a different camera-following method, this would not hold true anymore. Instead of overcomplicating things, we'll just leave it as is, though. You will often face such decisions in game development, as it is hard at times to keep everything clean from a software engineering point of view (as evidenced by our overuse of public or package private members).

You may be wondering why we don't use the SpatialHashGrid class we developed in the last chapter. I'll show you the reason in a minute. Let's get our game done by implementing the GameScreen class first.

0 0

Post a comment