Chapter 8

Defend Yourself!

Your gaming development skills are coming along at a great pace now. While Star Fighter is not going to win any gaming awards, it is a perfect sandbox for you to hone your newly developed game-making skills.

Now, you find yourself at the last chapter of this book that will deal directly with the development of a 2-D sprite-based game. In this chapter, you are going to add a weapon for your playable character to use and create some basic 2-D collision detection. You will load a sprite sheet that contains some weapon images, write some AI logic for the bullets to follow a path, and create some collision detection to ensure you know when your weapons hit their targets.

If this were a going to be a full game that you were going to release to the public, you would want to add some score tracking, multiple levels, and possibly items such as power-ups and upgradeable weapons. However, the real purpose of this small 2-D project is to give you a proper base of knowledge and enough experience using the skills so that the chapters on 3-D game development (Chapters 1012) will make sense to you. At the end of the chapter, you'll have the opportunity to review some of the key files that you've worked on thus far. This will ensure you have everything in place before moving on to the next stage of 3-D development.

Creating a Weapon Sprite Sheet

Your player would not last very long in the game without a way to defend against the onslaught of enemies that you set upon them in the last chapter. Therefore, you are going to arm your player with the standard space-fighting weapon—a blaster.

You first need to create a sprite sheet for your weapon, in the same way you created one for the enemies in Chapter 6 and for the playable character in Chapter 5.

NOTE: Theoretically, the weapons could be included on the same sprite sheet as the player and the enemy ships. However, it is good practice for you to see how to juggle two textures in OpenGL.

Add the sprite sheet (it can be downloaded from this book's page on Apress.com, along with the code to this project) to your Star Fighter project. The sprite sheet in Figure 8–1 includes multiple weapons and character explosions.

images

Figure 8–1. Weapon sprite sheet

Once you have added the sprite sheet to your project, open the SFEngine.java file and add the following constants to it:

SFEngine        public static final int WEAPONS_SHEET = R.drawable.destruction;
        public static final int PLAYER_SHIELDS = 5;
        public static final int SCOUT_SHIELDS = 3;
        public static final int INTERCEPTOR_SHIELDS = 1;
        public static final int WARSHIP_SHIELDS = 5;
        public static final float PLAYER_BULLET_SPEED = .125f;

SFMusic

The WEAPONS_SHEET constant is going to hold the pointer to your new sprite sheet. The SHOUT_SHIELDS, INTERCEPTOR_SHIELDS, and WARSHIP_SHIELDS constants will indicate how many hits the respective enemies can take before being destroyed, and the PLAYER_BULLET_SPEED constant will hold the speed at which the blaster fire will leave the playable character and travel up the screen.

Creating a Weapon Class

When you created the playable character and the enemies, you created a class to base them from. You are going to follow the same process for the weapon. Create a new class called SFWeapon for your weapons.

package com.proandroidgames;

public class SFWeapon {

}

You need to know three things about your weapons to draw them to the screen: the x and y positions of the sprite's vertex and whether the sprite is currently on the screen. The x and y positions are going to help you place the sprite on the correct point on the screen, and they will also help you in determining collisions.

Just as you might see multiple enemies, more than one blaster shot will be on the screen at a time. Therefore, you are going to have the weapons in an array. You will need to know, when you loop through your array, if the shot that you are looking at is currently on the screen or if it is free to be fired.

Add the following public properties to your class:

package com.proandroidgames;

public class SFWeapon {

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



}

Create the vertices, indices, and texture arrays the same way you did in the enemy and playable character classes. These arrays, along with the constructor, will be used to set the data needed for OpenGL to draw your weapons to the screen.

public class SFWeapon {

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


}

The last step to creating a weapon class is to make an onDraw() method. Having followed along with the previous chapters, you should be very familiar with the onDraw() method. Just be aware that the weapon sprite sheet is going to be the second OpenGL pointer in the spriteSheet array that you created for the game loop in the previous chapter. Therefore, alter the onDraw() method appropriately so that you are pulling from the correct sprite sheet when you call the method.

public class SFWeapon {




        public void draw(GL10 gl, 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);
        }

}

With the class for your weapons created, you can now move over to the game loop and add the weapons. In the next section, you will create the automatic firing process for your playable character and let it fire the weapons from the weapon class that you created.

Giving Your Weapon a Trajectory

Now that you have your weapon class created, you're ready to instantiate it and create a method to allow the playable character to fire it. Recall that, in the story for Star Fighter, the playable character's weapon is autofired. Therefore, the method that you create to fire the weapon must do so without interaction from the player.

Creating a Weapon Array

Like you did for the enemy ships, you are going to create an array to hold all of the possible shots that your player could fire. Open SFGameRenderer, and create a new array of SFWeapon() in your game loop class.

package com.proandroidgames;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class SFGameRenderer implements Renderer{
        private SFBackGround background = new SFBackGround();
        private SFBackGround background2 = new SFBackGround();
        private SFGoodGuy player1 = new SFGoodGuy();
        private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS +
SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1];
        private SFTextures textureLoader;
        private int[] spriteSheets = new int[2];
        private SFWeapon[] playerFire = new SFWeapon[4];

...

}

In the preceding chapter, you created a common texture class. Because the class is currently instantiated in your game loop, it will hold two textures. It is now time to add a second sprite sheet to this array.

Adding a Second Sprite Sheet

The second sprite sheet is the one that holds the weapons.

public class SFGameRenderer implements Renderer{

...

@Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                initializeInterceptors();
                initializeScouts();
                initializeWarships();
                initializePlayerWeapons();
                textureLoader = new SFTextures(gl);
                spriteSheets = textureLoader.loadTexture(gl, SFEngine.CHARACTER_SHEET,
SFEngine.context, 1);
spriteSheets = textureLoader.loadTexture(gl, SFEngine.WEAPONS_SHEET, SFEngine.context,
2);


                gl.glEnable(GL10.GL_TEXTURE_2D);
                gl.glClearDepthf(1.0f);
                gl.glEnable(GL10.GL_DEPTH_TEST);
                gl.glDepthFunc(GL10.GL_LEQUAL);

                background.loadTexture(gl,SFEngine.BACKGROUND_LAYER_ONE,
SFEngine.context);
                background2.loadTexture(gl,SFEngine.BACKGROUND_LAYER_TWO,
SFEngine.context);

}

CAUTION: Be careful to label the new sheet as number 2, or you will just replace the previous sheet.

In the preceding chapter, you wrote an initialization method to instantiate new copies of the enemy class and added it to the enemies' array. You are going to follow the same process when creating the weapons for you playable character.

Initializing the Weapons

Start by creating an initialization class named initializePlayerWeapons() as follows:

public class SFGameRenderer implements Renderer{


...

        private void initializePlayerWeapons(){

        }


...

}

In the initializePlayerWeapons() method, you need to loop through the playerFire[] array that you created and add a new instantiation of the SFWeapon() class to it.

public class SFGameRenderer implements Renderer{


...

        private void initializePlayerWeapons(){
                for(int x = 0; x < 4; x++){
                        SFWeapon weapon = new SFWeapon();
                        playerFire[x] = weapon;
                }


        }


...

}

Finish off the initialization method by setting the initial properties of the first shot to be fired. Since the weapon is autofired, you can set the first shot as having been fired. The x axis position of the shot is going to be equal to the current x axis position of the player character.

As for the y axis position, set that to 1.25. This will set the y axis of the shot to be slightly above the player's ship, giving it the appearance of coming out of a forward-facing blaster cannon. If you set the y axis lower, the shot will be drawn over the ship and look like it is coming from somewhere on top of the ship.

public class SFGameRenderer implements Renderer{


...

        private void initializePlayerWeapons(){
                for(int x = 0; x < 4; x++){
                        SFWeapon weapon = new SFWeapon();
                        playerFire[x] = weapon;
                }
                playerFire[0].shotFired = true;
                playerFire[0].posX = SFEngine.playerBankPosX;
                playerFire[0].posY = 1.25f;
        }


...

}

The array for the weapon shots is created, instantiated, and populated. In the previous chapters, you created private methods that could be called from the game loop to move players and enemies. Now, you need to create a private method to move the weapon shots up the screen.

Moving the Weapon Shots

The trajectory of each shot will be a straight line, and it will move from the player's x position at the time of the shot to the top of the screen. Create a method named firePlayerWeapon() to be used to move each shot in a straight line as it is fired.

public class SFGameRenderer implements Renderer{

...

        private void initializePlayerWeapons(){
                for(int x = 0; x < 4; x++){
                        SFWeapon weapon = new SFWeapon();
                        playerFire[x] = weapon;
                }
                playerFire[0].shotFired = true;
                playerFire[0].posX = SFEngine.playerBankPosX;
                playerFire[0].posY = 1.25f;
        }

...

        private void firePlayerWeapon(GL10 gl){

        }



...

}

In the firePlayerWeapon() method, create a loop that will run only if the shot has been fired. This will save you from looping on shots that do not need to be drawn.

public class SFGameRenderer implements Renderer{

...


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

                        }
                }

        }


...

}

The first thing that you are doing in this method is creating an int named nextShot. The autofire feature of the playable character fires each shot in succession. Therefore, one shot should not be fired until the shot before has traveled an acceptable distance away from the character. The nextShot int tracks the next shot to be fired so you can set some initial properties on it when the time is right.

public class SFGameRenderer implements Renderer{

...

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


                        }
                }
        }


...

}

Detecting the Edge of the Screen

You need a way to determine if a shot has hit the edge of the viewable screen, so the cannon blast isn't drawn when the player can't see it, thus wasting valuable resources. Set up an if statement to test if the current shot has gone off the screen. If the shot has extended off the screen, set its shotFired property to false to prevent it from being drawn unnecessarily.

public class SFGameRenderer implements Renderer{

...

        private void firePlayerWeapon(GL10 gl){
                for(int x = 0; x < 4; x++ ){
                        if (playerFire[x].shotFired){
                                int nextShot = 0;
if (playerFire[x].posY > 4.25){
                                        playerFire[x].shotFired = false;
                                }else{

                                }

                        }
                }
        }


...

}

Assuming the shot has not yet extended off the screen, it must still be in the player's view and must be dealt with. Because the shots fly in a straight trajectory, all you have to do to move the shot is continue to add PLAYER_BULLET_SPEED to the current y position of the shot. Then, you can call all of the OpenGL operations that you have been dealing with when drawing characters to the screen.

TIP: If any of the OpenGL operations in the following code do not look familiar, review Chapters 4 and 5.

public class SFGameRenderer implements Renderer{


...

        private void firePlayerWeapon(GL10 gl){
                for(int x = 0; x < 4; x++ ){
                        if (playerFire[x].shotFired){
                                int nextShot = 0;
                                if (playerFire[x].posY > 4.25){
                                        playerFire[x].shotFired = false;
                                }else{

playerFire[x].posY += SFEngine.PLAYER_BULLET_SPEED;
gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();
                                        gl.glScalef(.25f, .25f, 0f);
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,spriteSheets);

                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();

                                }
                        }
                }
        }


...

}

You need to take care of one last thing in this method. Once the current shot has moved along the y axis more than 1 unit away from the character, it is time to fire the next shot. Therefore, you need to test if the current shot is more than 1 y-axis unit away from the character and, if so, set the properties of the next shot to be fired.

Keep in mind that shots fire successively, so by the time the last shot has fired, the first shot should be off the screen and disabled. The first shot can then be fired again when the last shot has passed the firing threshold.

public class SFGameRenderer implements Renderer{


...

        private void firePlayerWeapon(GL10 gl){
                for(int x = 0; x < 4; x++ ){
                        if (playerFire[x].shotFired){
                                int nextShot = 0;
                                if (playerFire[x].posY > 4.25){
                                        playerFire[x].shotFired = false;
                                }else{
                                        if (playerFire[x].posY> 2){
                                                if (x == 3){
                                                        nextShot = 0;
                                                }else{
                                                        nextShot = x + 1;
                                                }
                                                if (playerFire[nextShot].shotFired ==
false){
                                                        playerFire[nextShot].shotFired =
true;
                                                        playerFire[nextShot].posX =
SFEngine.playerBankPosX;
                                                        playerFire[nextShot].posY =
1.25f;
                                                }
                                        }
                                        playerFire[x].posY +=
SFEngine.PLAYER_BULLET_SPEED;
gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();

                                        gl.glScalef(.25f, .25f, 0f);
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,spriteSheets);
                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();


                                }
                        }
                }
        }


...

}

Calling the firePlayerWeapons() Method

When you were working on the playable character and the enemies, you called methods to move them, in increments, from the main game loop. The problem with following this process for the player's weapon is that you do not know if the player's character is currently valid and ready for firing. To get around this, you are going to call the firePlayerWeapons() method from the movePlayer1() method, rather than calling it from the main game loop. Doing so will ensure that you move the weapons on the screen only when they are eligible to be moved.

public class SFGameRenderer implements Renderer{

...

        private void movePlayer1(GL10 gl){
                if(!player1.isDestroyed){
                        switch (SFEngine.playerFlightAction){

...

                        }
                        firePlayerWeapon(gl);
                }
        }

...

}

Your player can now fire weapons at the enemies. However, the weapons do not accomplish anything. If you were to compile and play the game right now, you would see the shots simply fly through any enemies and continue until they reach the edge of the screen. So too, the enemies would just continue in their descent, oblivious to any shots fired.

To make your shots effective, you need to create some collision detection. In the next section, you will create a method for simply 2-D collision detection that will be used to determine if an enemy should be destroyed.

Implementing Collision Detection

Collision detection determines if two objects on the screen have touched and is essential to any video game. In Star Fighter, you will use basic collision detection to destroy enemies. In other games, collision detection is used to keep a player from walking through walls, allow a player to pick up a new item, or even determine if an enemy can see the player from an obscured view.

In this section, you are going to create a method that will track the position each enemy on the screen, and each shot fired, to determine if any of the shots have hit any of the enemies. In a 2-D game like Star Fighter, this is process is made easier because you have to test on only two axes (you do not have to deal with the z axis in a 2-D game).

Applying Collision Damage

When a collision has been detected, you must apply the damage to the enemy that has been hit. Each enemy can take a certain amount of damage before it is destroyed. To track this damage, create a new method named applyDamage() in your SFEnemy() class. This method will simply increment an int each time the specific enemy is hit. When the int value reaches the predefined limit for that enemy, the isDestroyed flag will be flipped, and the enemy will no longer be drawn to the screen.

package com.proandroidgames;

...

import javax.microedition.khronos.opengles.GL10;

public class SFEnemy {

        public float posY = 0f;
        public float posX = 0f;
        public float posT = 0f;
        public float incrementXToTarget = 0f;
        public float incrementYToTarget = 0f;
        public int attackDirection = 0;
        public boolean isDestroyed = false;
        private int damage = 0;

...

        public void applyDamage(){
                damage++;
                switch(enemyType){

                        case SFEngine.TYPE_INTERCEPTOR:
                                if (damage == SFEngine.INTERCEPTOR_SHIELDS){
                                        isDestroyed = true;
                                }
                                break;
                        case SFEngine.TYPE_SCOUT:
                                if (damage == SFEngine.SCOUT_SHIELDS){
                                        isDestroyed = true;
                                }
                                break;
                        case SFEngine.TYPE_WARSHIP:
                                if (damage == SFEngine.WARSHIP_SHIELDS){
                                        isDestroyed = true;
                                }
                                break;
                }
        }



...

}

Every time that your collision detection method determines that a collision has been made with an enemy ship, all you have to do is call the appyDamage() method of the enemy, and it will take care of the rest. Once the isDestroyed flag on the enemy is set to true, that enemy will no longer be processed in the moveEnemy() method or drawn to the screen. Save and close SFEnemy.java.

Creating the detectCollisions() Method

With collision damage calculations taken care of, continue to edit your renderer by creating a method named detectCollisions() in your SFGameRenderer.java file.

public class SFGameRenderer implements Renderer{
        private void initializePlayerWeapons(){
                for(int x = 0; x < 4; x++){
                        sfweapon weapon = new sfweapon();
                        playerFire[x] = weapon;
                }
                playerFire[0].shotFired = true;
                playerFire[0].posX = sfengine.playerBankPosX;
                playerFire[0].posY = 1.25f;
        }

...

        private void detectCollisions(){

        }



...

}

Within the detectCollisions() method, set up two loops, one to iterate through each fired shot and one to iterate through each enemy that has not already been destroyed. Remember, because the enemies start at random positions beyond the upper edge of the screen, they can be valid (isDestroyed == false) without being in the player's view. This means you also need to test if the enemy is in view of the player as well as whether or not it has been destroyed.

public class SFGameRenderer implements Renderer{

...

        private void detectCollisions(){
                for (int y = 0; y < 3; y ++){
if (playerFire[y].shotFired){
                                for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS +
SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++ ){
                                        if(!enemies[x].isDestroyed && enemies[x].posY <
4.25 ){

                                        }
                                }
                        }
                }

        }


...


}

Now comes the tricky part of the method. You know two pieces of information about each enemy and each shot fired: the x and y positions. You also know dimension of the vertices for the enemies and the shots; in this case, they are each 1 × 1 unit.

Detecting the Specific Collisions

Create an if statement to determine if a shot and an enemy collided based on their x and y positions and their respective dimensions.

public class SFGameRenderer implements Renderer{


...

        private void detectCollisions(){
                for (int y = 0; y < 3; y ++){
if (playerFire[y].shotFired){
                                for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS +
SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++ ){

                                        if(!enemies[x].isDestroyed && enemies[x].posY <
4.25 ){
                                                if ((playerFire[y].posY >=
enemies[x].posY - 1
&& playerFire[y].posY <= enemies[x].posY )
&& (playerFire[y].posX <= enemies[x].posX + 1
&& playerFire[y].posX >= enemies[x].posX - 1)){

                                                }
                                        }
                                }
                        }
                }
        }



...


...

}

If both the enemy and the shot being tested make it passed this if statement, they have collided. When and enemy and a shot collide, you need to call the applyDamage() method on the enemy to either add to the damage of that enemy or destroy it completely.

Removing Void Shots

Once a shot has hit an enemy, regardless of whether the enemy is completely destroyed, the shot needs to be taken off the screen so that it cannot hit any other enemies. Set the shotFired flag on the shot to false to negate the shot.

NOTE: Whether a shot hits an enemy or travels until it reaches the top on the screen, it will have the same result; the next shot in the array can be activated. Therefore, in your collision method, if a shot has hit an enemy, activate the next shot after you have deactivated the one that collided.

public class SFGameRenderer implements Renderer{


...

        private void detectCollisions(){
                for (int y = 0; y < 3; y ++){
if (playerFire[y].shotFired){
                                for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS +
SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++ ){
                                        if(!enemies[x].isDestroyed && enemies[x].posY <
4.25 ){

                                                if ((playerFire[y].posY >=
enemies[x].posY - 1
&& playerFire[y].posY <= enemies[x].posY )
&& (playerFire[y].posX <= enemies[x].posX + 1
&& playerFire[y].posX >= enemies[x].posX - 1)){
                                                        int nextShot = 0;
enemies[x].applyDamage();
                                                        playerFire[y].shotFired = false;
                                                        if (y == 3){
                                                                nextShot = 0;
                                                        }else{
                                                                nextShot = y + 1;
                                                        }
                                                        if
(playerFire[nextShot].shotFired == false){

playerFire[nextShot].shotFired = true;

playerFire[nextShot].posX = SFEngine.playerBankPosX;

playerFire[nextShot].posY = 1.25f;
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }


...


...

}

That is a fairly simple version of 2-D collision detection that should get you well on your path to creating an entertaining game. Now, all you have to do is call the collision detection method from the main game loop.

...

public void onDrawFrame(GL10 gl) {
        loopStart = System.currentTimeMillis();
        try {
                if (loopRunTime < SFEngine.GAME_THREAD_FPS_SLEEP){
                        Thread.sleep(SFEngine.GAME_THREAD_FPS_SLEEP - loopRunTime);
                }
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        scrollBackground1(gl);
        scrollBackground2(gl);

        movePlayer1(gl);
        moveEnemy(gl);

        detectCollisions();


        gl.glEnable(GL10.GL_BLEND);
        gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
        loopEnd = System.currentTimeMillis();
        loopRunTime = ((loopEnd - loopStart));

}

...

Save and compile your game. You can now move your playable character and take out enemies as they try to attack you. This is the final chapter that deals directly with 2-D graphics and the Star Fighter game. In the next section, you will find some suggestions for expanding on what you learned in the chapters that led you here before progressing into the realm of 3-D gaming.

Expanding on What You Learned

If you want to really expand on your Star Fighter game, you can add several key elements to your code that will make a major difference to the game play.

  • Expand the weapons so that they are also fired from the scouts and warships.
  • Expand the collision detection to include shot impacts on the player and collisions between the payer and enemies.
  • Add a three- or four-sprite animation sequence of an explosion that can be triggered when a ship is destroyed.
  • Give the player more than one life to work with.

All of these items can easily be accomplished with the skills that you have obtained to this point in this book.

Summary

In this chapter, you learned how to create weapons that can be autofired by the player. You also added some basic 2-D collision detection to destroy enemies as they are hit by the player.

In the next chapter, you will learn how to publish your game on the Android Marketplace before moving on to 3-D game programming.

But before we move on, please review the full source code for the keys files of Star Fighter I have provided in the next section. I selected the files that have either had the most changes to them, or have the most code. The code listings for these files are provided so that you can compare your code against the code from the project.

This is designed to help you along should you have problems compiling or running your project. Given that games can be complicate to create from scratch, and because some code can be overlooked when you are following through the chapters, you may discover that you cannot correctly run or compile the game the way it appears in the book. Being the last chapter in which you will learn 2D gaming, this is a good spot to stop and review your code.

Reviewing the Key 2-D Code

The first file that you should check— if you are having problems running your Star Fighter game—is the SFEngine.java. This file contains settings that are used throughout the game, and in almost every class in the project. You first created this file back in Chapter 3, and continued to edit it thoughout Part 1. Therefore it is the most likely place that you may have missed something. The source for the SFEngine.java is shown in the Listing 8–1.

Listing 8–1. SFEngine.java

package com.proandroidgames;

import android.content.Context;
import android.content.Intent;
import android.view.Display;
import android.view.View;

public class SFEngine {
        /*Constants that will be used in the game*/
        public static final int GAME_THREAD_DELAY = 4000;
        public static final int MENU_BUTTON_ALPHA = 0;
        public static final boolean HAPTIC_BUTTON_FEEDBACK = true;
        public staticfinal int SPLASH_SCREEN_MUSIC = R.raw.warfieldedit;
        public static final int R_VOLUME = 100;
        public static final int L_VOLUME = 100;
        public static final boolean LOOP_BACKGROUND_MUSIC = true;
        public static final int GAME_THREAD_FPS_SLEEP = (1000/60);
        public static float SCROLL_BACKGROUND_1  = .002f;
        public static float SCROLL_BACKGROUND_2  = .007f;
        public static final int BACKGROUND_LAYER_ONE = R.drawable.backgroundstars;
        public static final int BACKGROUND_LAYER_TWO = R.drawable.debris;
        public static final int PLAYER_BANK_LEFT_1 = 1;
        public static final int PLAYER_RELEASE = 3;
        public static final int PLAYER_BANK_RIGHT_1 = 4;
        public static final int PLAYER_FRAMES_BETWEEN_ANI = 9;
        public static final float PLAYER_BANK_SPEED = .1f;
        public static int CHARACTER_SHEET = R.drawable.character_sprite;
        public static int TOTAL_INTERCEPTORS = 10;
        public static int TOTAL_SCOUTS = 15;
        public static int TOTAL_WARSHIPS = 5;
        public static float INTERCEPTOR_SPEED = SCROLL_BACKGROUND_1 * 4f;
        public static float SCOUT_SPEED = SCROLL_BACKGROUND_1 * 6f;
        public static float WARSHIP_SPEED = SCROLL_BACKGROUND_2 * 4f;
        public static final int TYPE_INTERCEPTOR = 1;
        public static final int TYPE_SCOUT = 2;
        public static final int TYPE_WARSHIP = 3;
        public static final int ATTACK_RANDOM = 0;
        public static final int ATTACK_RIGHT = 1;
        public static final int ATTACK_LEFT = 2;
        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;
        public static final int WEAPONS_SHEET = R.drawable.destruction;
        public static final int PLAYER_SHIELDS = 5;
        public static final int SCOUT_SHIELDS = 3;
        public static final int INTERCEPTOR_SHIELDS = 1;
        public static final int WARSHIP_SHIELDS = 5;
        public static final float PLAYER_BULLET_SPEED = .125f;
        /*Game Variables*/

        public static Context context;
        public static Thread musicThread;
        public static Display display;
        public static int playerFlightAction = 0;
        public static float playerBankPosX = 1.75f;
        /*Kill game and exit*/
        public boolean onExit(View v) {
                try
                {
                        Intent bgmusic = new Intent(context, SFMusic.class);
                        context.stopService(bgmusic);
                        musicThread.stop();

                        return true;
                }catch(Exception e){
                        return false;
                }
        }
}

The next file (Listing 8–2) is the class used to create your weapons. This file was created earlier and therefore may have been overlooked. Pay attention to the onDraw() method when you are checking this file against yours – if you coppied the contents of this file from a similar one, like SFBackground.java, you may have missed some changes.

Listing 8–2. SFWeapon.java

package com.proandroidgames;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class SFWeapon {

        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(GL10 gl, 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);
         }

}

The SFTextures file was also a relatively new file to the code, therefore it is possible for problems to show up here as well. This code was used to expand update an existing process for calling textures. If you were not paying attention it would have been very easy to overlook an important part of this. When you are checking the code in Listing 8–3, be sure to check that your arrays are instantiated correctly.

Listing 8–3. SFTextures.java

package com.proandroidgames;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;

public class SFTextures {

         private int[] textures = new int[2];

        public SFTextures(GL10 gl){

        gl.glGenTextures(2, textures, 0);

        }
         public int[] loadTexture(GL10 gl,int texture, Context context,int
textureNumber) {
        InputStream imagestream = context.getResources().openRawResource(texture);
        Bitmap bitmap = null;
        try {

                bitmap = BitmapFactory.decodeStream(imagestream);

        }catch(Exception e){

        }finally {
         //Always clear and close
         try {
                 imagestream.close();
                 imagestream = null;
         } catch (IOException e) {
         }
        }

        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[textureNumber - 1]);

        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_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);

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

        bitmap.recycle();

        return textures;
         }
}

If your files appear to match those in the previous listings, and you are still having problems, then it is time to dive into the game loop.The SFGameRenderer.java contains the main game loop for Star Fighter and is the most likely place for a problem to happen.Unfortunately, being the largest file in the game, it is also the hardest file to troubleshoot. Listing 8–4 shows the source code for the SFGameRenderer.java.Pay special attention to the onDrawFrame() method.

Listing 8–4. SFGameRenderer.java

package com.proandroidgames;

import java.util.Random;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class SFGameRenderer implements Renderer{
        private SFBackGround background = new SFBackGround();
        private SFBackGround background2 = new SFBackGround();
        private SFGoodGuy player1 = new SFGoodGuy();
        private SFEnemy[] enemies = new SFEnemy[SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1];
        private SFTextures textureLoader;
        private int[] spriteSheets = new int[2];
        private SFWeapon[] playerFire = new SFWeapon[4];

        private int goodGuyBankFrames = 0;
        private long loopStart = 0;
        private long loopEnd = 0;
        private long loopRunTime = 0 ;

        private float bgScroll1;
        private float bgScroll2;

        @Override
        public void onDrawFrame(GL10 gl) {
                loopStart = System.currentTimeMillis();
                // TODO Auto-generated method stub
                try {
                        if (loopRunTime < SFEngine.GAME_THREAD_FPS_SLEEP){
                                Thread.sleep(SFEngine.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);        

                scrollBackground1(gl);
                scrollBackground2(gl);

                movePlayer1(gl);
                moveEnemy(gl);

                detectCollisions();

                gl.glEnable(GL10.GL_BLEND);
        gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
        loopEnd = System.currentTimeMillis();
        loopRunTime = ((loopEnd - loopStart));

        }
        private void initializeInterceptors(){
                for (int x = 0; x<SFEngine.TOTAL_INTERCEPTORS -1 ; x++){
                        SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_INTERCEPTOR,
SFEngine.ATTACK_RANDOM);
                        enemies[x] = interceptor;
                }
        }
        private void initializeScouts(){
                for (int x = SFEngine.TOTAL_INTERCEPTORS -1;
x<SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS -1; x++){
                        SFEnemy interceptor;
                        if (x>=(SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS) / 2
){
                                interceptor = new SFEnemy(SFEngine.TYPE_SCOUT,
SFEngine.ATTACK_RIGHT);
                        }else{
                                interceptor = new SFEnemy(SFEngine.TYPE_SCOUT,
SFEngine.ATTACK_LEFT);
                        }
                        enemies[x] = interceptor;
                }
        }
        private void initializeWarships(){
                for (int x = SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS -1;
x<SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS -1;
x++){
                        SFEnemy interceptor = new SFEnemy(SFEngine.TYPE_WARSHIP,
SFEngine.ATTACK_RANDOM);
                        enemies[x] = interceptor;
                }
        }
        private void initializePlayerWeapons(){
                for(int x = 0; x < 4; x++){
                        SFWeapon weapon = new SFWeapon();
                        playerFire[x] = weapon;
                }
                playerFire[0].shotFired = true;
                playerFire[0].posX = SFEngine.playerBankPosX;
                playerFire[0].posY = 1.25f;
        }
        private void moveEnemy(GL10 gl){
                for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS + SFEngine.TOTAL_SCOUTS
+ SFEngine.TOTAL_WARSHIPS - 1; x++){
                        if (!enemies[x].isDestroyed){
                                Random randomPos = new Random();
                                switch (enemies[x].enemyType){
                                case SFEngine.TYPE_INTERCEPTOR:
                                        if (enemies[x].posY < 0){
                                                enemies[x].posY = (randomPos.nextFloat()
* 4) + 4;
                                                enemies[x].posX = randomPos.nextFloat()
* 3;
                                                enemies[x].isLockedOn = false;
                                                enemies[x].lockOnPosX = 0;
                                        }
                                        gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();
                                        gl.glScalef(.25f, .25f, 1f);

                                        if (enemies[x].posY >= 3){
                                                enemies[x].posY -=
SFEngine.INTERCEPTOR_SPEED;
                                        }else{
                                                if (!enemies[x].isLockedOn){
                                                        enemies[x].lockOnPosX =
SFEngine.playerBankPosX;
                                                        enemies[x].isLockedOn = true;
                                                        enemies[x].incrementXToTarget
=(float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY /
(SFEngine.INTERCEPTOR_SPEED* 4)));
                                                }
                                                enemies[x].posY -=
(SFEngine.INTERCEPTOR_SPEED* 4);
                                                enemies[x].posX +=
enemies[x].incrementXToTarget;

                                        }
                                                gl.glTranslatef(enemies[x].posX,
enemies[x].posY, 0f);
                                                gl.glMatrixMode(GL10.GL_TEXTURE);
                                                gl.glLoadIdentity();
                                                gl.glTranslatef(0.25f, .25f , 0.0f);
                                        enemies[x].draw(gl, spriteSheets);
                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();

                                        break;
                                case SFEngine.TYPE_SCOUT:
                                        if (enemies[x].posY <= 0){
                                                enemies[x].posY = (randomPos.nextFloat()
* 4) + 4;
                                                enemies[x].isLockedOn = false;
                                                enemies[x].posT = SFEngine.SCOUT_SPEED;
                                                enemies[x].lockOnPosX =
enemies[x].getNextScoutX();
                                                enemies[x].lockOnPosY =
enemies[x].getNextScoutY();
                                                if(enemies[x].attackDirection ==
SFEngine.ATTACK_LEFT){
                                                        enemies[x].posX = 0;
                                                }else{
                                                        enemies[x].posX = 3f;
                                                }
                                        }
                                        gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();
                                        gl.glScalef(.25f, .25f, 1f);

                                        if (enemies[x].posY >= 2.75f){
                                                enemies[x].posY -= SFEngine.SCOUT_SPEED;

                                        }else{
                                                enemies[x].posX =
enemies[x].getNextScoutX();
                                                enemies[x].posY =
enemies[x].getNextScoutY();
                                                enemies[x].posT += SFEngine.SCOUT_SPEED;


                                        }
                                                gl.glTranslatef(enemies[x].posX,
enemies[x].posY, 0f);
                                                gl.glMatrixMode(GL10.GL_TEXTURE);
                                                gl.glLoadIdentity();
                                                gl.glTranslatef(0.50f, .25f , 0.0f);
                                        enemies[x].draw(gl, spriteSheets);
                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();

                                        break;
                                case SFEngine.TYPE_WARSHIP:
                                        if (enemies[x].posY < 0){
                                                enemies[x].posY = (randomPos.nextFloat()
* 4) + 4;
                                                enemies[x].posX = randomPos.nextFloat()
* 3;
                                                enemies[x].isLockedOn = false;
                                                enemies[x].lockOnPosX = 0;
                                        }
                                        gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();
                                        gl.glScalef(.25f, .25f, 1f);

                                        if (enemies[x].posY >= 3){
                                                enemies[x].posY -=
SFEngine.WARSHIP_SPEED;

                                        }else{
                                                if (!enemies[x].isLockedOn){
                                                        enemies[x].lockOnPosX =
randomPos.nextFloat() * 3;
                                                        enemies[x].isLockedOn = true;
                                                        enemies[x].incrementXToTarget
=(float) ((enemies[x].lockOnPosX - enemies[x].posX )/ (enemies[x].posY /
(SFEngine.WARSHIP_SPEED* 4)));
                                                }
                                                enemies[x].posY -=
(SFEngine.WARSHIP_SPEED* 2);
                                                enemies[x].posX +=
enemies[x].incrementXToTarget;

                                        }
                                                gl.glTranslatef(enemies[x].posX,
enemies[x].posY, 0f);
                                                gl.glMatrixMode(GL10.GL_TEXTURE);
                                                gl.glLoadIdentity();
                                                gl.glTranslatef(0.75f, .25f , 0.0f);
                                        enemies[x].draw(gl,spriteSheets);
                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();

                                        break;

                                }

                        }
                }

        }


        private void movePlayer1(GL10 gl){
                if(!player1.isDestroyed){
                        switch (SFEngine.playerFlightAction){
                        case SFEngine.PLAYER_BANK_LEFT_1:
                                gl.glMatrixMode(GL10.GL_MODELVIEW);
                                gl.glLoadIdentity();
                                gl.glPushMatrix();
                                gl.glScalef(.25f, .25f, 1f);
                                if (goodGuyBankFrames <
SFEngine.PLAYER_FRAMES_BETWEEN_ANI && SFEngine.playerBankPosX > 0){
                                        SFEngine.playerBankPosX -=
SFEngine.PLAYER_BANK_SPEED;
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f,
0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.75f,0.0f, 0.0f);
                                        goodGuyBankFrames += 1;
                                }else if (goodGuyBankFrames >=
SFEngine.PLAYER_FRAMES_BETWEEN_ANI && SFEngine.playerBankPosX > 0){
                                        SFEngine.playerBankPosX -=
SFEngine.PLAYER_BANK_SPEED;
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f,
0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.0f,0.25f, 0.0f);
                                }else{
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f,
0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.0f,0.0f, 0.0f);
                                }
                                player1.draw(gl,spriteSheets);
                                gl.glPopMatrix();
                                gl.glLoadIdentity();

                                break;
                        case SFEngine.PLAYER_BANK_RIGHT_1:
                                gl.glMatrixMode(GL10.GL_MODELVIEW);
                                gl.glLoadIdentity();
                                gl.glPushMatrix();
                                gl.glScalef(.25f, .25f, 1f);
                                if (goodGuyBankFrames <
SFEngine.PLAYER_FRAMES_BETWEEN_ANI && SFEngine.playerBankPosX < 3){
                                        SFEngine.playerBankPosX +=
SFEngine.PLAYER_BANK_SPEED;
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f,
0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.25f,0.0f, 0.0f);
                                        goodGuyBankFrames += 1;
                                }else if (goodGuyBankFrames >=
SFEngine.PLAYER_FRAMES_BETWEEN_ANI && SFEngine.playerBankPosX < 3){
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f, 0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.50f,0.0f, 0.0f);
                                        SFEngine.playerBankPosX +=
SFEngine.PLAYER_BANK_SPEED;
                                }else{
                                        gl.glTranslatef(SFEngine.playerBankPosX, 0f,
0f);
                                        gl.glMatrixMode(GL10.GL_TEXTURE);
                                        gl.glLoadIdentity();
                                        gl.glTranslatef(0.0f,0.0f, 0.0f);
                                }
                                player1.draw(gl,spriteSheets);
                                gl.glPopMatrix();
                                gl.glLoadIdentity();
                                break;
                        case SFEngine.PLAYER_RELEASE:
                                gl.glMatrixMode(GL10.GL_MODELVIEW);
                                gl.glLoadIdentity();
                                gl.glPushMatrix();
                                gl.glScalef(.25f, .25f, 1f);
                                gl.glTranslatef(SFEngine.playerBankPosX, 0f, 0f);
                                gl.glMatrixMode(GL10.GL_TEXTURE);
                                gl.glLoadIdentity();
                                gl.glTranslatef(0.0f,0.0f, 0.0f);
                                goodGuyBankFrames = 0;
                                player1.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(SFEngine.playerBankPosX, 0f, 0f);
                                gl.glMatrixMode(GL10.GL_TEXTURE);
                                gl.glLoadIdentity();
                                gl.glTranslatef(0.0f,0.0f, 0.0f);
                                player1.draw(gl,spriteSheets);
                                gl.glPopMatrix();
                                gl.glLoadIdentity();
                                break;
                        }
                        firePlayerWeapon(gl);
                }


        }
        private void detectCollisions(){
                for (int y = 0; y < 3; y ++){
                        if (playerFire[y].shotFired){
                                for (int x = 0; x < SFEngine.TOTAL_INTERCEPTORS +
SFEngine.TOTAL_SCOUTS + SFEngine.TOTAL_WARSHIPS - 1; x++ ){
                                        if(!enemies[x].isDestroyed && enemies[x].posY <
4.25 ){
                                                if ((playerFire[y].posY >=
enemies[x].posY - 1
                                                                && playerFire[y].posY <=
enemies[x].posY )
                                                                && (playerFire[y].posX
<= enemies[x].posX + 1
                                                                && playerFire[y].posX >=
enemies[x].posX - 1)){
                                                        int nextShot = 0;
                                                        enemies[x].applyDamage();
                                                        playerFire[y].shotFired = false;
                                                        if (y == 3){
                                                                nextShot = 0;
                                                        }else{
                                                                nextShot = y + 1;
                                                        }
                                                        if
(playerFire[nextShot].shotFired == false){

playerFire[nextShot].shotFired = true;

playerFire[nextShot].posX = SFEngine.playerBankPosX;

playerFire[nextShot].posY = 1.25f;
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }
        private void firePlayerWeapon(GL10 gl){
                for(int x = 0; x < 4; x++){
                        if (playerFire[x].shotFired){
                                int nextShot = 0;
                                if (playerFire[x].posY > 4.25){
                                        playerFire[x].shotFired = false;
                                }else{
                                        if (playerFire[x].posY> 2){
                                                if (x == 3){
                                                        nextShot = 0;
                                                }else{
                                                        nextShot = x + 1;
                                                }
                                                if (playerFire[nextShot].shotFired ==
false){
                                                        playerFire[nextShot].shotFired =
true;
                                                        playerFire[nextShot].posX =
SFEngine.playerBankPosX;
                                                        playerFire[nextShot].posY =
1.25f;
                                                }

                                        }
                                        playerFire[x].posY +=
SFEngine.PLAYER_BULLET_SPEED;
                                        gl.glMatrixMode(GL10.GL_MODELVIEW);
                                        gl.glLoadIdentity();
                                        gl.glPushMatrix();
                                        gl.glScalef(.25f, .25f, 0f);
                                        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,spriteSheets);
                                        gl.glPopMatrix();
                                        gl.glLoadIdentity();

                                }
                        }
                }
        }
        private void scrollBackground1(GL10 gl){
                if (bgScroll1 == Float.MAX_VALUE){
                        bgScroll1 = 0f;
                }


        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glPushMatrix();
        gl.glScalef(1f, 1f, 1f);
        gl.glTranslatef(0f, 0f, 0f);

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

        background.draw(gl);
        gl.glPopMatrix();
        bgScroll1 +=SFEngine.SCROLL_BACKGROUND_1;
        gl.glLoadIdentity();


        }
        private void scrollBackground2(GL10 gl){
                if (bgScroll2 == Float.MAX_VALUE){
                        bgScroll2 = 0f;
                }
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glPushMatrix();
        gl.glScalef(.5f, 1f, 1f);
        gl.glTranslatef(1.5f, 0f, 0f);

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

        background2.draw(gl);
        gl.glPopMatrix();
        bgScroll2 +=SFEngine.SCROLL_BACKGROUND_2;
        gl.glLoadIdentity();
        }
        @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 config) {
                // TODO Auto-generated method stub
                initializeInterceptors();
                initializeScouts();
                initializeWarships();
                initializePlayerWeapons();
                textureLoader = new SFTextures(gl);
                spriteSheets = textureLoader.loadTexture(gl, SFEngine.CHARACTER_SHEET,
SFEngine.context, 1);
                spriteSheets = textureLoader.loadTexture(gl, SFEngine.WEAPONS_SHEET,
SFEngine.context, 2);

                gl.glEnable(GL10.GL_TEXTURE_2D);
                gl.glClearDepthf(1.0f);
                gl.glEnable(GL10.GL_DEPTH_TEST);
                gl.glDepthFunc(GL10.GL_LEQUAL);

                background.loadTexture(gl,SFEngine.BACKGROUND_LAYER_ONE,
SFEngine.context);
                background2.loadTexture(gl,SFEngine.BACKGROUND_LAYER_TWO,
SFEngine.context);
        }

}

Finally, Listings 8–5 and 8–6 show the last two key files in this project.The SFGoodGuy.java and SFBadGuy.java contain the code for the player and enemy classes.While you shouldn't have noticed any problems in these files early on in the writing of this game, it can never hurt to double check your work.

When looking at the SFEnemy.java, check the formulas for the Bezier curves.

Listing 8–5. SFGoodGuy.java

package com.proandroidgames;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class SFGoodGuy {
        public boolean isDestroyed = false;
        private int damage = 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, 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 void applyDamage(){
                damage++;
                if (damage == SFEngine.PLAYER_SHIELDS){
                        isDestroyed = true;
                }

        }
         public SFGoodGuy() {
        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(GL10 gl, 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 8–6. SFEnemy.java

package com.proandroidgames;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Random;

import javax.microedition.khronos.opengles.GL10;

public class SFEnemy {

                public float posY = 0f;
                public float posX = 0f;
                public float posT = 0f;
                public float incrementXToTarget = 0f;
                public float incrementYToTarget = 0f;
                public int attackDirection = 0;
                public boolean isDestroyed = false;
                private int damage = 0;

                public int enemyType = 0;

                public boolean isLockedOn = false;
                public float lockOnPosX = 0f;
                public float lockOnPosY = 0f;

                private Random randomPos = new Random();

         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 void applyDamage(){
                damage++;
                switch(enemyType){
                case SFEngine.TYPE_INTERCEPTOR:
                        if (damage == SFEngine.INTERCEPTOR_SHIELDS){
                                isDestroyed = true;
                        }                        
                        break;
                case SFEngine.TYPE_SCOUT:
                        if (damage == SFEngine.SCOUT_SHIELDS){
                                isDestroyed = true;
                        }
                        break;
                case SFEngine.TYPE_WARSHIP:
                        if (damage == SFEngine.WARSHIP_SHIELDS){
                                isDestroyed = true;
                        }
                        break;
                }
        }

         public SFEnemy(int type, int direction) {
                 enemyType = type;
                 attackDirection = direction;
                 posY = (randomPos.nextFloat() * 4) + 4;
                 switch(attackDirection){
                 case SFEngine.ATTACK_LEFT:
                         posX = 0;
                         break;
                 case SFEngine.ATTACK_RANDOM:
                         posX = randomPos.nextFloat() * 3;
                         break;
                 case SFEngine.ATTACK_RIGHT:
                         posX = 3;
                         break;
                 }
                 posT = SFEngine.SCOUT_SPEED;

        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 float getNextScoutX(){
                 if (attackDirection == SFEngine.ATTACK_LEFT){
                         return (float)((SFEngine.BEZIER_X_4*(posT*posT*posT)) +
(SFEngine.BEZIER_X_3 * 3 * (posT * posT) * (1-posT)) + (SFEngine.BEZIER_X_2 * 3 * posT *
((1-posT) * (1-posT))) + (SFEngine.BEZIER_X_1 * ((1-posT) * (1-posT) * (1-posT))));
                 }else{
                         return (float)((SFEngine.BEZIER_X_1*(posT*posT*posT)) +
(SFEngine.BEZIER_X_2 * 3 * (posT * posT) * (1-posT)) + (SFEngine.BEZIER_X_3 * 3 * posT *
((1-posT) * (1-posT))) + (SFEngine.BEZIER_X_4 * ((1-posT) * (1-posT) * (1-posT))));
                 }

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

         public void draw(GL10 gl, 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);
         }

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

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