Sponsored sites

Showing posts with label Game Development for Android. Show all posts
Showing posts with label Game Development for Android. Show all posts

Tuesday, 7 October 2014

Getting Started in Android Game Development with libgdx – Tutorial Part 3 – Jumping, Gravity and improved movement

This is the third part in the Building a Game with LibGdx series. Make sure you read the previous articles to build a context for this one.
In the previous article we have animated Bob’s movement, but the movement is quite robotic. In this article I’ll try to make Bob jump and also move in a more natural way. I will achieve this by using a little physics. I will also clean up the code a little and fix some issues that crept into the code in the previous articles.

Jumping – the physics

Jumping is the action performed by an entity (Bob in our case) which propels itself into the air and lands back onto the ground (substrate). This is achieved by applying a force big enough against the force exercised by the ground (gravity) on the object.
Identifying the objects we have:
  • Bob – entity
  • Ground – substrate
  • Gravity (G) – the constant force of gravity that acts on all entities in the world
To implement realistic jumping we will simply need to apply Newton’s laws of motion. If we add the necessary attributes (mass, gravity, friction) to Bob and the world we have everything we need to implement jumping.
Look at the following diagram and examine its components. The left side is when we hold down the ‘jump’ button and the right side shows Bob in a jump.
Forces in jump
Let’s examine the forces in different states of Bob.
1. Bob is idle and on the ground (grounded).
In this case, only the gravity acts on Bob. That means Bob is being pulled down with a constant force.
The formula to calculate the force that pulls an object to the ground is
F=m*a
where m is the mass (think weight although is not weight) and a is the acceleration.
We are simplifying things and consider Bob as having a mass of 1 so the force is equal to the acceleration.
If we apply a constant force to an object, its velocity increases infinitely.
The formula to calculate an object’s velocity is:
v=u+a*t
where
  • v – is the final velocity
  • u – is the initial velocity (the velocity which t seconds ago)
  • a – is the acceleration
  • t – is the time elapsed since the acceleration is being applied
If we place Bob in the middle of the air that means the starting velocity is 0. If we consider that the Earth’s gravitational acceleration is 9.8 and Bob’s weight (mass) is 1 then it’s easy to calculate his falling speed after a second.
v = 0 + 9.8 * 1 = 9.8m/s
So after a second in free fall, Bob accelerated from 0 to 9.8 meters per second which is 35.28 kph or 21.92 mph. That is very fast.
If we want to know his velocity after a further second we would use the same formula.
v = 9.8 + 9.8 * 1 = 19.6m/s
That is 70.56 kph or 43.84 mph which is very fast.
We already see that the acceleration is linear and that under a constant force an object will accelerate infinitely. This is in an ideal environment where there is no friction and drag. Because the air has friction and it also applies some forces to the falling object, the falling object will reach a terminal velocity at some point, past which it won’t accelerate. This depends on a lot of factors which we will ignore.
Once the falling object hit the ground, it will stop, the gravity won’t affect it any more. This is not true however but we are not building a complete physics simulator but a game where Bob won’t get killed if he hits the ground at terminal velocity.
Reformulating it, we check if Bob has hit the ground, and if so then we will ignore gravity.
Making Bob jump
To make Bob jump, we need a force pointing opposite gravity (upward) which not just cancels the effect of gravity but thrusts Bob into the air. If you check the diagram, that force (F) is much stronger (its magnitude or length is much greater than that of the gravity’s vector). By adding the 2 vectors together (G and F) we obtain the final force that will act on Bob.
To simplify things, we can get rid of vectors and work only with their Y components.
On Earth, G = 9.8m/s^2. Because it is pointing down, we it’s actually -9.8 m/s^2. When Bob jumps, he does nothing more, than generating enough force to produce enough acceleration that will get him to height (h) before gravity (G) takes him back to the ground.
Because Bob is a human like us, he can’t maintain the acceleration once he is airborne, not without a jetpack at least. To simulate this, we could create a huge force when we press the ‘jump’ key. By applying the above formulas, the initial velocity will be high enough so even if gravity will act on Bob he will still climb to a point after which he starts the free falling sequence.
If we implement this method we will have a really nice realistic looking jump.
If we carefully check the original star guard game, the hero can jump to different heights depending on how long we press down the jump button. This is easily dealt with if we keep the up pointing force applied as long as we hold down the jump key and cut it off after a certain amount of time, jut to make sure that Bob does not start to fly.

Implement Jump

I think it was enough physics, let’s see how we implement the jump.
We will also do a little housekeeping task and reorganise the code. I want to isolate the jumping and movement so I will ignore the rest of the world. To see what has been modified in the code, scroll down to the Refactoring section.
Open up BobController.java. This is the old WorldController.java but was renamed. It made sense since we control Bob with it.
public class BobController {

	enum Keys {
		LEFT, RIGHT, JUMP, FIRE
	}

	private static final long LONG_JUMP_PRESS 	= 150l;
	private static final float ACCELERATION 	= 20f;
	private static final float GRAVITY 			= -20f;
	private static final float MAX_JUMP_SPEED	= 7f;
	private static final float DAMP 			= 0.90f;
	private static final float MAX_VEL 			= 4f;
	
	// these are temporary
	private static final float WIDTH = 10f;

	private World 	world;
	private Bob 	bob;
	private long	jumpPressedTime;
	private boolean jumpingPressed;
	
	// ... code omitted ... //

	public void jumpReleased() {
		keys.get(keys.put(Keys.JUMP, false));
		jumpingPressed = false;
	}

	// ... code omitted ... //
	/** The main update method **/
	public void update(float delta) {
		processInput();
		
		bob.getAcceleration().y = GRAVITY;
		bob.getAcceleration().mul(delta);
		bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
		if (bob.getAcceleration().x == 0) bob.getVelocity().x *= DAMP;
		if (bob.getVelocity().x > MAX_VEL) {
			bob.getVelocity().x = MAX_VEL;
		}
		if (bob.getVelocity().x < -MAX_VEL) {
			bob.getVelocity().x = -MAX_VEL;
		}
		
		bob.update(delta);
		if (bob.getPosition().y < 0) {
			bob.getPosition().y = 0f;
			bob.setPosition(bob.getPosition());
			if (bob.getState().equals(State.JUMPING)) {
					bob.setState(State.IDLE);
			}
		}
		if (bob.getPosition().x < 0) {
			bob.getPosition().x = 0;
			bob.setPosition(bob.getPosition());
			if (!bob.getState().equals(State.JUMPING)) {
				bob.setState(State.IDLE);
			}
		}
		if (bob.getPosition().x > WIDTH - bob.getBounds().width ) {
			bob.getPosition().x = WIDTH - bob.getBounds().width;
			bob.setPosition(bob.getPosition());
			if (!bob.getState().equals(State.JUMPING)) {
				bob.setState(State.IDLE);
			}
		}
	}

	/** Change Bob's state and parameters based on input controls **/
	private boolean processInput() {
		if (keys.get(Keys.JUMP)) {
			if (!bob.getState().equals(State.JUMPING)) {
				jumpingPressed = true;
				jumpPressedTime = System.currentTimeMillis();
				bob.setState(State.JUMPING);
				bob.getVelocity().y = MAX_JUMP_SPEED; 
			} else {
				if (jumpingPressed && ((System.currentTimeMillis() - jumpPressedTime) >= LONG_JUMP_PRESS)) {
					jumpingPressed = false;
				} else {
					if (jumpingPressed) {
						bob.getVelocity().y = MAX_JUMP_SPEED;
					}
				}
			}
		}
		if (keys.get(Keys.LEFT)) {
			// left is pressed
			bob.setFacingLeft(true);
			if (!bob.getState().equals(State.JUMPING)) {
				bob.setState(State.WALKING);
			}
			bob.getAcceleration().x = -ACCELERATION;
		} else if (keys.get(Keys.RIGHT)) {
			// left is pressed
			bob.setFacingLeft(false);
			if (!bob.getState().equals(State.JUMPING)) {
				bob.setState(State.WALKING);
			}
			bob.getAcceleration().x = ACCELERATION;
		} else {
			if (!bob.getState().equals(State.JUMPING)) {
				bob.setState(State.IDLE);
			}
			bob.getAcceleration().x = 0;
			
		}
		return false;
	}
}
Take a bit of time to analyse what we have added to this class.
The following lines are explained:
#07 – #12 – constants containing values that affect the world and Bob
  • LONG_JUMP_PRESS – time in milliseconds before the thrust applied to jump is cut off. Remember that we are doing high jumps and the longer the player presses the button the higher Bob jumps. To prevent flying we will cut off the jump propulsion after 150 ms.
  • ACCELERATION – this is actually used for walking/running. It is exactly the same principle as jumping but on the horizontal X axis
  • GRAVITY – this is the gravity acceleration (G pointing down in the diagram)
  • MAX_JUMP_SPEED – this is the terminal velocity which we will never exceed when jumping
  • DAMP – this is to smooth out movement when Bob stops. He won’t stop that sudden. More on this later, ignore it for the jump
  • MAX_VEL – the same as MAX_JUMP_SPEED but for movement on the horizontal axis
#15 – this is a temporary constant and it’s the width of the world in world units. It is used to limit Bob’s movement to the screen
#19 – jumpPressedTime is the variable that cumulates the time the jump button is being pressed for
#20 – a boolean which is true if the jump button was pressed
#26 – the jumpReleased() has to set the jumpingReleased variable to false. It is just a simple state variable
Following the main update method which does most of the work for us.
#32 – calls the processInput as usual to check if any keys were pressed
Moving to the processInput
#71 – checks if the JUMP button is pressed
#72 – #76 – in case Bob is not in the JUMPING state (meaning he is on the ground) the jumping is initiated. Bob is set to the jumping state and he is ready for take off. We cheat a little here and instead of applying the force pointing up, we set Bob’s vertical velocity to the maximum speed he can jump with (line #76). We also store the time in milliseconds when the jump was initiated.
#77 – #85 – this gets executed whenever Bob is in the air. In case we still press the jump button we check if the time elapsed since the initiation of the jump is greater than the threshold we set and if we are still in the cut-off time (currently 150ms) we maintain Bob’s vertical speed.
Ignore lines #87-107 as they are for horizontal walking.
Going back to the update method we have:
#34 – Bob’s acceleration is set to GRAVITY. This is because the gravity is a constant and we start from here
#35 – we calculate the acceleration for the time spent in this cycle. Our initial values are in units/seconds so we need to adjust the values accordingly. If we have 60 updates per second then the delta will be 1/60. It’s all handled for you by libgdx.
#36 – Bob’s current velocity gets updated with his acceleration on both axis. Remember that we are working with vectors in the Euclidean space.
#37 – This will smooth out Bob’s stopping. If we have NO acceleration on the X axis then we decrease it’s velocity by 10% every cycle. Having many cycles in a second, Bobo will come to a halt very quickly but very smoothly.
#38 – #43 – making sure Bob won’t exceed his maximum allowed speed (terminal velocity). This guards agains the law that says that an object will accelerate infinitely if a constant force acts on it.
#45 – calls Bob’s update method which does nothing else than updates Bob’s position according to his velocity.
#46 – #66 – This is a very basic collision detection which prevents Bob to leave the screen. We simply check if Bob’s position is outside the screen (using world coordinates) and if so, then we just place Bob back to the edge. It is worth noting that whenever Bob hits the ground or reaches the edge of the world (screen), we set his status to Idle. This allows us to jump again.
If we run the application with the above changes, we will have to following effect:

Housekeeping – refactoring

We notice that in the resulting application there are no tiles and Bob is not constrained only by the screen edges.
There is also a different image for when Bob is in the air. One image when he is jumping and one when he is falling.
We did the following:
  • Renamed WorldController to BobController. It made sense since we control Bob with it.
  • Commented out the drawBlocks() in WorldRenderer's render() method. We don’t render the tiles now because we ignore them.
  • Added the setDebug() method to the WorldRendered and the supporting toggle function in GameScreen.java. Debug rendering now can be toggled by pressing D on the keyboard in desktop mode.
  • WorldRenderer has new texture regions to represent the jumping and falling Bob. We still maintain just one state though. How the world renderer knows when to display which, takes place by checking Bob’s vertical velocity (on the Y axis). If it’s positive, Bob is jumping, if it’s negative, Bob is falling.
    public class WorldRenderer {
    
    	// ... omitted ... //
    
    	private TextureRegion bobJumpLeft;
    	private TextureRegion bobFallLeft;
    	private TextureRegion bobJumpRight;
    	private TextureRegion bobFallRight;
    
    	private void loadTextures() {
    		TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("images/textures/textures.pack"));
    
    		// ... omitted ... //
    
    		bobJumpLeft = atlas.findRegion("bob-up");
    		bobJumpRight = new TextureRegion(bobJumpLeft);
    		bobJumpRight.flip(true, false);
    		bobFallLeft = atlas.findRegion("bob-down");
    		bobFallRight = new TextureRegion(bobFallLeft);
    		bobFallRight.flip(true, false);
    	}
    
    	private void drawBob() {
    		Bob bob = world.getBob();
    		bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight;
    		if(bob.getState().equals(State.WALKING)) {
    			bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(), true) : walkRightAnimation.getKeyFrame(bob.getStateTime(), true);
    		} else if (bob.getState().equals(State.JUMPING)) {
    			if (bob.getVelocity().y > 0) {
    				bobFrame = bob.isFacingLeft() ? bobJumpLeft : bobJumpRight;
    			} else {
    				bobFrame = bob.isFacingLeft() ? bobFallLeft : bobFallRight;
    			}
    		}
    		spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
    	}
    }
    
    The above code excerpt shows the important additions.
    #5-#8 – The new texture regions for jumping. We need one for left and one for right.
    #15-#20 – The preparation of the assets. We need to add a few more png images to the project. Check the star-assault-android/images/ directory and there you will see bob-down.png and bob-up.png. These were added and also the texture atlas recreated with the ImagePacker2 tool. See Part 2 on how to create it.
    #28-#33 – is the part where we determine which texture region to draw when Bob is in the air.
  • There were some bug fixes in Bob.java. The bounding box now has the same position as bob and the update takes care of that. Also the setPosition method updates the bounding boxes’ position. This had an impact on the drawDebug() method inside the WorldRenderer. Now we don’t need to worry about calculating the bounding boxes based on the tiles’ position as the boxes now have the same position as the entity. This was a stupid bug which I let to slip in. This will be very important when doing collision detection.
This list pretty much sums up all the changes but it should be very easy to follow through.

Getting Started in Android Game Development with libgdx – Tutorial Part 4 – Collision Detection

This is the fourth part of the libgdx tutorial in which we create a 2d platformer prototype modeled after Star Guard.
You can read up on the previous articles if you are interested in how we got here.
Following the tutorial so far we managed to have a tiny world consisting of some blocks, our hero called Bob who can move around in a nice way but the problem is, he doesn’t have any interaction with the world. If we switch the tile rendering back we would see Bob happily walking and jumping around without the blocks impending him. All the blocks get ignored. This happens because we never check if Bob actually collides with the blocks.
Collision detection is nothing more than detecting when two or more objects collide. In our case we need to detect when Bob collides with the blocks. What exactly is being checked is if Bob’s bounding box intersects with the bounding boxes of their respective blocks. In case it does, we have detected a collision. We take note of the objects (Bob and the block(s)) and act accordingly. In our case we need to stop Bob from advancing, falling or jumping, depending with which side of the block Bob collided with.

The quick and dirty way
The easy and quick way to do it is to iterate through all the blocks in the world and check if the blocks collide with Bob’s current bounding box. This works well in our tiny 10×7 world but if we have a huge world with thousands of blocks, doing the detection every frame becomes impossible without affecting performance.
A better way
To optimise the above solution we will selectively pick the tiles that are potential candidates for collision with Bob.
By design, the game world consists of blocks whose bounding boxes are axis aligned and their width and height are both 1 unit.
In this case our world looks like the following image (all the blocks/tiles are in unit blocks):
Blocks
The red squares represent the bounds where the blocks would have been placed if any. The yellow ones are placed blocks.
Now we can pick a simple 2 dimensional array (matrix) for our world and each cell will hold a Block or null if there is none. This is the map container.
We always know where Bob is so it is easy to work out in which cell we are.
The easy and lazy way to get the block candidates that Bob can collide with is to pick all the surrounding cells and check if Bob’s current bounding box in overlaps with one of the tiles that has a block.
collision-magnified
Because we also control Bob’s movement we have access to his direction and movement speed. This narrows our options down even further.
For example if Bob is heading left we have the following scenario
collision-candidates
The above image gives us 2 candidate cells (tiles) to check if the objects in those cells collide with Bob.
Remember that gravity is constantly pulling Bob down so we will always have to check for tiles on the Y axis. Based on the vertical velocity’s sign we know when Bob is jumping or falling. If Bob is jumping, the candidate will be the tile (cell) above him. A negative vertical velocity means that Bob is falling so we pick the tile from underneath him as a candidate.
If he is heading left (his velocity is < 0) then we pick the candidate on his left. If he's heading right (velocity > 0) then we pick the tile to his right. If the horizontal velocity is 0 that means we don’t need to bother with the horizontal candidates.
We need to make it optimal because we will be doing this every frame and we will have to do this for every enemy, bullet and whatever collideable entities the game will have.
What happens upon collision?
This is very simple in our case. Bob’s movement on that axis stops. His velocity on that axis will be set to 0. This can be done only if the 2 axis are checked separately. We will check for the horizontal collision first and if Bob collides, then we stop his horizontal movement.
We do the exact same thing on the vertical (Y) axis. It is simple as that.
Simulate first and render after
We need to be careful when we check for collision. We humans tend to think before we act. If we are facing a wall, we don’t just walk into it, we see and we estimate the distance and we stop before we hit the wall. Imagine if you were blind. You would need a different sensor than your eye. You would use your arm to reach out and if you feel the wall, you’d stop before you walked into it.
We can translate this to Bob, but instead of his arm we will use his bounding box.
First we displace his bounding box on the X axis by the distance it would have taken Bob to move according to his velocity and check if the new position would hit the wall (if the bounding box intersects with the block’s bounding box). If yes, then a collision has been detected. Bob might have been some distance away from the wall and in that frame he would have covered the distance to the wall and some more. If that’s the case, we will simply position Bob next to the wall and align his bounding box with the current position. We also set Bob’s speed to 0 on that axis.
The following diagram is an attempt to show just what I have described.


collision-position
The green box is where Bob currently stands. The displaced blue box is where Bob should be after this frame.
The purple are is how much Bob is into the wall. That is the distance we need to push Bob back so he stands next to the wall. We just set his position next to the wall to achieve this without too much computation.
The code for collision detection is actually very simple.
It all resides in the BobController.java. There are a few other changes too which I should mention prior to the controller.
The World.java has the following changes
01public class World {
02 
03    /** Our player controlled hero **/
04    Bob bob;
05    /** A world has a level through which Bob needs to go through **/
06    Level level;
07     
08    /** The collision boxes **/
09    Array<Rectangle> collisionRects = new Array<Rectangle>();
10 
11    // Getters -----------
12     
13    public Array<Rectangle> getCollisionRects() {
14        return collisionRects;
15    }
16    public Bob getBob() {
17        return bob;
18    }
19    public Level getLevel() {
20        return level;
21    }
22    /** Return only the blocks that need to be drawn **/
23    public List<Block> getDrawableBlocks(int width, int height) {
24        int x = (int)bob.getPosition().x - width;
25        int y = (int)bob.getPosition().y - height;
26        if (x < 0) {
27            x = 0;
28        }
29        if (y < 0) {
30            y = 0;
31        }
32        int x2 = x + 2 * width;
33        int y2 = y + 2 * height;
34        if (x2 > level.getWidth()) {
35            x2 = level.getWidth() - 1;
36        }
37        if (y2 > level.getHeight()) {
38            y2 = level.getHeight() - 1;
39        }
40         
41        List<Block> blocks = new ArrayList<Block>();
42        Block block;
43        for (int col = x; col <= x2; col++) {
44            for (int row = y; row <= y2; row++) {
45                block = level.getBlocks()[col][row];
46                if (block != null) {
47                    blocks.add(block);
48                }
49            }
50        }
51        return blocks;
52    }
53 
54    // --------------------
55    public World() {
56        createDemoWorld();
57    }
58 
59    private void createDemoWorld() {
60        bob = new Bob(new Vector2(7, 2));
61        level = new Level();
62    }
63}
#09collisionRects is just a simple array where I will put the rectangles Bob is colliding with in that particular frame. This is only for debug purposes and to show the boxes on the screen. It can and will be removed from the final game.
#13 – Just provides access to the collision boxes
#23getDrawableBlocks(int width, int height) is the method that returns the list of Block objects that are in the camera’s window and will be rendered. This method is just to prepare the application to render huge worlds without performance loss. It’s a very simple algorithm. Get the blocks surrounding Bob within a distance and return those to render. It’s an optimisation.
#61 – Creates the Level declared in line #06. It’s good to move out the level from the world as we want our game to have multiple levels. This is the obvious first step.
The Level.java can be found here.
As I mentioned before, the actual collision detection is in BobController.java
01public class BobController {
02    // ... code omitted ... //
03    private Array<Block> collidable = new Array<Block>();
04    // ... code omitted ... //
05 
06    public void update(float delta) {
07        processInput();
08        if (grounded && bob.getState().equals(State.JUMPING)) {
09            bob.setState(State.IDLE);
10        }
11        bob.getAcceleration().y = GRAVITY;
12        bob.getAcceleration().mul(delta);
13        bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
14        checkCollisionWithBlocks(delta);
15        bob.getVelocity().x *= DAMP;
16        if (bob.getVelocity().x > MAX_VEL) {
17            bob.getVelocity().x = MAX_VEL;
18        }
19        if (bob.getVelocity().x < -MAX_VEL) {
20            bob.getVelocity().x = -MAX_VEL;
21        }
22        bob.update(delta);
23    }
24 
25    private void checkCollisionWithBlocks(float delta) {
26        bob.getVelocity().mul(delta);
27        Rectangle bobRect = rectPool.obtain();
28        bobRect.set(bob.getBounds().x, bob.getBounds().y, bob.getBounds().width, bob.getBounds().height);
29        int startX, endX;
30        int startY = (int) bob.getBounds().y;
31        int endY = (int) (bob.getBounds().y + bob.getBounds().height);
32        if (bob.getVelocity().x < 0) {
33            startX = endX = (int) Math.floor(bob.getBounds().x + bob.getVelocity().x);
34        } else {
35            startX = endX = (int) Math.floor(bob.getBounds().x + bob.getBounds().width + bob.getVelocity().x);
36        }
37        populateCollidableBlocks(startX, startY, endX, endY);
38        bobRect.x += bob.getVelocity().x;
39        world.getCollisionRects().clear();
40        for (Block block : collidable) {
41            if (block == null) continue;
42            if (bobRect.overlaps(block.getBounds())) {
43                bob.getVelocity().x = 0;
44                world.getCollisionRects().add(block.getBounds());
45                break;
46            }
47        }
48        bobRect.x = bob.getPosition().x;
49        startX = (int) bob.getBounds().x;
50        endX = (int) (bob.getBounds().x + bob.getBounds().width);
51        if (bob.getVelocity().y < 0) {
52            startY = endY = (int) Math.floor(bob.getBounds().y + bob.getVelocity().y);
53        } else {
54            startY = endY = (int) Math.floor(bob.getBounds().y + bob.getBounds().height + bob.getVelocity().y);
55        }
56        populateCollidableBlocks(startX, startY, endX, endY);
57        bobRect.y += bob.getVelocity().y;
58        for (Block block : collidable) {
59            if (block == null) continue;
60            if (bobRect.overlaps(block.getBounds())) {
61                if (bob.getVelocity().y < 0) {
62                    grounded = true;
63                }
64                bob.getVelocity().y = 0;
65                world.getCollisionRects().add(block.getBounds());
66                break;
67            }
68        }
69        bobRect.y = bob.getPosition().y;
70        bob.getPosition().add(bob.getVelocity());
71        bob.getBounds().x = bob.getPosition().x;
72        bob.getBounds().y = bob.getPosition().y;
73        bob.getVelocity().mul(1 / delta);
74    }
75 
76    private void populateCollidableBlocks(int startX, int startY, int endX, int endY) {
77        collidable.clear();
78        for (int x = startX; x <= endX; x++) {
79            for (int y = startY; y <= endY; y++) {
80                if (x >= 0 && x < world.getLevel().getWidth() && y >=0 && y < world.getLevel().getHeight()) {
81                    collidable.add(world.getLevel().get(x, y));
82                }
83            }
84        }
85    }
86    // ... code omitted ... //
87}
The full source code is on github and I have tried to document it but I will go through the important bits here.
#03 – the collidable array will hold each frame the blocks that are the candidates for collision with Bob.
The update method is more concise now.
#07 – processing the input as usual and nothing changed there
#08 – #09 – resets Bob’s state if he’s not in the air.
#12 – Bob’s acceleration is transformed to the frame time. This is important as a frame can be very small (usually 1/60 second) and we want to do this conversion just once in a frame.
#13 – compute the velocity in frame time
#14 – is highlighted because this is where the collision detection is happening. I’ll go through that method in a bit.
#15 - #22 – Applies the DAMP to Bob to stop him and makes sure that Bob is not exceeding his maximum velocity.
#25 – the checkCollisionWithBlocks(float delta) method which sets Bob’s states, position and other parameters based on his collision or not with the blocks in the level.
#26 – transform velocity to frame time
#27 – #28 – We use a Pool to obtain a Rectangle which is a copy of Bob’s current bounding box. This rectangle will be displaced where bob should be this frame and checked against the candidate blocks.
#29 – #36 – These lines identify the start and end coordinates in the level matrix that are to be checked for collision. The level matrix is just a 2 dimensional array and each cell represents one unit so can hold one block. Check Level.java
#31 – The Y coordinate is set since we only look for the horizontal for now.
#32 – checks if Bob is heading left and if so, it identifies the tile to his left. The math is straight forward and I used this approach so if I decide that I need some other measurements for cells, this will still work.
#37 – populates the collidable array with the blocks within the range provided. In this case is either the tile on the left or on the right, depending on Bob’s bearing. Also note that if there is no block in that cell, the result is null.
#38 – this is where we displace the copy of Bob’s bounding box. The new position of bobRec is where Bob should be in normal circumstances. But only on the X axis.
#39 – remember the collisionRects from the world for debugging? We clear that array now so we can populate it with the rectangles that Bob is colliding with.
#40 – #47 – This is where the actual collision detection on the X axis is happening. We iterate through all the candidate blocks (in our case will be 1) and check if the block’s bounding box intersects Bob’s displaced bounding box. We use the bobRect.overlaps method which is part of the Rectangle class in libgdx and returns true if the 2 rectangles overlap. If there is an overlap, we have a collision so we set Bob’s velocity to 0 (line #43 add the rectangle to the world.collisionRects and break out of the detection.
#48 – We reset the bounding box’s position because we are moving to check collision on the Y axis disregarding the X.
#49 – #68 – is exactly the same as before but it happens on the Y axis. There is one additional instruction #61 – #63 and that sets the grounded state to true if a collision was detected when Bob was falling.
#69 – Bob’s rectangle copy is reset
#70 – Bob’s new velocity is being set which will be used to compute Bob’s new position.
#71 – #72 – Bob’s real bounds’ position is updated
#73 – We transform the velocity back to the base measurement units. This is very important.
And that is all for the collision of Bob with the tiles. Of course we will evolve this as more entities are added but for now is as good as it gets.
We cheated here a bit as in the diagram I stated that I will place Bob next to the Block when colliding but in the code I completely ignore the replacing. Because the distance is so tiny that we can’t even see it, it’s OK. It can be added, it won’t make much difference. If you decide to add it, make sure sure you set Bob’s position next next to the Block, a tiny bit farther so the overlap function will result false
There is a small addition to the WorldRenderer.java too.
01public class WorldRenderer {
02    // ... code omitted ... //
03    public void render() {
04        spriteBatch.begin();
05            drawBlocks();
06            drawBob();
07        spriteBatch.end();
08        drawCollisionBlocks();
09        if (debug)
10            drawDebug();
11    }
12 
13    private void drawCollisionBlocks() {
14        debugRenderer.setProjectionMatrix(cam.combined);
15        debugRenderer.begin(ShapeType.FilledRectangle);
16        debugRenderer.setColor(new Color(1, 1, 1, 1));
17        for (Rectangle rect : world.getCollisionRects()) {
18            debugRenderer.filledRect(rect.x, rect.y, rect.width, rect.height);
19        }
20        debugRenderer.end();
21    }
22    // ... code omitted ... //
23}
The addition of the drawCollisionBlocks() method which draws a white box wherever the collision is happening. It’s all for your viewing pleasure.
The result of the work we put in so far should be similar to this video:

This article should wrap up basic collision detection. Next we will look at extending the world, camera movement, creating enemies, using weapons, adding sound. Please share your ideas what should come first as all are important.

Measuring FPS

In the previous entry we have created a game loop that runs at a constant speed and constant (more or less) FPS.
How can we measure it? Check the new MainThread.java class.

001package net.obviam.droidz;
002 
003import java.text.DecimalFormat;
004 
005import android.graphics.Canvas;
006import android.util.Log;
007import android.view.SurfaceHolder;
008 
009 
010/**
011 * @author impaler
012 *
013 * The Main thread which contains the game loop. The thread must have access to
014 * the surface view and holder to trigger events every game tick.
015 */
016public class MainThread extends Thread {
017     
018    private static final String TAG = MainThread.class.getSimpleName();
019     
020    // desired fps
021    private final static int    MAX_FPS = 50;  
022    // maximum number of frames to be skipped
023    private final static int    MAX_FRAME_SKIPS = 5;   
024    // the frame period
025    private final static int    FRAME_PERIOD = 1000 / MAX_FPS;
026     
027    // Stuff for stats */
028    private DecimalFormat df = new DecimalFormat("0.##");  // 2 dp
029    // we'll be reading the stats every second
030    private final static int    STAT_INTERVAL = 1000; //ms
031    // the average will be calculated by storing
032    // the last n FPSs
033    private final static int    FPS_HISTORY_NR = 10;
034    // last time the status was stored
035    private long lastStatusStore = 0;
036    // the status time counter
037    private long statusIntervalTimer    = 0l;
038    // number of frames skipped since the game started
039    private long totalFramesSkipped         = 0l;
040    // number of frames skipped in a store cycle (1 sec)
041    private long framesSkippedPerStatCycle  = 0l;
042 
043    // number of rendered frames in an interval
044    private int frameCountPerStatCycle = 0;
045    private long totalFrameCount = 0l;
046    // the last FPS values
047    private double  fpsStore[];
048    // the number of times the stat has been read
049    private long    statsCount = 0;
050    // the average FPS since the game started
051    private double  averageFps = 0.0;
052 
053    // Surface holder that can access the physical surface
054    private SurfaceHolder surfaceHolder;
055    // The actual view that handles inputs
056    // and draws to the surface
057    private MainGamePanel gamePanel;
058 
059    // flag to hold game state
060    private boolean running;
061    public void setRunning(boolean running) {
062        this.running = running;
063    }
064 
065    public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
066        super();
067        this.surfaceHolder = surfaceHolder;
068        this.gamePanel = gamePanel;
069    }
070 
071    @Override
072    public void run() {
073        Canvas canvas;
074        Log.d(TAG, "Starting game loop");
075        // initialise timing elements for stat gathering
076        initTimingElements();
077         
078        long beginTime;     // the time when the cycle begun
079        long timeDiff;      // the time it took for the cycle to execute
080        int sleepTime;      // ms to sleep (<0 if we're behind)
081        int framesSkipped;  // number of frames being skipped
082 
083        sleepTime = 0;
084         
085        while (running) {
086            canvas = null;
087            // try locking the canvas for exclusive pixel editing
088            // in the surface
089            try {
090                canvas = this.surfaceHolder.lockCanvas();
091                synchronized (surfaceHolder) {
092                    beginTime = System.currentTimeMillis();
093                    framesSkipped = 0// resetting the frames skipped
094                    // update game state
095                    this.gamePanel.update();
096                    // render state to the screen
097                    // draws the canvas on the panel
098                    this.gamePanel.render(canvas);             
099                    // calculate how long did the cycle take
100                    timeDiff = System.currentTimeMillis() - beginTime;
101                    // calculate sleep time
102                    sleepTime = (int)(FRAME_PERIOD - timeDiff);
103                     
104                    if (sleepTime > 0) {
105                        // if sleepTime > 0 we're OK
106                        try {
107                            // send the thread to sleep for a short period
108                            // very useful for battery saving
109                            Thread.sleep(sleepTime);   
110                        } catch (InterruptedException e) {}
111                    }
112                     
113                    while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
114                        // we need to catch up
115                        this.gamePanel.update(); // update without rendering
116                        sleepTime += FRAME_PERIOD;  // add frame period to check if in next frame
117                        framesSkipped++;
118                    }
119 
120                    if (framesSkipped > 0) {
121                        Log.d(TAG, "Skipped:" + framesSkipped);
122                    }
123                    // for statistics
124                    framesSkippedPerStatCycle += framesSkipped;
125                    // calling the routine to store the gathered statistics
126                    storeStats();
127                }
128            } finally {
129                // in case of an exception the surface is not left in
130                // an inconsistent state
131                if (canvas != null) {
132                    surfaceHolder.unlockCanvasAndPost(canvas);
133                }
134            }   // end finally
135        }
136    }
137 
138    /**
139     * The statistics - it is called every cycle, it checks if time since last
140     * store is greater than the statistics gathering period (1 sec) and if so
141     * it calculates the FPS for the last period and stores it.
142     *
143     *  It tracks the number of frames per period. The number of frames since
144     *  the start of the period are summed up and the calculation takes part
145     *  only if the next period and the frame count is reset to 0.
146     */
147    private void storeStats() {
148        frameCountPerStatCycle++;
149        totalFrameCount++;
150         
151        // check the actual time
152        statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer);
153         
154        if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) {
155            // calculate the actual frames pers status check interval
156            double actualFps = (double)(frameCountPerStatCycle / (STAT_INTERVAL / 1000));
157             
158            //stores the latest fps in the array
159            fpsStore[(int) statsCount % FPS_HISTORY_NR] = actualFps;
160             
161            // increase the number of times statistics was calculated
162            statsCount++;
163             
164            double totalFps = 0.0;
165            // sum up the stored fps values
166            for (int i = 0; i < FPS_HISTORY_NR; i++) {
167                totalFps += fpsStore[i];
168            }
169             
170            // obtain the average
171            if (statsCount < FPS_HISTORY_NR) {
172                // in case of the first 10 triggers
173                averageFps = totalFps / statsCount;
174            } else {
175                averageFps = totalFps / FPS_HISTORY_NR;
176            }
177            // saving the number of total frames skipped
178            totalFramesSkipped += framesSkippedPerStatCycle;
179            // resetting the counters after a status record (1 sec)
180            framesSkippedPerStatCycle = 0;
181            statusIntervalTimer = 0;
182            frameCountPerStatCycle = 0;
183 
184            statusIntervalTimer = System.currentTimeMillis();
185            lastStatusStore = statusIntervalTimer;
186//          Log.d(TAG, "Average FPS:" + df.format(averageFps));
187            gamePanel.setAvgFps("FPS: " + df.format(averageFps));
188        }
189    }
190 
191    private void initTimingElements() {
192        // initialise timing elements
193        fpsStore = new double[FPS_HISTORY_NR];
194        for (int i = 0; i < FPS_HISTORY_NR; i++) {
195            fpsStore[i] = 0.0;
196        }
197        Log.d(TAG + ".initTimingElements()", "Timing elements for stats initialised");
198    }
199 
200}
I introduced a simple measuring function. I count the number of frames every second and store them in the fpsStore[] array. The storeStats() is called every tick and if the 1 second interval (STAT_INTERVAL = 1000;) is not reached then it simply adds the number of frames to the existing count.
If the one second is hit then it takes the number of rendered frames and adds them to the array of FPSs. After this I just reset the counters for the current statistics cycle and add the results to a global counter. The average is calculated on the values stored in the last 10 seconds.
Line 171 logs the FPS every second while line 172 sets the avgFps value of the gamePanel instance to be displayed on the screen.
The MainGamePanel.java class’s render method contains the the displayFps call which just draws the text onto the top right corner of the display every time the state is rendered. It also has a private member that is set from the thread.
01// the fps to be displayed
02private String avgFps;
03public void setAvgFps(String avgFps) {
04    this.avgFps = avgFps;
05}
06 
07public void render(Canvas canvas) {
08    canvas.drawColor(Color.BLACK);
09    droid.draw(canvas);
10    // display fps
11    displayFps(canvas, avgFps);
12}
13 
14private void displayFps(Canvas canvas, String fps) {
15    if (canvas != null && fps != null) {
16        Paint paint = new Paint();
17        paint.setARGB(255, 255, 255, 255);
18        canvas.drawText(fps, this.getWidth() - 50, 20, paint);
19    }
20}
Try running it. You should have the FPS displayed in the top right corner.
FPS displayed