Chapter 21: Completing the Scrolling Shooter Game

In this chapter, we will complete the Scrolling Shooter game. We will achieve this by coding the remaining component classes, which represent the three different types of aliens and the lasers that they can shoot at the player. Once we have completed the component classes, we will make minor modifications to the GameEngine, Level, and GameObjectFactory classes to accommodate these newly completed entities.

The final step to complete the game is the collision detection that we will add to the PhysicsEngine class.

Here are the topics we will be covering in this chapter:

  • Coding the alien's components
  • Spawning the aliens
  • Coding the collision detection

We are nearly done, so let's get coding.

Adding the alien's components

Remember that some of the alien's components are the same as some of the other components we have already coded. For example, all the aliens and their lasers have a StdGraphicsComponent. In addition, the alien's laser has the same components as the player's laser. The only difference is the specification (that we have already coded) and the need for an AlienLaserSpawner interface.

As all the specifications are completed, everything is in place, so we can just go ahead and code these remaining classes shown next.

AlienChaseMovementComponent

Create a new class called AlienChaseMovementComponent, and then add the following member and constructor methods:

class AlienChaseMovementComponent implements MovementComponent {

    private Random mShotRandom = new Random();

    // Gives this class the ability to tell the game engine

    // to spawn a laser

    private AlienLaserSpawner alienLaserSpawner;

    AlienChaseMovementComponent(AlienLaserSpawner als){

        alienLaserSpawner = als;

    }

}

There are just two members. One is a Random object called mShotRandom that we will use to decide when the chasing alien should take a shot at the player and the other is an instance of AlienLaserSpawner. We will code and implement AlienLaserSpawner after we finish this class. In the constructor, we initialize the AlienLaserSpawner instance with the reference passed in as a parameter.

This is exactly what we did for the PlayerLaserSpawner class and we will get a refresher on how it works when we implement the rest of the parts of AlienLaserSpawner shortly.

Now we can code the move method. Remember that the move method is required because this class implements the MovementComponent interface.

First, add the signature along with a fairly long list of local variables that will be needed in the move method. It will help you quite a bit if you review the comments while adding this code:

@Override

    public boolean move(long fps, Transform t,

Transform playerTransform) {

// 1 in 100 chances of shot being fired

//when in line with player

      final int TAKE_SHOT=0; // Arbitrary

      final int SHOT_CHANCE = 100;

      // How wide is the screen?

      float screenWidth = t.getmScreenSize().x;

      // Where is the player?

      PointF playerLocation = playerTransform.

      getLocation();

      // How tall is the ship

      float height = t.getObjectHeight();

      // Is the ship facing right?

      boolean facingRight =t.getFacingRight();

      // How far off before the ship doesn't bother

      chasing?

      float mChasingDistance = t.getmScreenSize().x / 3f;

      // How far can the AI see?

      float mSeeingDistance = t.getmScreenSize().x / 1.5f;

      // Where is the ship?

      PointF location = t.getLocation();

      // How fast is the ship?

      float speed = t.getSpeed();

      // Relative speed difference with player

      float verticalSpeedDifference = .3f;

      float slowDownRelativeToPlayer = 1.8f;

      // Prevent the ship locking on too accurately

      float verticalSearchBounce = 20f;

       // More code here next

}

As with all move methods, the alien's Transform and the player's Transform are passed in as parameters.

The first two variables are final int. SHOT_CHANCE being equal to 100 will mean that every time the move method detects an opportunity to take a shot, there is a 1 percent chance that it will take it and fire a new laser. The TAKE_SHOT variable is simply an arbitrary number that represents the value that a randomly generated number must equal to for taking a shot. TAKE_SHOT could be initialized to any value between 0 and 99 and the effect would be the same.

Most of the local variables are initialized with values from one of the passed-in Transform references. We could use the various getter methods throughout the code but initializing some local variables as we have done is cleaner and more readable. In addition, it might speed the code up a little bit as well.

Many of the new local variables are therefore self-explanatory. We have things such as location and playerLocation for the positions of the protagonists. There is facingRight, height, and speed for which way the alien is looking, how tall it is, and how fast it is traveling. Furthermore, we have also made a local variable to remember the width of the screen in pixels: screenWidth.

Some variables, however, need a bit more explanation. We have chasingDistance and seeingDistance. Look at the way they are initialized using a fraction of the horizontal screen size. The actual fraction used is slightly arbitrary and you can experiment with different values, but what these variables will do is determine at what distance the alien will start to chase (home in on) the player and at what distance it will "see" the player and consider firing a laser.

The final three variables are verticalSpeedDifference, slowDownReleativeToPlayer, and verticalSearchBounce. These three variables also have apparently arbitrary initialization values and can also be experimented with. Their purpose is to regulate the movement speed of the alien relative to the player.

In this game, the player graphic just sits in the center (horizontally) of the screen. Any movement (horizontally) is an illusion created by the scrolling background. Therefore, we need the speed of the aliens to be moderated based on which direction the player is flying. For example, when the player is headed toward an alien heading straight at them, the player will appear to whizz past them very quickly. As another example, if the player is headed away from an alien (perhaps one that is chasing them), the player will appear to slowly pull away.

This slight convolutedness will be avoided in the next project because the appearance of movement will be created with the use of a camera.

Now add this next code, still inside the move method. Note the highlighted comment that shows where this code goes in relation to the previous code:

// More code here next

// move in the direction of the player

// but relative to the player's direction of travel

if (Math.abs(location.x - playerLocation.x)

          > mChasingDistance) {

    

     if (location.x < playerLocation.x) {

          t.headRight();

     } else if (location.x > playerLocation.x) {

          t.headLeft();

     }

}

// Can the Alien "see" the player? If so, try and align vertically

if (Math.abs(location.x - playerLocation.x)

          <= mSeeingDistance) {

    

     // Use a cast to get rid of unnecessary

     // floats that make ship judder

     if ((int) location.y - playerLocation.y

               < -verticalSearchBounce) {

          

          t.headDown();

     } else if ((int) location.y - playerLocation.y

               > verticalSearchBounce) {

          

          t.headUp();

     }

     // Compensate for movement relative to player-

     // but only when in view.

     // Otherwise alien will disappear miles off to one

     // side

     if(!playerTransform.getFacingRight()){

          location.x += speed * slowDownRelativeToPlayer /

          fps;

     } else{

          location.x -= speed * slowDownRelativeToPlayer /

          fps;

     }

}

else{

     // stop vertical movement otherwise alien will

     // disappear off the top or bottom

     t.stopVertical();

}

// More code here next

In the code we just added, there is an if block and an if-else block. The if block subtracts the player's horizontal position from the alien's horizontal position and tests whether it is greater than the distance at which an alien should chase the player. If the condition is met, the internal if-else code sets the heading of the alien.

The if-else block tests whether the alien can "see" the player. If it can, it aligns itself vertically but only if it is unaligned to the extent held in mVerticalSearchBounce. This has the effect of the alien "bouncing" up and down as it homes in on the player.

After the vertical adjustment, the code detects which way the player is facing and adjusts the speed of the alien to create the effect of the player having speed. If the player is facing away from the alien, they will appear to pull away, and if they are facing toward the alien, they will close quickly.

The final else block handles what happens when the alien cannot "see" the player and stops all vertical movement.

Now add this next code, still inside the move method. Note the highlighted comment that shows where this code goes in relation to the previous code:

     // More code here next

     // Moving vertically is slower than horizontally

     // Change this to make game harder

     if(t.headingDown()){

          location.y += speed * verticalSpeedDifference /

          fps;

     }

     else if(t.headingUp()){

          location.y -= speed * verticalSpeedDifference /

          fps;

     }

     // Move horizontally

     if(t.headingLeft()){

          location.x -= (speed) / fps;

     }

     if(t.headingRight()){

          location.x += (speed) / fps;

     }

     // Update the collider

     t.updateCollider();

     // Shoot if the alien is within a ships height above,

     // below, or in line with the player?

     // This could be a hit or a miss

     if(mShotRandom.nextInt(SHOT_CHANCE) == TAKE_SHOT) {

          if (Math.abs(playerLocation.y - location.y) <

          height) {

               // Is the alien facing the right direction

               // and close enough to the player

               if ((facingRight && playerLocation.x >

               location.x

                         || !facingRight && playerLocation.

                         x <

                         location.x)

                         && Math.abs(playerLocation.x -

                         location.x)

                         < screenWidth) {

                    

                    // Fire!

                    alienLaserSpawner.spawnAlienLaser(t);

               }

          }

     }

     return true;

}

The first part of the code checks whether the alien is heading in each of the four possible directions and adjusts its position accordingly, and then updates the alien's collider to its new position.

The final block of code calculates whether the alien will take a shot during this frame. The if condition generates a 1 in 100 chance of firing a shot. This is arbitrary but works quite well.

If the alien decides to take a shot, it tests whether it is vertically within the height of a ship to the player. Note that this could result in a laser that narrowly misses the player or a shot on target.

The final internal if detects whether the alien is facing in the correct direction (toward the player) and that it is within a screen's width of the player. Once all these conditions are met, the AlienLaserSpawner interface is used to call spawnAlienLaser.

The reason we want the alien to fire so apparently infrequently is that the chance is tested every single frame of the game and actually creates quite a feisty alien. The aliens are restricted in their ferocity by the availability of lasers. If there isn't one available, then the call to spawnAlienLaser yields no result.

There are some errors in the code because AlienLaserSpawner doesn't exist yet. We will now deal with it just like PlayerLaserSpawner.

Coding AlienLaserSpawner

To get rid of the errors in the move method, we need to code and then implement a new interface called AlienLaserSpawner.

Create a new class/interface and code it as shown next:

Important note

When you create a class, as we have seen, you have the option to select Interface from the drop-down options. However, if you edit the code to be as shown, you don't have to select Interface or even worry about selecting the access specifier (Package private) either. The code that you type will override any options or drop-down selections you might choose. Therefore, when making a new class or interface, type the code in full or use the different selectors—whichever you prefer.

// This allows an alien to communicate with the game engine

// and spawn a laser

interface AlienLaserSpawner {

    void spawnAlienLaser(Transform transform);

}

This code is exactly the same as the PlayerLaserSpawner code except for the name. Next, we will implement it in the GameEngine class.

Implementing the interface in GameEngine

Add AlienLaserSpawner to the list of interfaces that GameEngine implements as shown highlighted next:

class GameEngine extends SurfaceView

        implements Runnable,

        GameStarter,

        GameEngineBroadcaster,

        PlayerLaserSpawner,

        AlienLaserSpawner {

Now add the required method to GameEngine:

public void spawnAlienLaser(Transform transform) {

     ArrayList<GameObject> objects =

     mLevel.getGameObjects();

     // Shoot laser IF AVAILABLE

     // Pass in the transform of the ship

     // that requested the shot to be fired

     if (objects.get(Level.mNextAlienLaser

     ).spawn(transform)) {

          Level.mNextAlienLaser++;

          mSoundEngine.playShoot();

          if(Level.mNextAlienLaser ==Level.LAST_ALIEN_

          LASER + 1) {

               // Just used the last laser

               Level.mNextAlienLaser =

               Level.FIRST_ALIEN_LASER;

          }

     }

}

Now, any class that has an AlienLaserSpawner reference (such as AlienChaseMovementComponent) will be able to call the spawnAlienLaser method. The method works in the same way the spawnPlayerLaser method does. It uses the nextAlienLaser variable from the Level class to pick a GameObject to spawn. If a new alien laser is successfully spawned, then a sound effect is played and nextAlienLaser is updated ready for the next shot.

AlienDiverMovementComponent

Create a new class called AlienDiverMovementComponent. Add all the code shown next:

import android.graphics.PointF;

import java.util.Random;

class AlienDiverMovementComponent implements MovementComponent {

    @Override

    public boolean move(long fps, Transform t,

    Transform playerTransform) {

        // Where is the ship?

        PointF location = t.getLocation();

        // How fast is the ship?

        float speed = t.getSpeed();

        // Relative speed difference with player

        float slowDownRelativeToPlayer = 1.8f;

        // Compensate for movement relative to player-

        // but only when in view.

        // Otherwise alien will disappear miles off to one

        side

        if(!playerTransform.getFacingRight()){

            location.x += speed * slowDownRelativeToPlayer

            / fps;

        } else{

            location.x -=  speed *

            slowDownRelativeToPlayer / fps;

        }

        // Fall down then respawn at the top

        location.y += speed / fps;

        if(location.y > t.getmScreenSize().y){

            // Respawn at top

            Random random = new Random();

            location.y = random.nextInt(300)

               - t.getObjectHeight();

            location.x = random

                 .nextInt((int)t.getmScreenSize().x);

        }

        // Update the collider

        t.updateCollider();

        return true;

    }

}

As we have come to expect, a movement-related class has the move method. Be sure to study the variables and make a mental note of the names as well as how they are initialized by the Transform references of the alien and the player. There are much fewer member variables than AlienChaseMovementComponent because diving requires much less "thinking" on the part of the alien than chasing does.

After the variables, the code that performs the diving logic can be divided into two parts. The first is an if-else block. This block detects whether the player is facing right or left. It then moves the alien horizontally, relative to the direction of the player, the speed of the alien, and the time the frame took (fps).

After the if-else block, the vertical position of the alien is updated. This movement is very simple, just one line of code:

// Fall down then respawn at the top

location.y += speed / fps;

However, the if block that follows this one line of code does the job of detecting whether the alien has disappeared off the bottom of the screen and if it has, it respawns it above the screen ready to start diving on the player again—usually quite soon.

The final line of code updates the collider ready to detect collisions in its newly updated position.

AlienHorizontalSpawnComponent

Create a class called AlienHorizontalSpawnComponent. This class will be used to randomly spawn aliens off screen to the left or right. This will be used for both aliens that chase and aliens that patrol. You can probably then guess that we will need an AlienVerticalSpawnComponent, as well as spawning a diving alien.

Add the code shown next to the AlienHorizontalSpawnComponent class. It has one method, spawn. This method is required because it implements the SpawnComponent interface:

import android.graphics.PointF;

import java.util.Random;

class AlienHorizontalSpawnComponent implements SpawnComponent {

    @Override

    public void spawn(Transform playerLTransform,

    Transform t) {

        // Get the screen size

        PointF ss = t.getmScreenSize();

        // Spawn just off screen randomly left or right

        Random random = new Random();

        boolean left = random.nextBoolean();

        // How far away?

        float distance =  random.nextInt(2000)

                + t.getmScreenSize().x;

        // Generate a height to spawn at where

        // the entire ship is vertically on-screen

        float spawnHeight = random.nextFloat()

                * ss.y - t.getSize().y;

        // Spawn the ship

        if(left){

            t.setLocation(-distance, spawnHeight);

            t.headRight();

        }

else{

            t.setLocation(distance, spawnHeight);

            t.headingLeft();

        }

    }

}

Most of the code involves initializing some local variables to correctly (and randomly) spawn the alien. First of all, we capture the screen size in ss.

Next, we declare a Random object called random. We will spawn the alien using three random values: random left or right, random height, and random distance horizontally. Then, aliens could appear from either side at any height and will sometimes appear immediately and sometimes take a while to travel to the player.

The next variable is a Boolean called left and it is initialized using the nextBoolean method of the Random class, which randomly returns a value of true or false. Then, a random float value is stored in distance, followed by a random float value in height. We now know where to spawn the alien.

Using an if-else block that checks the value of left, the alien is then spawned using the setLocation method at the previously calculated random height and random distance. Note that depending upon whether the alien is spawned off to the left or right, it is made to face in the appropriate direction, so it will eventually come across the player. If an alien was spawned off to the right and is heading right, then it might never be seen by the player and would be of no use to the game at all.

AlienPatrolMovementComponent

Create a class called AlienPatrolMovementComponent and code the constructor method as shown next:

import android.graphics.PointF;

import java.util.Random;

class AlienPatrolMovementComponent implements MovementComponent {

    private AlienLaserSpawner alienLaserSpawner;

    private Random mShotRandom = new Random();

    AlienPatrolMovementComponent(AlienLaserSpawner als){

        alienLaserSpawner = als;

    }

}

As the patrolling aliens are required to fire lasers, they will need a reference to AlienLaserSpawner. The constructor also initializes a Random object to avoid initializing a new one on almost every single frame of the game.

Now add the first part of the move method:

@Override

public boolean move(long fps, Transform t,

                         Transform playerTransform) {

     final int TAKE_SHOT = 0; // Arbitrary

     // 1 in 100 chance of shot being fired

     // when in line with player

     final int SHOT_CHANCE = 100;

     // Where is the player

     PointF playerLocation = playerTransform.getLocation();

     // The top of the screen

     final float MIN_VERTICAL_BOUNDS = 0;

     // The width and height of the screen

     float screenX = t.getmScreenSize().x;

     float screenY = t.getmScreenSize().y;

     // How far ahead can the alien see?

     float mSeeingDistance = screenX * .5f;

     // Where is the alien?

     PointF loc = t.getLocation();

     // How fast is the alien?

     float speed = t.getSpeed();

     // How tall is the alien

     float height = t.getObjectHeight();

     // Stop the alien going too far away

     float MAX_VERTICAL_BOUNDS = screenY- height;

     final float MAX_HORIZONTAL_BOUNDS = 2 * screenX;

     final float MIN_HORIZONTAL_BOUNDS = 2 * -screenX;

     // Adjust the horizontal speed relative

     // to the player's heading

     // Default is no horizontal speed adjustment

     float horizontalSpeedAdjustmentRelativeToPlayer = 0 ;

     // How much to speed up or slow down relative

     // to player's heading

     float horizontalSpeedAdjustmentModifier = .8f;

    

     // More code here soon

}

The local variables will look quite familiar by now. We declare and initialize them to avoid repeatedly calling the getters of the Transform objects (alien and player). In addition to the usual suspects, we have some variables to control the bounds of the alien's movement, MAX_VERTICAL_BOUNDS, MAX_HORIZONTAL_BOUNDS, and MIN_HORIZONTAL_BOUNDS. We will use them in the next part of the code to constrain how far away the alien can fly before it changes direction.

Now add the next part of the move method:

// More code here soon

// Can the Alien "see" the player? If so make speed relative

if (Math.abs(loc.x - playerLocation.x)

          < mSeeingDistance) {

     if(playerTransform.getFacingRight()

               != t.getFacingRight()){

          

          // Facing a different way speed up the alien

        horizontalSpeedAdjustmentRelativeToPlayer =

                  speed *

                  horizontalSpeedAdjustmentModifier;

     }

else{

          // Facing the same way slow it down

          horizontalSpeedAdjustmentRelativeToPlayer =

                    -(speed *

                     horizontalSpeedAdjustmentModifier);

     }

}

// Move horizontally taking into account

// the speed modification

if(t.headingLeft()){

     loc.x -= (speed +

          horizontalSpeedAdjustmentRelativeToPlayer) / fps;

          

     // Turn the ship around when it reaches the

     // extent of its horizontal patrol area

     if(loc.x < MIN_HORIZONTAL_BOUNDS){

          loc.x = MIN_HORIZONTAL_BOUNDS;

          t.headRight();

     }

}

else{

     loc.x += (speed +

          horizontalSpeedAdjustmentRelativeToPlayer) / fps;

    

     // Turn the ship around when it reaches the

     // extent of its horizontal patrol area

     if(loc.x > MAX_HORIZONTAL_BOUNDS){

          loc.x = MAX_HORIZONTAL_BOUNDS;

          t.headLeft();

     }

}

// More code here soon

The code just added can be broken up into two parts and it is very similar to the other alien movement-related code. Adjust the speed based on which way the player is facing, and then check whether the alien has reached either a vertical or horizontal position limit and if it has, change the direction it is heading in.

Now add the final part of the move method:

// More code here soon

// Vertical speed remains same,

// Not affected by speed adjustment

if(t.headingDown()){

     loc.y += (speed) / fps;

     if(loc.y > MAX_VERTICAL_BOUNDS){

          t.headUp();

     }

}

else{

     loc.y -= (speed) / fps;

     if(loc.y < MIN_VERTICAL_BOUNDS){

          t.headDown();

     }

}

// Update the collider

t.updateCollider();

// Shoot if the alien within a ships height above,

// below, or in line with the player?

// This could be a hit or a miss

if(mShotRandom.nextInt(SHOT_CHANCE) == TAKE_SHOT) {

     if (Math.abs(playerLocation.y - loc.y) < height) {

          // is the alien facing the right direction

          // and close enough to the player

          if ((t.getFacingRight() && playerLocation.x >

          loc.x

                    || !t.getFacingRight()

                    && playerLocation.x < loc.x)

                    && Math.abs(playerLocation.x - loc.x)

                    < screenX) {

              

               // Fire!

               alienLaserSpawner.spawnAlienLaser(t);

          }

     }

}

return true;

}// End of move method

The final code in this class moves the alien based on the heading and adjusted speed, then updates its collider. The code to take a shot is the same as we used in AlienChaseMovementComponent.

One more component to code and then we are nearly done.

AlienVerticalSpawnComponent

Create a class called AlienVerticalSpawnComponent and code the spawn method as shown next:

import java.util.Random;

class AlienVerticalSpawnComponent implements SpawnComponent {

    public void spawn(Transform playerLTransform,

          Transform t) {

        // Spawn just off screen randomly but

        // within the screen width

        Random random = new Random();

        float xPosition =  random.nextInt((int)t

                .getmScreenSize().x);

        // Set the height to vertically

        // just above the visible game

        float spawnHeight = random

                .nextInt(300) - t.getObjectHeight();

        // Spawn the ship

        t.setLocation(xPosition, spawnHeight);

        // Always going down

        t.headDown();

    }

}

This class will be used to randomly spawn a diving alien offscreen. As the aliens always dive downward, we generate two random values: one for the horizontal position (xPosition) and one for how many pixels there are above the top of the screen (spawnHeight). Then all we need to do is call the setLocation method with these two new values as the arguments. Finally, we call the headDown method to set the direction of travel.

Now we can go ahead and spawn some aliens into our game.

Spawning the aliens

Now that all the alien components, as well as AlienLaserSpawner, are coded, we can put them all to work in the game. It will take three steps, as follows:

  1. Update GameEngine's deSpawnReSpawn method to spawn some of each alien.
  2. Update the Level class to add some aliens and alien lasers to the ArrayList of objects.
  3. Update the GameObjectFactory class to handle instantiating the correct component classes (that we just coded) when the level class requests the various alien GameObject instances be built.

Let's complete these steps now.

Updating the GameEngine class

Add this code to the end of the deSpawnReSpawn method:

for (int i = Level.FIRST_ALIEN;

     i != Level.LAST_ALIEN + 1; i++) {

    

     objects.get(i).spawn(objects

          .get(Level.PLAYER_INDEX).getTransform());

}

This loops through the appropriate indexes of object ArrayList and spawns the aliens. The alien lasers will be spawned by the spawnAlienLaser method when requested by an alien (chaser or patroller).

Next, we will update the Level class.

Updating the Level class

Add this code to the end of the buildGameObjects method of the Level class. You can identify exactly where it goes by the pre-existing comments and return statement that I have highlighted in the next code:

// Create some aliens

objects.add(FIRST_ALIEN, factory

          .create(new AlienChaseSpec()));

objects.add(SECOND_ALIEN, factory

          .create(new AlienPatrolSpec()));

objects.add(THIRD_ALIEN, factory

          .create(new AlienPatrolSpec()));

objects.add(FOURTH_ALIEN, factory

          .create(new AlienChaseSpec()));

objects.add(FIFTH_ALIEN, factory

          .create(new AlienDiverSpec()));

objects.add(SIXTH_ALIEN, factory

          .create(new AlienDiverSpec()));

// Create some alien lasers

for (int i = FIRST_ALIEN_LASER; i != LAST_ALIEN_LASER + 1; i++) {

     objects.add(i, factory

               .create(new AlienLaserSpec()));

}

mNextAlienLaser = FIRST_ALIEN_LASER;

return objects;

In the previous code, we use the final variables of the Level class to add aliens with different specifications into objects ArrayList at the required positions.

Now that we are calling create with these specifications, we will need to update the GameObjectFactory class so that it knows how to handle them.

Updating the GameObjectFactory class

Add the highlighted case blocks to the switch statement in the create method of the GameObjectFactory class:

case "BackgroundSpawnComponent":

     object.setSpawner(new BackgroundSpawnComponent());

     break;

    

case "AlienChaseMovementComponent":

     object.setMovement(

               new AlienChaseMovementComponent(

               mGameEngineReference));

     break;

case "AlienPatrolMovementComponent":

     object.setMovement(

               new AlienPatrolMovementComponent(

               mGameEngineReference));

     break;

case "AlienDiverMovementComponent":

     object.setMovement(

               new AlienDiverMovementComponent());

     break;

case "AlienHorizontalSpawnComponent":

     object.setSpawner(

               new AlienHorizontalSpawnComponent());

     break;

case "AlienVerticalSpawnComponent":

     object.setSpawner(

               new AlienVerticalSpawnComponent());

     break;

default:

     // Error unidentified component

     break;

The new code simply detects the various alien-related components, and then initializes them and adds them to the GameObject instance under construction in the same way that we initialized the other movement- and spawn-related components when we handled the player's components.

Let's run the game.

Running the game

Although the game is still not finished, we can run it to see the progress so far:

Figure 21.1 – Checking the progress of the game

Figure 21.1 – Checking the progress of the game

The figure shows how one of each type of alien (and the player) are now going about their various tasks, chasing, patrolling, and diving.

Now we can make them bump into things and the game will be done.

Detecting collisions

We don't need to detect everything bumping into everything else. Specifically, we need to detect the following three cases:

  • An alien bumping into the player, resulting in losing a life
  • The alien laser bumping into the player, resulting in losing a life
  • The player's laser bumping into an alien, resulting in the score going up, a particle effect explosion being started, and the dead alien being respawned

In the update method of the PhysicsEngine class, change the return statement as highlighted next:

// This signature and much more will change later in the project

boolean update(long fps, ArrayList<GameObject> objects,

                  GameState gs, SoundEngine se,

                  ParticleSystem ps){

     // Update all the game objects

     for (GameObject object : objects) {

          if (object.checkActive()) {

               object.update(fps, objects.get(

               Level.PLAYER_INDEX).getTransform());

          }

     }

     if(ps.mIsRunning){

          ps.update(fps);

     }

     return detectCollisions(gs, objects, se, ps);

}

Now the detectCollisions method is called at every single update after all the game objects have been moved.

There will be an error because we need to code the detectCollisions method. The method will return true when there has been a collision.

Add the detectCollisions method to the PhysicsEngine class as shown next:

// Collision detection will go here

private boolean detectCollisions(

     GameState mGameState,

     ArrayList<GameObject> objects,

     SoundEngine se,

     ParticleSystem ps ){

    

   boolean playerHit = false;

   for(GameObject go1 : objects) {

        if(go1.checkActive()){

             // The ist object is active

             // so worth checking

             for(GameObject go2 : objects) {

                  if(go2.checkActive()){

                    // The 2nd object is active

                    // so worth checking

                    if(RectF.intersects(

                         go1.getTransform().getCollider(),

                         go2.getTransform()

                         .getCollider())){

                      

                          // switch goes here

                       }

                  }

             }

        }

   }

   return playerHit;

}

The structure loops through each game object and tests it against every other game object with a nested pair of enhanced for loops. If both game objects (go1 and go2) are active, then a collision test is done using RectF.intersects and the getCollider method of the object's Transform (obtained via getTransform).

The call to intersects is wrapped in an if condition. If there is an intersection, then this next switch block is executed.

Notice the highlighted switch goes here comment in the previous code. Add this next code right after that:

// Switch goes here

// There has been a collision

// - but does it matter

switch (go1.getTag() + " with " + go2.getTag()){

   case "Player with Alien Laser":

        playerHit = true;

        mGameState.loseLife(se);

        break;

   case "Player with Alien":

        playerHit = true;

        mGameState.loseLife(se);

        break;

   case "Player Laser with Alien":

        mGameState.increaseScore();

        // Respawn the alien

        ps.emitParticles(

             new PointF(

                  go2.getTransform().getLocation().x,

                  go2.getTransform().getLocation().y

             )

        );

        go2.setInactive();

        go2.spawn(objects.get(Level

        .PLAYER_INDEX).getTransform());

          

        go1.setInactive();

        se.playAlienExplode();

        break;

   default:

        break;

}

The switch block constructs a String based on the tags of the two colliding game objects. The case statements test for the different collisions that matter in the game. For example, we don't test whether different aliens collide with each other or whether something collides with the background. We only test for Player with Alien Laser, Player with Alien, and Player Laser with Alien.

If the player collides with an alien laser, playerHit is set to true and the loseLife method of the GameState class is called. Notice also that a reference to SoundEngine is passed in so that the GameState class can play the required sound effect.

If the player collides with an alien, the same steps are taken as when the player collides with an alien laser.

If a player laser collides with an alien, the score is increased, a cool particle effect is started, the alien is made inactive and then respawned, the laser is set to inactive, and finally, the playAlienExplode method plays a sound effect.

We are done. Let's run the finished game.

Running the completed game

Here is the game in action. I changed my particle size to 5 and plain white by following the comments in the ParticleSystem class:

Figure 21.2 – Running the game

Figure 21.2 – Running the game

Note your high scores are saved. They will remain until you uninstall the application.

Note

I have also created a multi-level platform game named Open-World Platformer. In this the character Bob makes a second appearance and the game is a time trial where the player must get from the start point to the exit in the fastest time possible. The detailed instructions and code explanations can be found on my website at gamecodeschool.com/ http://gamecodeschool.com/android/open-world-platform-game.

Summary

In this project—as I have said before—you have achieved so much, not only because you have made a new game with neat effects such as particles, multiple enemy types, and a scrolling background but also because you have built a reusable system that can be put to work on a whole variety of games.

I hope you have enjoyed building these five games. Why not take a quick look at the final short chapter about what to do next?

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

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