Chapter     14

Firing Weapons

Many games require the player to fire or throw weapons at obstacles or enemies. If you have ever tried to fire weapons, you have likely encountered problems in getting your projectiles to leave your character in a predictable manner, and travel along a set path to a target.

Weapons can come in many shapes, sizes, and functions. Bullets, in many gaming situations, travel in straight lines, as do missiles, lasers, and most other propelled weapons. Thrown weapons, such as rocks, grenades, and even arrows to an extent, follow trajectories that are more parabolic. Regardless of the image or animation that you choose to use for the weapon, the math for getting it from point A to point B will be mostly the same.

This chapter will present multiple solutions for both wiring up “buttons” that will trigger weapons, and for animating the weapons on the screen. Much like Chapter 13, this chapter is not as heavy in OpenGL ES as some of the past chapters. There is more periphery coding involved when you need a character that uses weapons.

The first recipe that we will look at will offer a way to wire up a “fire button” on the screen. This button will be based on previous solutions and will give you a way to control the firing of weapons in your game.

There are multiple game scenarios where you might not need a fire button. Rather, the weapons may either fire automatically or they may even fire constantly. Weapons that fire constantly are quite popular in games such as top/down shooters. If you are planning to use a game type where the weapons fire automatically, feel free to skip the first recipe in this chapter.

14.1 Wire a “Fire” Button

Problem

The player has no way to fire weapons. A button—or interactive area of the screen—is needed to let the player fire the character’s weapons.

Solution

Createan interactive space on the screen where the player can tap to trigger the firing of the weapons. This will be demonstrated in two different solutions.

How It Works

I am going to tackle this solution two ways. The first is based on a previous solution in Recipe 5.3 where the screen area was divided into touch zones. We will now dedicate one of these zones to firing. If the player touches this area of the screen, the flag for firing the weapons will be set.

This method is good for some situations; however, if the game type calls for multiple touch areas on the screen, it could lead to weapons unintentionally being fired. Therefore, a second solution will also be explored where the player can double tap anywhere on the screen to fire the weapons.

Solution 1

For the first solution, override the onTouchEvent() of the game’s activity. Keep in mind that this might not necessarily be the main activity, especially if your game begins with a menu.Set the PLAYER_FIRE_WEAPONS flag when a touch is detected by this event, as shown in Listing 14-1.

Listing 14-1.  onTouchEvent()

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
DisplayMetrics outMetrics = new DisplayMetrics();

display.getMetrics(outMetrics);

int height = outMetrics.heightPixels / 4;

int playableArea = outMetrics.heightPixels - height;
if (y >playableArea){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(x <outMetrics.widthPixels / 2){
playeraction = PLAYER_FIRE_WEAPONS;
}
break;
}
}

return false;
}

In many of the solutions contained in this book, you have been working with the playeraction int. This int was established earlier in the book to act as a holder for the current action. The game loop contains a case statement that will read this int and execute the weapon firing code when playeraction = PLAYER_FIRE_WEAPONS.

Note   The display variable used in this solution is set in the main activity of the game. This is the activity that is started when the player launches the game. Thus, the display variable is set as follows:

display = ((WindowManager)
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

If this solution is not exactly what you need, you can easily set up a different solution where the player can double tap anywhere on the screen to trigger the firing of the weapons. We will look at this solution next.

Solution 2

To detect a double tap, you need to implement the GestureDetector. The code in Listing 14-2 will allow the player to double tap on the screen and fire the weapons.

Listing 14-2.  Activity with GestureDetector

public class SBGGameMain extends Activity {
private GestureDetector gd;

@Override
public void onCreate(Bundle savedInstanceState) {

...

gd = new GestureDetector(this,gestureListener);
}
@Override
protected void onResume() {
super.onResume();
gameView.onResume();
}

@Override
protected void onPause() {
super.onPause();
gameView.onPause();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
DisplayMetrics outMetrics = new DisplayMetrics();

display.getMetrics(outMetrics);

int height = outMetrics.heightPixels/4;

int playableArea = outMetrics.heightPixels - height;
if (y >playableArea){
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(x <outMetrics.widthPixels/2){
playeraction = PLAYER_MOVE_LEFT;
}else{
playeraction = PLAYER_MOVE_RIGHT;
}
break;
case MotionEvent.ACTION_UP:
playeraction = PLAYER_STAND;
break;
}
}
else {
return gd.onTouchEvent(event);
}

return false;
}

GestureDetector.SimpleOnGestureListener gestureListener = new
GestureDetector.SimpleOnGestureListener(){

@Override
public boolean onDown(MotionEvent arg0) {
//TODO Auto-generated method stub
return false;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {

return false;
}
@Override
public void onLongPress(MotionEvent e) {
//TODO Auto-generated method stub

}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
//TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent e) {
//TODO Auto-generated method stub

}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//TODO Auto-generated method stub
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {

playeraction = PLAYER_FIRE_WEAPONS;

return false;

};

};
}

The key to Solution 2 is to create a GestureDetector in your activity. Then establish a new SimpleOnGestureListener() and pass the event from the onTouchEvent() to it. The SimpleOnGestureListener() will then determine if the event is the result of a double tap and set the playeraction to PLAYER_FIRE_WEAPONS.

14.2 Animate a Missile

Problem

When the player fires the weapons, a projectile should leave from the character and travel in a straight line until it hits a target or moves off the screen.

Solution

Create a new missile class and use OpenGL ES to move it from the character to the target.

How It Works

The first step is to create a new class for your weapon. This class, like many of those created in other solutions in this book, will draw the square for your image’s texture, and then map your texture into the square. The new class for drawing the weapon should look like that shown in Listings 14-3 (OpenGL ES 1) and 14-4 (OpenGL ES 2/3).

Listing 14-3.  SBGWeapon()(OpenGL ES 1)

public class SBGWeapon {

public float posY = 0f;
public float posX = 0f;
public boolean shotFired = false;

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

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[1]);

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

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

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

try {

bitmap = BitmapFactory.decodeStream(imagestream);

}catch(Exception e){

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

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

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);

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

bitmap.recycle();
}

}

Listing 14-4.  SBGWeapon() (OpenGL ES 2/3)

public class SBGWeapon {

public float posY = 0f;
public float posX = 0f;
public boolean shotFired = false;

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;" +
"varying vec2 TexCoordOut;" +
"void main() {" +
"  gl_FragColor = texture2D(TexCoordIn, TexCoordOut);" +
"}";

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

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

positionHandle = GLES20.glGetAttribLocation(program, "vPosition");

GLES20.glEnableVertexAttribArray(positionHandle);

int vsTextureCoord = GLES20.glGetAttribLocation(program, "TexCoordIn");

GLES20.glVertexAttribPointer(positionHandle, 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]);
intfsTexture = GLES20.glGetUniformLocation(program, "TexCoordOut");
GLES20.glUniform1i(fsTexture, 0);

matrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");

GLES20.glUniformMatrix4fv(matrixHandle, 1, false, matrix, 0);

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

GLES20.glDisableVertexAttribArray(positionHandle);
}
}

The SBGWeapon() class contains three key features beyond those required by OpenGL ES. Two variables(x and y) are used to track the on-axis coordinates of the weapon through the game loop. The shotFired variable is used to determine whether the specific instantiation of SBGWeapon has been fired and should be drawn on the screen or ignored.

Why have a Boolean to represent whether the shot has been fired? It is not uncommon for a player in a game to fire multiple shots in quick succession. This means that at any one time, your game could have to track many shots in a single iteration of the game loop. By using the shotFired boolean you can determine which of the SBGWeapons in memory have been fired and which are waiting to be drawn.

The plan from here is to instantiate an SBGWeapon() in your Renderer class. Then, when you detect the PLAYER_FIRE_WEAPON, draw the SBGWeapon() and move it in a straight line on each iteration of the game loop, until the SBGWeapon() hits a target or reaches the end of the screen.

In the Renderer class, instantiate an array of SBGWeapons.In Listings 14-5 and 14-6, I will use an array of four, meaning that only four missiles can be on the screen together at one time.

private SBGWeapon[] playerFire = new SBGWeapon[4];

Don’t forget to load the texture for the image of whatever weapon you are firing. The texture is loaded in the onSurfaceCreated() method of the Renderer (see Listings 14-5 and 14-6).

Listing 14-5.  Load the Texture (OpenGL ES 1)

for(int x = 0; x<4; x++){
playerFire[x].loadTexture(gl,R.drawable.weapon, context);
}

Listing 14-6.  Load the Texture (OpenGL ES 2/3)

for(int x = 0; x<4; x++){
playerFire[x].loadTexture(R.drawable.weapon, context);
}

Finally, create a new method that can be called from the game loop. In many of the solutions in this book, I’ve referenced a case statement that acts on playerAction. Add a new case to this statement that tests for playerAction = PLAYER_FIRE_WEAPON. If PLAYER_FIRE_WEAPON is detected, call your new method to draw the weapons to the screen (see Listings 14-7 and 14-8).

Listing 14-7.  firePlayerWeapon() (OpenGL ES 1)

private void firePlayerWeapon(GL10gl){
for(int x = 0; x < 4; x++  ){
if (playerFire[x].shotFired){
int nextShot = 0;
if (playerFire[x].posY> 4.25){ //represents the top of the screen
playerFire[x].shotFired = false;
}else{
if (playerFire[x].posY> 2){
if (x == 3){//since we only have 4 should, recycle any that are no longer in use
nextShot = 0;
}else{
nextShot = x + 1;
}
if (playerFire[nextShot].shotFired == false){
playerFire[nextShot].shotFired = true;
//set the weapon x to the x of the character when it was fired
playerFire[nextShot].posX = player.x;
playerFire[nextShot].posY = 1.25f;
}

}
playerFire[x].posY += .12f; //the speed of the shot as it moves
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playerFire[x].posX, playerFire[x].posY, 0f);

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

playerFire[x].draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();

}
}
}
}

Listing 14-8.  firePlayerWeapon() (OpenGL ES 2/3)

private void firePlayerWeapon(GL10 unused, float[] rotationMatrix, float[] matrix){
for(int x = 0; x < 4; x++  ){
if (playerFire[x].shotFired){
int nextShot = 0;
if (playerFire[x].posY> 4.25){ //represents the top of the screen
playerFire[x].shotFired = false;
}else{
if (playerFire[x].posY> 2){
if (x == 3){//since we only have 4 should, recycle any that are no longer in use
nextShot = 0;
}else{
nextShot = x + 1;
}
if (playerFire[nextShot].shotFired == false){
playerFire[nextShot].shotFired = true;
//set the weapon x to the x of the character when it was fired
playerFire[nextShot].posX = player.x;
playerFire[nextShot].posY = 1.25f;
}

}
playerFire[x].posY += .12f; //the speed of the shot as it moves
Matrix.translateM(RotationMatrix, 0, playerFire[x].posX, playerFire[x].posY, 0);
playerFire[x].draw(matrix);
Matrix.multiplyMM(matrix, 0, rotationMatrix, 0, matrix, 0);

}
}
}
}

This method will fire a shot from the position of the character, straight up until it hits the top edge of the screen. Modify the assignment of the x and y values of SBGWeapon() to move the shot in different directions. By increasing or decreasing the x value, your shot will move to the right or to the left; by increasing or decreasing the y value, your shot will move up or down.

In Chapter 15, you will be presented with solutions for implementing collision detection. Collision detection is the key to acting when your shot hits a target, rather than simply having your shot move off the edge of the screen.

In the next solution, you will modify the firePlayerWeapon() method to move the shot in a parabolic motion, as if thrown rather than shot straight.

14.3 Animate a Thrown Weapon

Problem

The weapons do not travel in an arc like a thrown weapon would.

Solution

Use a formula, like that used when jumping, to determine a curved trajectory.

How It Works

To move your shot in a arching motion, as if it were thrown, you need to modify the firePlayerWeapon() method. We are going to use the same math formula from Chapter 13 that enabled the character to jump, and place it in the firePlayerWeapons() formula. This is shown in Listings 14-9 and 14-10.

Listing 14-9.  Arching Trajectory (OpenGL ES 1)

private void firePlayerWeapon(GL10gl){
for(int x = 0; x < 4; x++  ){
if (playerFire[x].shotFired){
int nextShot = 0;

previousArcPos = arcJump;

arcJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);
if (arcJump<= Math.PI)
{
playerFire[x].posY += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;

}else{
playerFire[x].posY -=(Math.sin((double)posArc) - Math.sin((double)previousArcPos))* 1.5;
if (playerFire[x].posY<= .75f){
playerFire[x].shotFired = false;
playerFire[x].posY = .75f;
}else{

if (x == 3){//since we only have 4 should, recycle any that are no longer in use
nextShot = 0;
}else{
nextShot = x + 1;
}
}

if (playerFire[nextShot].shotFired == false){
playerFire[nextShot].shotFired = true;
playerFire[nextShot].posX = player.x;
playerFire[nextShot].posY = player.y;
}

}

playerFire[x].posx += .12f;

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glTranslatef(playerFire[x].posX, playerFire[x].posY, 0f);

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

playerFire[x].draw(gl);
gl.glPopMatrix();
gl.glLoadIdentity();

}
}
}
}

Listing 14-10.  Arching Trajectory (OpenGL ES 2/3)

private void firePlayerWeapon(GL10 unused, float[] rotationMatrix, float[] matrix){
for(int x = 0; x < 4; x++  ){
if (playerFire[x].shotFired){
int nextShot = 0;
previousArcPos = arcJump;

arcJump += (float)(((Math.PI / 2) / .5) * PLAYER_RUN_SPEED);
if (arcJump<= Math.PI)
{
playerFire[x].posY += 1.5 / .5 * .15 * PLAYER_RUN_SPEED;

}else{
playerFire[x].posY -=(Math.sin((double)posJump) - Math.sin((double)previousJumpPos))* 1.5;
if (playerFire[x].posY<= .75f){
playerFire[x].shotFired = false;
playerFire[x].posY = .75f;
}else{

if (x == 3){//since we only have 4 should, recycle any that are no longer in use
nextShot = 0;
}else{
nextShot = x + 1;
}
}

if (playerFire[nextShot].shotFired == false){
playerFire[nextShot].shotFired = true;
playerFire[nextShot].posX = player.x;
playerFire[nextShot].posY = player.y;
}

}

playerFire[x].posx += .12f;
Matrix.translateM(RotationMatrix, 0, playerFire[x].posX, playerFire[x].posY, 0);
playerFire[x].draw(matrix);
Matrix.multiplyMM(matrix, 0, rotationMatrix, 0, matrix, 0);

}
}
}
}

By making this small modification, you can give your weapon a thrown arc rather than the straight line of a projectile that has been fired.

Summary

In Chapter 13 you reviewed recipes that allowed you to add enemies to your game. However, adding enemies to the game is unfair if the player does not have a way to defend themselves. The recipes in this chapter helped you provide the player with a way to fire weapons.

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

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