You are here

Chapter 36: Refactoring - Proper states in GameEngine

I had the intention to finally let the ship be mortal, but realized that the structure in GameEngine didn't really allow me to introduce a "GameOver" state without making the code really ugly.

So this chapter is instead dedicated to a re-factoring of GameEngine, in which we remove the "surfaceReady" flag and introduce the four different states "WAITING_FOR_SURFACE", "COUNTDOWN", "RUNNING" and "GAMEOVER" and switch-case constructions in both update() and draw().

So, here is the new GameEngine:

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 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 boolean surfaceReady = false;
  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);

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

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

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

  }

  // Rewritten
  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;

    if (asteroidHandler.asteroids.isEmpty()) {
     setLevel(gameStatus.getLevel() + 1);
    }
    break;
   }

   case GAMEOVER: {
    // Nothing here yet.
    break;
   }

   }

  }

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

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

   case COUNTDOWN: {
    // No "break" here. We want the same draw:s during COUNTDOWN as
    // during RUNNING
   }

   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: {
    // Here we will add a nice little "Game Over" screen later on
    break;
   }

   }

  }

}

The effect on the game after these changes in GameEngine was absolutely none.

So, it was a successful refactoring ;-)

Comments

Dude.. it's very hard to get logged in and place a comment here.. I had to sign in twice and reload 5 times. Anyway; thanks for this tutorial. You helped me develop aPpLeZ (check it in the market ;). I also run a blog on android development. I'm not a professional developer. It's in dutch, you might want to check it out @ http://happyworx.nl/blog .
I'll keep reading your posts.. are you planning any new projects in the near future?

Thanks again!
 
Rgdz., K.

ajo's picture

I know, the site site is hosted on a very shaky connection :-/

I have no actual project ongoing as of now, but I'm sure I will have one during the summer ;-)

BR - Anders

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer