Chapter     16

Keeping Score

To this point in the book, many problems have been addressed pertaining to Android game development. From moving characters to collision detection, your game should be shaping up nicely. However, one fundamental problem has yet to be addressed: how do you keep score?

Scoring is an integral part of most games. Without a score, the player would have no way to determine how well they are progressing in the game and no way to compare their progress with that of other players. From the earliest days of video games, scores have been the center of many players’ bragging rights.

In this chapter, I will present solutions to some common problems related to keeping the score in a game. The solutions should be adaptable to most game types.

16.1 Assign Point Values to Objects

Problem

The game does not award a score to the player for destroying objects.

Solution

Modify an object’s class to assign it a score.

How It Works

This solution involves assigning point values to objects, and using those values as a score for the player. Assigning a value to an object in the game is very easy and only requires the modification of the object’s class.

In a game, objects can be anything that you want to assign a value to. For example, enemies, breakable objects, and on-screen goals (such as waypoints in levels) can all be assigned a point value that is then used to compute the player’s score.

To assign a point value to an enemy, modify the enemy’s class to add a property called pointValue. In this example we are going to modify the SBGEnemy() class, used multiple times in this book, and assign it a point value of 3.

public class SBGEnemy {
public boolean isDead = false;

public int pointValue = 3;
...

}

This same solution can be applied to all of the classes in your game, assigning each a point value that can then be used in the overall score of the game.

Another way this solution can be implemented is in a graduating format. For example, we can use one class to create multiple objects, each with a different point value. Take a look at the following class. This class is taken from a Breakout-style game that I wrote where one brick class was used to create five different kinds of brick.

The class is written in OpenGL ES 1;however, the OpenGL ES code is not important to the solution. Do not worry if your game is in OpenGL ES 2/3, as the modifications that are made to this class are not OpenGL ES version-specific and can be easily followed.

Listing 16-1.  PBBrick()

public class PBBrick {

public float posY = 0f;
public float posX = 0f;
public float posT = 0f;

public boolean isDestroyed = false;

public int brickType = 0;

private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;
private ByteBuffer indexBuffer;

private float vertices[] = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, .25f, 0.0f,
0.0f, .25f, 0.0f,
};

private float texture[] = {
0.0f, 0.0f,
0.25f, 0.0f,
0.25f, 0.25f,
0.0f, 0.25f,
};

private byte indices[] = {
0,1,2,
0,2,3,
};

public PBBrick(int type) {
brickType = type;

ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuf.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

indexBuffer = ByteBuffer.allocateDirect(indices.length);
indexBuffer.put(indices);
indexBuffer.position(0);
}

public void draw(GL10gl, int[] spriteSheet) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, spriteSheet[0]);

gl.glFrontFace(GL10.GL_CCW);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);

gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer);

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_CULL_FACE);
}

}

Let’s modify this class so that the five different types of bricks are each assigned a different point value ranging from 1 to 5.

Listing 16-2.  PBBrick()Modification

public class PBBrick {

public float posY = 0f;
public float posX = 0f;
public float posT = 0f;

public boolean isDestroyed = false;

public int brickType = 0;

public int pointValue = 0;

private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;
private ByteBuffer indexBuffer;

private float vertices[] = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, .25f, 0.0f,
0.0f, .25f, 0.0f,
};

private float texture[] = {
0.0f, 0.0f,
0.25f, 0.0f,
0.25f, 0.25f,
0.0f, 0.25f,
};

private byte indices[] = {
0,1,2,
0,2,3,
};

public PBBrick(int type) {
brickType = type;

switch(type){
case 1:
pointValue = 1;
break;
case 2:
pointValue = 2;
break;
case 3:
pointValue = 3;
break;
case 4:
pointValue = 4;
break;
case 5:
pointValue = 5;
break;

}

ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuf.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

indexBuffer = ByteBuffer.allocateDirect(indices.length);
indexBuffer.put(indices);
indexBuffer.position(0);
}

...

}

In Recipe 16.2, you will take the point values assigned to game objects and use them to create the player’s score.

16.2 Add and Track the Score

Problem

The game does not track the player’s score, even though each object is assigned a point value.

Solution

Use the game character’s class to track the overall score.

How It Works

In this solution, you are going modify the class for the player’s character to add a property. The property will be used to track the player’s overall score. Once the player character class is modified, you will modify the collision detection method to assign the correct point values to the new score property.

First, modify the player character class and add a new property called overallScore.

public class SuperBanditGuy {
public boolean isDead = false;

public int overallScore = 0;
...

}

In Chapter 15, you created a method for performing collision detection. Since this solution assumes that the basis for rewarding points will be some kind of collision (e.g., destroying an object), you will modify the collision detection method to assign points when necessary.

Listing 16-3.  detectCollisions()

private void detectCollisions(){
for (int y = 1; y <4; y ++){
if (playerFire[y].shotFired){
for (int x = 1; x <10; x++ ){ //assumes you have an array of 10 enemies
if(!enemies[x].isDestroyed){
if (((playerFire[y].posY>= enemies[x].posY
&& playerFire[y].posY<= enemies[x].posY + 1f )  ||
(playerFire[y].posY +.25f>= enemies[x].posY
&& playerFire[y].posY + .25f<= enemies[x].posY + 1f )) &&
((playerFire[y].posX>= enemies[x].posX
&& playerFire[y].posX<= enemies[x].posX + 1f) ||
(playerFire[y].posX + .25f>= enemies[x].posX
&& playerFire[y].posX + 25f<= enemies[x].posX + 1f ))){

//collision detected between enemy and a shot
goodguy.overallScore += enemies[x].pointValue;

}
}
}
}
}
}

As mentioned, this method was taken from Chapter 15. It is a basic method that tracks collisions on ten different enemies. The enemies[] array is an array of SBGEnemy() classes. The goodguy in the method is simply an instantiation of the SuperBanditGuy() class.

Using this solution, the overall score of the player character will be added to every time they destroy an enemy.

16.3 Write the Score to the Screen

Problem

The game does not display the player’s score to the screen.

Solution

Use multiple OpenGL shapes and a sprite sheet to display the score to the user.

How It Works

To track the score, add a new sprite sheet to the project that contains all of the digits used to display the score. This spritesheet can be seen in Figure 16-1.

9781430257646_Fig16-01.jpg

Figure 16-1. Score digits spritesheet

Next, create a new class called SBGScoreTile(). This class will be used to display a portrait-oriented score tile to the screen in both OpenGL ES 1 and OpenGL ES 2/3 (see Listings 16-4 and 16-5). Later, you will use a sprite sheet to display a specific score digit on the tile.

Listing 16-4.  SBGScoreTile() (OpenGL ES 1)

public class SBGScoreTile {

private FloatBuffer vertexBuffer;
private FloatBuffer textureBuffer;
private ByteBuffer indexBuffer;

private float vertices[] = {
0.0f, 0.0f, 0.0f,
0.25f, 0.0f, 0.0f,
0.25f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
};

private float texture[] = {
0.0f, 0.0f,
0.25f, 0.0f,
0.25f, 0.25f,
0.0f, 0.25f,
};

private byte indices[] = {
0,1,2,
0,2,3,
};

public SBGScoreTile() {

ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuf.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

indexBuffer = ByteBuffer.allocateDirect(indices.length);
indexBuffer.put(indices);
indexBuffer.position(0);
}

public void draw(GL10gl) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, spriteSheet[0]);

gl.glFrontFace(GL10.GL_CCW);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);

gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer);

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_CULL_FACE);
}

}

Listing 16-5.  SBGScoreTile()(OpenGL ES 2/3)

public class SBGScoreTile {

public float scoreX = 0;
public float scoreY = 0;

private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec2 TexCoordIn;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
"  gl_Position = uMVPMatrix * vPosition;" +
"  TexCoordOut = TexCoordIn;" +
"}";

private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"uniform sampler2D TexCoordIn;" +
 "uniform float scoreX;" +
"uniform float scoreY;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
" gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x +
scoreX,TexCoordOut.y + scoreY));"+
"}";

private float texture[] = {
0f, 0f,
1f, 0f,
1f, 1f,
0f, 1f,
};

private int[] textures = new int[1];
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private final FloatBuffer textureBuffer;
private final int program;
private int positionHandle;
private int matrixHandle;

static final int COORDS_PER_VERTEX = 3;
static final int COORDS_PER_TEXTURE = 2;
static float vertices[] = { -1f,  1f, 0.0f,
-1f, -1f, 0.0f,
1f, -1f, 0.0f,
1f,  1f, 0.0f };

private final short indices[] = { 0, 1, 2, 0, 2, 3 };

private final int vertexStride = COORDS_PER_VERTEX * 4;
public static int textureStride = COORDS_PER_TEXTURE * 4;

public void loadTexture(int texture, Context context) {
InputStream imagestream = context.getResources().openRawResource(texture);
      Bitmap bitmap = null;

android.graphics.Matrix flip = new android.graphics.Matrix();
flip.postScale(-1f, -1f);

try {

bitmap = BitmapFactory.decodeStream(imagestream);

}catch(Exception e){

}finally {
try {
imagestream.close();
imagestream = null;
} catch (IOException e) {
}
}

GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();

}

public SBGScoreTile() {

ByteBuffer byteBuff = ByteBuffer.allocateDirect(
byteBuff.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuff.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuff = ByteBuffer.allocateDirect(texture.length * 4);
byteBuff.order(ByteOrder.nativeOrder());
textureBuffer = byteBuff.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);

ByteBuffer indexBuffer = ByteBuffer.allocateDirect(
indexBuffer.order(ByteOrder.nativeOrder());
drawListBuffer = indexBuffer.asShortBuffer();
drawListBuffer.put(indices);
drawListBuffer.position(0);

int vertexShader = SBGGameRenderer.loadShader(
GLES20.GL_VERTEX_SHADER,vertexShaderCode);
int fragmentShader = SBGGameRenderer.loadShader(
GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
}

public void draw(float[] matrix) {

GLES20.glUseProgram(mProgram);

mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

GLES20.glEnableVertexAttribArray(mPositionHandle);

int vsTextureCoord = GLES20.glGetAttribLocation(mProgram, "TexCoordIn");
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
GLES20.glVertexAttribPointer(vsTextureCoord, COORDS_PER_TEXTURE,
GLES20.GL_FLOAT, false,
textureStride, textureBuffer);
GLES20.glEnableVertexAttribArray(vsTextureCoord);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
int fsTexture = GLES20.glGetUniformLocation(mProgram, "TexCoordOut");
int fsScoreX = GLES20.glGetUniformLocation(mProgram, "scoreX");
int fsScoreY = GLES20.glGetUniformLocation(mProgram, "scoreY");
GLES20.glUniform1i(fsTexture, 0);
GLES20.glUniform1f(fsScoreX, scoreX);
GLES20.glUniform1f(fsScoreY, scoreY);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}

Each tile should default to the 0 when drawn. This is accomplished in Listing 16-6 by performing a glTranslatef() to the coordinates of 0,0,0 in the texture matrix in OpenGL ES 1, and indirectly setting the TexCoordOut.x of the fragment shader to 0 in OpenGL ES 2/3 (for a more detailed look at how sprite sheets work see Chapter 6, “Loading a Sprite Sheet”).

Listing 16-6.  Drawing the Tile (OpenGL ES 1)

gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f , 0.0f);

Listing 16-7.  Drawing the Tile (OpenGL ES 2/3)

SBGScoreTile.scoreX = 0;
SBGScoreTile.scoreY = 0;

Simply advance the sprite sheet to the correct digit for the score. First, create a switch...case statement to set the x and y sprite sheet coordinate location for each corresponding digit.

Listing 16-8.  Tile switch Statement

switch(SuperBanditGuy){
case 0:
x = 0;
y = 0;
break;
case 1:
x = 0;
y = .25;
break;
case 2:
x = 0;
y = .50;
break;
case 3:
x = 0;
y = .75;
break;

...

}

Finally, use the x and y coordinates that are set in the switch statement to display the correct tile.

Listing 16-9.  Display the Tile (OpenGL ES 1)

gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
gl.glTranslatef(x, y ,0.0f);

Listing 16-10.  Display the Tile (OpenGL ES 2/3)

SBGScoreTile.scoreX = x;
SBGScoreTile.scoreY = y;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.171.103