You are here

Chapter 37: Game Over

Well, it's finally time for the most important part in every arcade game: the words "GAME OVER" in capital letter all over the screen.

Most bits and pieces needed for this is are already in place, but we have to make some final changes in two files; GameEngine and GameStatus.

Let's start with GameStatus, where we add a new method to draw the actual "Game Over" screen. We also make two minor fixes while we have the file open:

package com.ajomannen.justroids;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

public class GameStatus {
  private Paint textPaint;
  private int level;
  private int thrusts;
  private int thrustsLastLevel;
  private int firedMissiles;
  private int firedMissilesLastLevel;
  private long levelStartTime;
  private long passedLevelTime;
  private long passedLevelTimeLastLevel;

  private static final long GRACE_PERIOD = 5000000000L;

  public GameStatus() {
   textPaint = new Paint();
   textPaint.setColor(Color.LTGRAY);
   textPaint.setTextSize(25);
  }

  public int getLevel() {
   return level;
  }

  public void setLevel(int level) {
   this.level = level;

   thrustsLastLevel = thrusts;
   firedMissilesLastLevel = firedMissiles;
   passedLevelTimeLastLevel = passedLevelTime;

   thrusts = 0;
   firedMissiles = 0;
   levelStartTime = System.nanoTime() + GRACE_PERIOD;
  }

  public long getPassedLevelTime() {
   return passedLevelTime;
  }

  public void increaseThrusts() {
   thrusts++;
  }

  public void increaseFiredMissiles() {
   firedMissiles++;
  }

  public void update() {
   passedLevelTime = (System.nanoTime() - levelStartTime) / 1000000000L;
  }

  public void draw(Canvas canvas, float screenHeight, float screenWidth) {
   if (passedLevelTime > 0) {
    textPaint.setTextAlign(Paint.Align.LEFT);
    canvas.drawText("Level: " + level, 1, 19, textPaint);
    canvas.drawText("Thrusts: " + thrusts, 1, screenHeight - 2,
      textPaint);

    textPaint.setTextAlign(Paint.Align.RIGHT);
    canvas.drawText("Time: " + passedLevelTime + "s", screenWidth - 1,
      19, textPaint);
    canvas.drawText("Missiles: " + firedMissiles, screenWidth - 1,
      screenHeight - 2, textPaint);
   } else {
    textPaint.setTextAlign(Paint.Align.CENTER);
    if (level > 1) {
     canvas.drawText("Level " + (level - 1) + " completed! ",
       screenWidth / 2, 29, textPaint);
     canvas.drawText(" Time: " + passedLevelTimeLastLevel + "s",
       screenWidth / 2, 60, textPaint);
     canvas.drawText("Thrusts: " + thrustsLastLevel,
       screenWidth / 2, 90, textPaint);
     canvas.drawText("Missiles: " + firedMissilesLastLevel,
       screenWidth / 2, 120, textPaint);
    }
    // textPaint.setColor(Color.LTGRAY); // This line was redundant and can be removed
    textPaint.setTextSize(30);
    canvas.drawText("Level " + level + " starts in "
      + (-passedLevelTime) + "s", screenWidth / 2,
      (float) (screenHeight * 0.75), textPaint); // 75% scales better than a fixed value
    textPaint.setTextSize(25);
   }
  }

  // New method
  public void drawGameOverScreen(Canvas canvas, float screenHeight,
    float screenWidth) {
   textPaint.setTextAlign(Paint.Align.CENTER);
   textPaint.setTextSize(50);
   canvas.drawText("GAME OVER", screenWidth / 2, (float) (screenHeight * 0.50),
     textPaint);
   textPaint.setTextSize(25);
   canvas.drawText("You reached level " + level, screenWidth / 2, (float) (screenHeight * 0.60),
     textPaint);
   canvas.drawText("Press 'Back' for Main Menu", screenWidth / 2, (float) (screenHeight * 0.85),
     textPaint);
  }
}

Then it's time for GameEngine, where we add draw calls during GAMEOVER and rewrite the COUNTDOWN part also to make it look a bit better:

package com.ajomannen.justroids;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;

public class GameEngine {

  Context context;
  Resources resources;

  public float screenWidth;
  public float screenHeight;
  private Paint blackPaint;
  private Paint semiTransparentPaint;

  private AsteroidHandler asteroidHandler;
  private MissileHandler missileHandler;
  private Ship ship;
  private GameStatus gameStatus;

  public static final int EVENT_NONE = 0;
  public static final int EVENT_LEFT = 1;
  public static final int EVENT_RIGHT = 2;
  public static final int EVENT_THRUST = 3;
  public static final int EVENT_FIRE = 4;
  public int pendingEvent = EVENT_NONE;

  private static final int WAITING_FOR_SURFACE = 0;
  private static final int COUNTDOWN = 1;
  private static final int RUNNING = 2;
  private static final int GAMEOVER = 3;

  private int mode = WAITING_FOR_SURFACE;

  public void Init(Context context) {
   this.context = context;
   resources = context.getResources();

   blackPaint = new Paint();
   blackPaint.setColor(Color.BLACK);
   blackPaint.setStyle(Style.FILL);

   // New Paint for "shading effects"
   semiTransparentPaint = new Paint();
   semiTransparentPaint.setColor(Color.BLACK);
   semiTransparentPaint.setStyle(Style.FILL);
   semiTransparentPaint.setAlpha(200);

   gameStatus = new GameStatus();
  }

  private void setLevel(int level) {
   gameStatus.setLevel(level);
   asteroidHandler = new AsteroidHandler(resources, level);
   missileHandler = new MissileHandler(resources);
   ship = new Ship(resources, screenWidth / 2, screenHeight - 30, 0.1, 0,
     0);
   mode = COUNTDOWN;
  }

  public void onDestroy() {
   try {
   } catch (Exception e) {
   }
  }

  public void setSurfaceDimensions(int width, int height) {
   screenWidth = width;
   screenHeight = height;

   if (mode == WAITING_FOR_SURFACE) {
    setLevel(1);
   }

  }

  public void update() {
   switch (mode) {

   case WAITING_FOR_SURFACE: {
    // Don't update anything. Just wait until setSurfaceDimensions() is
    // called.
    break;
   }

   case COUNTDOWN: {
    // Just update gameStatus so the timer can tick up to zero
    gameStatus.update();
    if (gameStatus.getPassedLevelTime() > 0) {
     pendingEvent = EVENT_NONE;
     mode = RUNNING;
    }
    break;
   }

   case RUNNING: {
    // Standard mode - update all objects
    gameStatus.update();
    asteroidHandler.update(screenWidth, screenHeight);
    ship.update(screenWidth, screenHeight, asteroidHandler.asteroids,
      pendingEvent, gameStatus);
    missileHandler.update(screenWidth, screenHeight, pendingEvent,
      ship, asteroidHandler.asteroids, gameStatus);
    pendingEvent = EVENT_NONE;

    // Up one level if all asteroids are gone
    if (asteroidHandler.asteroids.isEmpty()) {
     setLevel(gameStatus.getLevel() + 1);
    }

    // Game Over if the ship has collided with an asteroid
    if (ship.isCollided()) {
     mode = GAMEOVER;
    }
    break;
   }

   case GAMEOVER: {
    // Nothing needed here
    break;
   }

   }

  }

  public void draw(Canvas canvas) {
   switch (mode) {

   case WAITING_FOR_SURFACE: {
    // Don't draw anything before the surface is ready
    break;
   }

   case COUNTDOWN: {
    // We want to draw all objects, but everything except the counter
    // should be shaded
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
      blackPaint);
    asteroidHandler.draw(canvas);
    missileHandler.draw(canvas);
    ship.draw(canvas);
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
      semiTransparentPaint);
    gameStatus.draw(canvas, screenHeight, screenWidth);
    break;
   }

   case RUNNING: {
    // Standard mode - draw all objects
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
      blackPaint);
    asteroidHandler.draw(canvas);
    missileHandler.draw(canvas);
    ship.draw(canvas);
    gameStatus.draw(canvas, screenHeight, screenWidth);
    break;
   }

   case GAMEOVER: {
    // Draw everything plus a game-over text
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
      blackPaint);
    asteroidHandler.draw(canvas);
    missileHandler.draw(canvas);
    ship.draw(canvas);
    gameStatus.draw(canvas, screenHeight, screenWidth);
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
      semiTransparentPaint);
    gameStatus.drawGameOverScreen(canvas, screenHeight, screenWidth);
    break;
   }

   }

  }

}

With these latest changes and additions, the game look more or less complete!

 

If I don't stumble upon any new issue, we should be ready to choose an icon for the app, put the ads back and then publish it on the Marketplace.

Exciting!

Requirements implemented in this chapter

✔ 27. If the ship collides with an asteroid, the game is over.

✔ 28. When the game is over, some sort of summery shall be shown along with a link back to the Main Menu.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer