In the last chapter, you added all of the game elements to the game world: bricks, a player paddle, and the ball. After running your code, however, you found that although you could move the player paddle, it had no effect on the gameplay. The ball starts in a random position and ends up falling off the bottom of the screen. The reason for this is the distinct lack of collision detection in your game.
In this chapter, you will add collision detection to the game.
The Purpose of Collision Detection
The strict definition of collision detection is, well, detecting when elements on the screen have collided. It is much more crucial than that, however. A good collision detection system tests for and evaluates when elements in the game world come into contact with one another. It also provides you with a method by which to react to those collisions.
The collision detection system for Prison Break is going to be basic, but it will show you how collision detection works and it will allow you to expand upon it as you see fit.
Keep in mind, OpenGL does not have any built-in collision detection or vertex testing capabilities; it just isn’t built for that. It is your responsibility as a game developer to provide that mechanism to the game.
Collision Detection in Prison Break
Before we begin to write the collision detection system for Prison Break, we need to discuss what it needs to do. It is always best to have a good idea of how you want a system to work before you begin to code it.
The following is a list of the items that we need to test in the collision detection system for Prison Break:
Has the ball hit the paddle?
Has the ball hit a brick?
Has the ball hit the right “wall” or edge of the screen?
Has the ball hit the left “wall” or edge of the screen?
Has the ball exited the upper boundary of the game?
Has the ball exited the lower boundary of the game?
In addition to these tests, the collision detection system has to perform the following actions:
“Destroy” any brick that is hit by the ball.
Calculate the angle of deflection and move the ball along if it hits the paddle or the right or left edges of the game screen.
While this seems like a lot, this is a surprisingly basic collision detection system for your game. Some collision detection systems are incredibly complex.
Creating the Collision Detection System
You will not need to create a new class for the collision detection system; rather, it will reside in a method of the PBGameRenderer. It is important that the collision detection system run on every frame of the game renderer. Therefore, the method will be called from the onDrawFrame().
Create a new method in your PBGameRenderer called detectCollisions(). Let’s take a step-by-step look at the code in detectCollisions(). Then I will give you the full code of the PBGameRenderer so that you can see it in context.
The first thing the method does is test to see if the ball is off the bottom edge of the screen. If it is, this indicates a game-over scenario. To determine if the ball is off the edge of the screen, simply test its y-axis position to see if it is less than 0.
if(ball.posY < = 0){
//GameOver
}
Note When you are testing the x- or y-coordinate positions of anything on the screen in Prison Break, the coordinate given represents the position of the upper left-hand corner of the vertex that the texture is mapped on to. This is important to note because you may need to take into account the width or height of the vertex when testing certain collisions.
Next, you test each brick to see whether or not the ball has contacted it.
for (int x = 0; x < wall.rows.length; x++)
{
for(int y = 0; y < wall.rows[x].bricks.length; y++)
{
if(!wall.rows[x].bricks[y].isDestroyed)
{
if (((ball.posY > wall.rows[x].bricks[y]. posY - .25f)
&& (ball.posY < wall.rows[x].bricks[y].posY)
&& (ball.posX + .25f > wall.rows[x].bricks[y].posX)
&& (ball.posX < wall.rows[x].bricks[y]. posX + 1.50f)))
{
wall.rows[x].bricks[y].isDestroyed = true;
PBGameVars.ballTargetY = PBGameVars. ballTargetY * -1f;
if(PBGameVars.ballTargetX == −2f){
PBGameVars.ballTargetX = 5f;
}else{
PBGameVars.ballTargetX = −2f;
}
}
}
}
}
Notice the if statement that tests if the ball has touched a brick. First, it tests if the y-axis position of the ball (the upper-left corner) is greater than the y-axis position of the brick minus 0.25. This is because the y-axis position of the brick is the upper-left corner of the brick; but we want to see if the ball has hit the bottom of the brick, so we subtract the height of the brick from its y-axis position to get the y-axis position of the bottom.
If the ball hits a brick, the isDestroyed flag of the brick is set to true. As you have seen from the drawBricks() method, any brick that has the isDestroyed flag set to true is not drawn. Therefore, on the next iteration of the game loop, the brick that has been hit will not be drawn and it will disappear from the game screen.
After the brick is destroyed, the ball bounces off of it. Therefore, you now need to calculate the ball’s angle of deflection. Luckily for you, all of the bricks lie at 90-degree angles and the angle of attack for the ball is always going to be (roughly) 45 degrees. This gives you a very clean and consistent angle of deflection. In fact, all you have to do to deflect the ball correctly is set the y-axis value to the inverse sign of itself.
Tip The angle of attack is the angle at which the ball bounces off of an object and continues on the game screen. In real-world physics, a ball bounces off a flat object set to a 90-degree angle at an angle inverse to the angle at which it approached the object. Therefore, a ball approaching a flat, 90-degree angle object at a 30-degree angle would bounce off of it at a 60-degree angle. To avoid the need for having to conduct a full physics lesson, the ball in Prison Break is always going to travel at a 45-degree angle, and every surface is going to be at a 90-degree angle. This way, every time the ball is deflected, it does so at a 45-degree angle.
Finally, you need to set the target for the ball. This was not discussed in the last chapter, but you are cheating a little to get the ball moving. By using the ballTargetX variable, you are giving the ball an x-axis value to “aim for” as it moves. This allows you to easily keep it on course. If the ball is moving right, the target is on the positive side of the x-axis; if the ball is moving left, the target is on the negative side of the x-axis.
When the ball has a collision, it is going to bounce; therefore, the target needs to flip to get the ball moving in the opposite direction.
The final section of the collision detection method tests to see if the ball has hit the player paddle. You will notice that the tests performed against the bricks, as well as the deflections, are also applied to the paddle.
if((ball.posY - .25f < = .5f)
&& (ball.posX + .25f > PBGameVars.playerBankPosX )
&& (ball.posX < PBGameVars.playerBankPosX + 1.50f)){
PBGameVars.ballTargetY = PBGameVars.ballTargetY * -1f;
if(PBGameVars.ballTargetX == −2f){
PBGameVars.ballTargetX = 5f;
}else{
PBGameVars.ballTargetX = −2f;
}
}
if(ball.posX < 0 || ball.posX + .25f > 3.75f)
{
PBGameVars.ballTargetX = PBGameVars.ballTargetX * -1f;
}
}
Let’s take a look at the finished method in context.
Tip If you want to tweak the collision detection system, change the detection on the ball using the y-axis position to use the y-position minus 0.125. This will give you a detection point that is closer to the top of the ball. I used the corner of the ball in the preceding code to destroy more bricks simultaneously and clear the screen faster.
Listing 6-1 shows the finished PBGameRenderer. The collision detection method and the call to it are bolded to make them easier to see.
Listing 6-1. PBGameRenderer
package com.jfdimarzio;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
public class PBGameRenderer implements Renderer{
private PBBackground background = new PBBackground();
private PBPlayer player1 = new PBPlayer();
private PBBall ball = new PBBall();
private PBTextures textureLoader;
private int[] spriteSheets = new int[3];
private int numberOfRows = 4;
private PBWall wall;
private long loopStart = 0;
private long loopEnd = 0;
private long loopRunTime = 0 ;
private float bgScroll1;
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
loopStart = System.currentTimeMillis();
// TODO Auto-generated method stub
try {
if (loopRunTime < PBGameVars.GAME_THREAD_FPS_SLEEP){
Thread.sleep(PBGameVars.GAME_THREAD_FPS_SLEEP - loopRunTime);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
drawBackground1(gl);
movePlayer1(gl);
drawBricks(gl);
moveBall(gl);
detectCollisions();
loopEnd = System.currentTimeMillis();
loopRunTime = ((loopEnd - loopStart));
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
gl.glViewport(0, 0, width,height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrthof(0f, 1f, 0f, 1f, -1f, 1f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig arg1) {
// TODO Auto-generated method stub
initializeBricks();
textureLoader = new PBTextures(gl);
spriteSheets = textureLoader.loadTexture(gl, PBGameVars.BRICK_ SHEET, PBGameVars.context, 1);
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
background.loadTexture(gl,PBGameVars.BACKGROUND, PBGameVars.context);
player1.loadTexture(gl,PBGameVars.PADDLE, PBGameVars.context);
}
private void drawBackground1(GL10 gl){
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(1f, 1f, 1f);
background.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
}
private void initializeBricks(){
wall = new PBWall(numberOfRows);
}
private void drawBricks(GL10 gl){
for (int x = 0; x < wall.rows.length; x++)
{
for(int y = 0; y < wall.rows[x].bricks.length; y++)
{
if(!wall.rows[x].bricks[y].isDestroyed)
{
switch (wall.rows[x].bricks[y].brickType){
case PBGameVars.BRICK_BLUE:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.50f, 0.25f, 0.0f);
wall.rows[x].bricks[y].draw(gl,spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_BROWN:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.50f, 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_DARK_GRAY:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.25f,0.25f , 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_GREEN:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.25f,0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_LITE_GRAY:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.25f, 0.0f, 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_PURPLE:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.50f, 0.0f, 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
case PBGameVars.BRICK_RED:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
default:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(wall.rows[x].bricks[y].posX, wall.rows[x].bricks[y].posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, 0.0f);
wall.rows[x].bricks[y].draw(gl, spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
break;
}
}
}
}
}
private void moveBall(GL10 gl){
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
ball.posX + = (float) ((PBGameVars.ballTargetX - ball.posX )/ (ball.posY / (PBGameVars.ballTargetY )));
ball.posY - = PBGameVars.ballTargetY * 3;
gl.glTranslatef(ball.posX, ball.posY, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f,0.0f, 0.0f);
ball.draw(gl,spriteSheets);
gl.glPopMatrix();
gl.glLoadIdentity();
}
private void movePlayer1(GL10 gl){
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
if (PBGameVars.playerAction == PBGameVars.PLAYER_MOVE_LEFT_1 &&PBGameVars.playerBankPosX > 0)
{
PBGameVars.playerBankPosX = PBGameVars.playerBankPosX - PBGameVars.PLAYER_MOVE_SPEED;
}
else if(PBGameVars.playerAction == PBGameVars.PLAYER_MOVE_RIGHT_1 && PBGameVars.playerBankPosX < 2.5)
{
PBGameVars.playerBankPosX = PBGameVars.playerBankPosX + PBGameVars.PLAYER_MOVE_SPEED;
}
gl.glTranslatef(PBGameVars.playerBankPosX, .5f, 0f);
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f,0.0f, 0.0f);
player1.draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();
}
private void detectCollisions(){
if(ball.posY < = 0){
//GameOver
}
for (int x = 0; x < wall.rows.length; x++)
{
for(int y = 0; y < wall.rows[x].bricks.length; y++)
{
if(!wall.rows[x].bricks[y].isDestroyed)
{
if (((ball.posY > wall.rows[x].bricks[y].posY - .25f)
&& (ball.posY < wall.rows[x].bricks[y].posY)
&& (ball.posX + .25f > wall.rows[x].bricks[y].posX)
&& (ball.posX < wall.rows[x].bricks[y].posX + 1.50f)))
{
wall.rows[x].bricks[y].isDestroyed = true;
PBGameVars.ballTargetY = PBGameVars.ballTargetY * -1f;
if(PBGameVars.ballTargetX == −2f){
PBGameVars.ballTargetX = 5f;
}else{
PBGameVars.ballTargetX = −2f;
}
}
}
}
}
if((ball.posY - .25f < = .5f)
&& (ball.posX + .25f > PBGameVars.playerBankPosX )
&& (ball.posX < PBGameVars.playerBankPosX + 1.50f)){
PBGameVars.ballTargetY = PBGameVars.ballTargetY * -1f;
if(PBGameVars.ballTargetX == −2f){
PBGameVars.ballTargetX = 5f;
}else{
PBGameVars.ballTargetX = −2f;
}
}
if(ball.posX < 0 || ball.posX + .25f > 3.75f)
{
PBGameVars.ballTargetX = PBGameVars.ballTargetX * -1f;
}
}
}
Compile and run your game. You should now be able to play a fairly complete version of your game and break some bricks.
Summary
In this chapter, you learned how to make a basic collision detection system and add it to your game. This gave you a fairly complete version of your game. In the next chapter of the book, we explore several ways to keep score.
98.82.120.188