Chapter     12

Moving an Enemy

Moving a character around the screen—be it a person, animal, robot, or vehicle—is one of the more crucial parts of a compelling game. Chances are, if you have tried to create a character that moves freely in a game, you have run into some issue.

This chapter will present solutions to help you add enemies to your game. Solutions in this chapter include loading enemies into predetermined locations within your game, and moving enemies along a specific path.

12.1 Load Enemies to Predetermined Locations

Problem

The game does not load enemies in the correct locations.

Solution

Use a class to determine where enemy spawn points are.

How It Works

Many game types have “spawn points” where characters will generate. To spawn enemies in these predetermined locations, you need to add some floats to your enemy class and then use these floats to translate the model matrix of the enemy to the spawn location.

The solutions in this chapter will be based on a basic character class, which in turn is based on the SBGBackground class from Chapters 7 and 8. Given that we are talking about enemies within the game now, let’s rename the class SBGEnemy. The contents of the class should appear as shown in Listings 12-1 and 12-2.

Listing 12-1.  SBGEnemy() (OpenGL ES 1)

public class SBGEnemy {

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, 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 SBGEnemy() {

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);
   }

}

Listing 12-2.  SBGEnemy() (OpenGL ES 2/3)

public class SBGEnemy {

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 texX;" +
"uniform float texY;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
" gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x +
texX,TexCoordOut.y + texY));"+
"}";
private float texture[] = {
0f, 0f,
.25f, 0f,
.25f, .25f,
0f, .25f,
};

private int[] textures = new int[1];
private final FloatBuffer vertexBuffer;
private final ShortBuffer indexBuffer;
private final FloatBuffer textureBuffer;
private final int mProgram;
private int mPositionHandle;
private int mMVPMatrixHandle;

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 SBGEnemy() {

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);

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

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

public void draw(float[] mvpMatrix, int texX, int texY, int[] spriteSheet) {
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, spriteSheet[0]);

int fsTexture = GLES20.glGetUniformLocation(mProgram, "TexCoordOut");
int fsTexX = GLES20.glGetUniformLocation(mProgram, "texX");
int fsTexY = GLES20.glGetUniformLocation(mProgram, "texY");
GLES20.glUniform1i(fsTexture, 0);
GLES20.glUniform1f(fsTexX, texX);
GLES20.glUniform1f(fsTexY, texY);

mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

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

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

GLES20.glDisableVertexAttribArray(mPositionHandle);
   }
}

Then modify this class and add two floats. One float will track the x axis spawn location and the other float will track the y axis spawn location (see Listing 12-3).

Listing 12-3.  Floats for Tracking Spawn Location

public class SBGEnemy {

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

...

}

Set these floats to the desired spawn location in the SBGEnemy constructor (see Listing 12-4).

Listing 12-4.  Assigning Values to the Location Floats

public class SBGEnemy {

public float posY = 0f;
public float posX = 0f;
...
public SBGEnemy() {
posX = .25;
posY = .25;
...
}
...

}

Now you can use the SBGEnemy.posX and SBGEnemy.posY in the glTranslatef() method call for OpenGL ES 1  to move the model matrix of the enemy to the spawn location before you draw it. You can use the same properties in the Matrix.translateM() method for OpenGL ES 2/3. The spawnEnemy() method, shown in Listings 12-5 and 12-6, can be created in your game to help you spawn enemies in a location.

Listing 12-5.  spawnEnemy()(OpenGL ES 1)

private SFEnemy enemy = new SFEnemy();

spawnEnemy(){
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glScalef(.25f, .25f, 1f);
gl.glTranslatef(enemy.posX, enemy.posY, 0f);
}

Listing 12-6.  spawnEnemy()(OpenGL ES 2/3)

private SFEnemy enemy = new SFEnemy();

spawnEnemy(){
Matrix.translateM(mTMatrix, 0, enemy.posX, enemy.posY, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mTMatrix, 0, mMVPMatrix, 0);
}

12.2 Load Enemies to Random Locations

Problem

The game needs to spawn enemies in random locations.

Solution

Modify the last solution to create “random” locations for spawning enemies.

How It Works

Many games will spawn enemies in random locations. This adds a level of difficulty to your game by taking away the predictability of predetermined spawn locations.

The code from the last solution can easily be modified to generate random spawn locations. Listings 12-7 and 12-8 show the modification that should be made to accommodate having a random spawn location.

Listing 12-7.  SBGEnemy() for Random Locations (OpenGL ES 1)

public class SBGEnemy {

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

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, 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 SBGEnemy() {
Random randomPos = new Random();
posX = randomPos.nextFloat() * 3;
posY = randomPos.nextFloat() * 3;

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);
   }

}

Listing 12-8.  SBGEnemy() for Random Locations (OpenGL ES 2/3)

public class SBGEnemy {

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 texX;" +
"uniform float texY;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
" gl_FragColor = texture2D(TexCoordIn, vec2(TexCoordOut.x +
texX,TexCoordOut.y + texY));"+
"}";
private float texture[] = {
0f, 0f,
.25f, 0f,
.25f, .25f,
0f, .25f,
};

private int[] textures = new int[1];
private final FloatBuffer vertexBuffer;
private final ShortBuffer indexBuffer;
private final FloatBuffer textureBuffer;
private final int mProgram;
private int mPositionHandle;
private int mMVPMatrixHandle;

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 SBGEnemy() {
Random randomPos = new Random();
posX = randomPos.nextFloat() * 3;
posY = randomPos.nextFloat() * 3;

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);

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

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

public void draw(float[] mvpMatrix, int texX, int texY, int[] spriteSheet) {
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, spriteSheet[0]);

int fsTexture = GLES20.glGetUniformLocation(mProgram, "TexCoordOut");
int fsTexX = GLES20.glGetUniformLocation(mProgram, "texX");
int fsTexY = GLES20.glGetUniformLocation(mProgram, "texY");
GLES20.glUniform1i(fsTexture, 0);
GLES20.glUniform1f(fsTexX, texX);
GLES20.glUniform1f(fsTexY, texY);

mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

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

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

GLES20.glDisableVertexAttribArray(mPositionHandle);
   }
}

This solution builds on the posX and posY properties that you created in the previous solution. Rather than populate these properties with static values, the constructor of the SBGEnemy class will now populate random locations into the posX and posY floats. The result is that now when you call SBGEnemy.posX and SBGEnemy.posY from the spanEnemy() method, the enemy will be created at a random location on the screen.

12.3 Move Enemies Along a Path

Problem

The enemies do not move along a predetermined path.

Solution

Use an algorithm to create paths for the characters to automatically move along.

How It Works

This solution is geared toward moving your enemy along a specific path, known as a Bezier curve. Bezier curves are commonly used in games because they can be easily produced by a fairly simple algorithm. They can also be modified to create variations that make games interesting and unpredictable. Figure 12-1 illustrates what a Bezier curve looks like.

9781430257646_Fig12-01.jpg

Figure 12-1. A quadratic Bezier curve

For the enemy to move in a quadratic Bezier curve from the top to the bottom of the screen, you will need two methods. You can create one method to get the next x-axis value on the Bezier curve, and one method to give you the next y-axis value on the Bezier curve. Each time you call these methods, you will be given the next place on the x and y axes that the particular enemy needs to be moved to. Once you have these locations, you use glTranslatef() to move the model matrix to the calculated position.

Luckily, it is fairly simple to plot points on a Bezier curve. To construct a quadratic Bezier curve, you need four Cartesian points: a start, an end, and two curving points somewhere in between for the curve to wrap around. Let’s review how to do this now.

Create eight new floats to track the x and y coordinates of these points, as shown in Listing 12-9.

Listing 12-9.  Bezier Tracking Coordinates

public static final float BEZIER_X_1 = 0f;
public static final float BEZIER_X_2 = 1f;
public static final float BEZIER_X_3 = 2.5f;
public static final float BEZIER_X_4 = 3f;
public static final float BEZIER_Y_1 = 0f;
public static final float BEZIER_Y_2 = 2.4f;
public static final float BEZIER_Y_3 = 1.5f;
public static final float BEZIER_Y_4 = 2.6f;

Modify the SBGEnemy class to add a posT float to the existing posX and posY, as shown in Listing 12-10.

Listing 12-10.  posT

public class SBGEnemy {
public float posY = 0f; //the x position of the enemy
public float posX = 0f; //the y position of the enemy
public float posT = 0f; //the t used in calculating a Bezier curve

...
}

The key value in plotting the points is called the t position. The t position tells the formula where on the curve you are, thus allowing the formula to calculate the x or y coordinate for that single position.

Tip   If you do not understand the math behind the following formulas, there are many great resources, including a wiki, for Bezier curves.

Create two methods in your SBGEnemy() class(see Listing 12-11). One method is used to get the next x-axis value, and one is used to get the next y-axis value. Also, add random values to the posX and posY floats, and a set value to posT.

Listing 12-11.  Seeding the Position Values

public class SBGEnemy {
public float posY = 0f; //the x position of the enemy
public float posX = 0f; //the y position of the enemy
public float posT = 0f; //the t used in calculating a Bezier curve

public SBGEnemy() {
posY = (randomPos.nextFloat() * 4) + 4;
posX = randomPos.nextFloat() * 3;
posT = .012;

}

public float getNextPosX(){

}
public float getNextPosY(){

}

}

The formula to find a point on a quadratic Bezier curve on the y axis is as follows:

(y1*(t3)) + (y2 * 3 * (t2) * (1-t)) + (y3 * 3 * t * (1-t)2) + (y4* (1-t)3)

Note   To get the x-axis point, simply replace y with x in the preceding equation.

Use this formula in your getNextPosY() to calculate your enemy’s position (see Listing 12-12).

Listing 12-12.  getNextPosY()

public class SBGEnemy {
public float posY = 0f; //the x position of the enemy
public float posX = 0f; //the y position of the enemy
public float posT = 0f; //the t used in calculating a Bezier curve

public SBGEnemy() {
posY = (randomPos.nextFloat() * 4) + 4;
posX = randomPos.nextFloat() * 3;
posT = .012;

}

public float getNextPosX(){

}

public float getNextPosY(){
return (float)((BEZIER_Y_1*(posT*posT*posT)) +
(BEZIER_Y_2 * 3 * (posT * posT) * (1-posT)) +
(BEZIER_Y_3 * 3 * posT * ((1-posT) * (1-posT))) +
(BEZIER_Y_4 * ((1-posT) * (1-posT) * (1-posT))));
}

}

Use this same formula for the x axis, with one minor change, as shown in Listing 12-13.

Listing 12-13.  getNextPosX()

public class SBGEnemy {
public float posY = 0f; //the x position of the enemy
public float posX = 0f; //the y position of the enemy
public float posT = 0f; //the t used in calculating a Bezier curve

public SBGEnemy() {
posY = (randomPos.nextFloat() * 4) + 4;
posX = randomPos.nextFloat() * 3;
posT = sfengine.SCOUT_SPEED;

}

public float getNextPosX(){
return (float)((BEZIER_X_4*(posT*posT*posT)) +
(BEZIER_X_3 * 3 * (posT * posT) * (1-posT)) +
(BEZIER_X_2 * 3 * posT * ((1-posT) * (1-posT))) +
(BEZIER_X_1 * ((1-posT) * (1-posT) * (1-posT))));

 }

public float getNextPosY(){
return (float)((BEZIER_Y_1*(posT*posT*posT)) +
(BEZIER_Y_2 * 3 * (posT * posT) * (1-posT)) +
(BEZIER_Y_3 * 3 * posT * ((1-posT) * (1-posT))) +
(BEZIER_Y_4 * ((1-posT) * (1-posT) * (1-posT))));
}

}

Notice that when calculating for the right-hand side of the x axis, the values are x1, x2, x3, then x4; however, from the left-hand side, the points are used in the opposite order, x4, x3, x2, then x1.

Now, on each execution of the game loop, set the SBGEnemy.posX to SBGEnemy.getNextPosX() and set SBGEnemy.posY to SBGEnemy.getNextPosY(), and then translate the model matrix to the posX and posY points, as you have been doing.

..................Content has been hidden....................

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