Android Game Programming by Example
上QQ阅读APP看书,第一时间看更新

Building the enemies

Now that we have the tap controls implemented, it is time to add some enemies that the player can boost to avoid.

This is going to be much easier than when we added our player's spaceship because most of what we need is in place already. All we have to do is code a class to represent our enemy, instantiate as many enemy objects as we need, call their update methods, and then draw them.

As we will see, the update method for our enemy will be quite different to that of PlayerShip. It will need to handle things like simple AI to fly toward the player. It will also need to handle respawning when it leaves the screen.

Designing the enemy

To begin with, create a new Java class and call it EnemyShip. Add these member variables inside the class so your new class will look like this:

public class EnemyShip{
    private Bitmap bitmap;
    private int x, y;
    private int speed = 1;

    // Detect enemies leaving the screen
    private int maxX;
    private int minX;

    // Spawn enemies within screen bounds
    private int maxY;
    private int minY;
}

Now, add some getter and setter methods so that the draw method can access what it needs to draw, and where it needs to draw it. There is nothing new or unusual here:

//Getters and Setters
public Bitmap getBitmap(){
  return bitmap;
}

public int getX() {
  return x;
}

public int getY() {
  return y;
}

Spawning the enemy

Let's implement the EnemyShip constructor in full. Enter the code now, and we will then take a closer look:

// Constructor
public EnemyShip(Context context, int screenX, int screenY){
    bitmap = BitmapFactory.decodeResource 
    (context.getResources(), R.drawable.enemy);

  maxX = screenX;
  maxY = screenY;
  minX = 0;
  minY = 0;

  Random generator = new Random();
  speed = generator.nextInt(6)+10;

  x = screenX;
  y = generator.nextInt(maxY) - bitmap.getHeight();
}

The constructors' signature is exactly that of the PlayerShip class. A Context class for manipulating your Bitmap object and screenX and screenY that hold the resolution of the screen.

Just as we did with the PlayerShip class, we load up an image into Bitmap. Of course, we once again need to add an image file named enemy.png to the drawable folder of our project. There is a neat enemy graphic in the Chapter3/drawable folder of the download bundle or you can design your own. Any size between roughly 32 x 32 and 256 x 256 will suffice for the purposes of this game. Also, like those supplied, your graphics do not need to be square. We will see that our game engine is imperfect when it comes to how it looks on different screen sizes, and we will address this in the next project:

Next, we initialize maxX, maxY, minX, and minY. Although the enemies only move horizontally, we need the maxY and minY coordinates to make sure that we spawn them at a sensible height. The maxX coordinate will enable us to spawn them just off-screen horizontally.

We create a new object of type Random and generate a random number between the values of 10 and 15. These are the maximum and minimum speeds our enemies can travel at. These values are fairly arbitrary, and we might adjust them when we do some play-testing in Chapter 4, Tappy Defender – Going Home.

Note

If you are wondering how generator.nextInt(6)+10; comes up with a number between 10 and 15, it is because the 6 argument causes nextInt() to return a number between 0 and 5.

We then set the enemy ship's x coordinate to screen, which spawns it on the far left of the screen. Actually, this spawns it off screen. However, that is fine because it will then emerge in to the player's view rather than just appearing all at once.

We now generate another random number based on maxY—the height of the enemy ship bitmap (bitmap.getHeight())—to create a random but sensible y coordinate for our enemy ship to spawn at.

What we need to do now is to give our enemies life by coding their update method.

Making the enemy think

Now, we can handle the EnemyShip class's update method. For now, we just need to handle two things. First, fly the enemy toward the player's end of the screen. We need to take account of the enemy's speed and the player's speed to simulate this accurately. The reason we need to do this is because when the player boosts, he expects his speed to increase, and objects to rush toward him more quickly. However, the spaceship graphic is horizontally static.

We can increase the rate of travel of an enemy in proportion to both the enemy's static and randomly generated speed at the same time as the player's dynamically set speed (through boosting). This will give the player a sense of speeding up even though the ship graphic never moves forward.

The other issue is that the enemy ship will eventually fly off the screen, on the left-hand side. We need to detect when this happens and respawn it on the right-hand side with a new random y coordinate and a new random speed. This is just like we did in the constructor.

Finally before we get to the actual code, let's consider something. If the enemy is going to take note of and use the player's speed, it will need to be able to get it. Note that in the next block of code, the EnemyShip class's update method declaration has a parameter to receive the player's speed.

We will see how this is passed in when we add code to the TDView class's update method soon. Enter the following code for the EnemyShip class's update method to implement what we have just discussed:

public void update(int playerSpeed){
        
  // Move to the left
  x -= playerSpeed;
  x -= speed;

  //respawn when off screen
  if(x < minX-bitmap.getWidth()){
    Random generator = new Random();
    speed = generator.nextInt(10)+10;
    x = maxX;
    y = generator.nextInt(maxY) - bitmap.getHeight();
  }
}

As you can see, we first decreased the enemy's x coordinate by the player's speed then by the enemy's speed. As the player boosts, the enemy will fly at the player faster. However, if the player is not boosting then the enemy will attack at the speed that was previously and randomly generated.

// Move to the left
x -= playerSpeed;
x -= speed;

After this, we simply detected if the right-hand edge of the enemy bitmap has disappeared from the left-hand side of the screen. This is done by detecting if the EnemyShip class's x coordinate is the width of the bitmap off screen.

if(x < minX-bitmap.getWidth()){

Then we respawn the very same object to come at the player again. This appears to the player as if it is an entirely new enemy.

The last three things we must do are create a new object from EnemyShip by declaring and then initializing an object. Actually, let's make three.

Here, were we declared our player's ship in our TDView.java file, declare three enemy ships like this:

// Game objects
private PlayerShip player;
public EnemyShip enemy1;
public EnemyShip enemy2;
public EnemyShip enemy3;

Now, in the constructor of our TDView class, initialize our three new enemies:

// Initialize our player ship
player = new PlayerShip(context, x, y);
enemy1 = new EnemyShip(context, x, y);
enemy2 = new EnemyShip(context, x, y);
enemy3 = new EnemyShip(context, x, y);

In the update method of our TDView class, we call each of the new object's update methods in turn. Here, we also see how we pass in the player's speed to each of our enemies so they can use it in their update methods to adjust speed accordingly.

// Update the player
player.update();
// Update the enemies
enemy1.update(player.getSpeed());
enemy2.update(player.getSpeed());
enemy3.update(player.getSpeed());

Finally, in the TDView class's draw method, we draw our new enemies to the screen.

// Draw the player
canvas.drawBitmap
    (player.getBitmap(), player.getX(), player.getY(), paint);

canvas.drawBitmap
 (enemy1.getBitmap(), 
 enemy1.getX(), 
 enemy1.getY(), paint);

canvas.drawBitmap
 (enemy2.getBitmap(), 
 enemy2.getX(), 
 enemy2.getY(), paint);

canvas.drawBitmap
 (enemy3.getBitmap(), 
 enemy3.getX(), 
 enemy3.getY(), paint);

You can run the game and give this a try now.

The first and most obvious problem is that the player and the enemies fly right through each other. We will solve this problem later in this chapter, in the Things that go bump – collision detection section. But right now, we can make our player's sense of immersion better by drawing a star/space dust field as a background.