You are here

Chapter 32: Introducing levels in the game

One key factor when making a game with the concept of "levels", is of course to make the next level a bit more difficult than the previous one.

In JustRoids, we accomplished that when we wrote the AsteroidHandler. Remember how each asteroid's velocity was not random:

 for (int i = 0; i < level; i++) {
   x = 240 * random.nextFloat();
   y = 50 * random.nextFloat();
   velocity = 0.2 + 0.5 * i;
   direction = 180 - 45 + 90 * random.nextFloat();
   rotation = -1 - random.nextInt(3);
   asteroids.add(new Asteroid(asteroidBitmaps[Asteroid.WHOLE],
     asteroidBitmapsHighlighted[Asteroid.WHOLE], Asteroid.WHOLE,
     x, y, velocity, direction, rotation));
}

We create as many asteroids as the number of the current level, and each one is faster then the previous. On level 1, there will be only one asteroid, slowly moving at 0.2 points per frame, but on level 10 the fastest asteroid will sweep across the screen at 25 times that speed. It should be sufficient to make the game a bit tricky as the level increases...

I most of this article, we've had the level hard coded to "5" in order to have enough asteroids on the screen for proper testing, but now it's time to make the game start on level 1 and level up each time all asteroids are gone.

There are only two files we have to modify for this. First we add a new "setLevel" method in GameEngine and move a few calls from Init to the new method:

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;

  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();
   setLevel(1);
   // asteroidHandler = new AsteroidHandler(resources, gameStatus.getLevel());
   // missileHandler = new MissileHandler(resources);
   // ship = new Ship(resources, 120, 280, 0.1, 0, 0);

   setSurfaceDimensions(240, 320);

  }

  private void setLevel(int level) {
   gameStatus.setLevel(level);
   asteroidHandler = new AsteroidHandler(resources, level);
   missileHandler = new MissileHandler(resources);
   ship = new Ship(resources, 120, 280, 0.1, 0, 0);
  }

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

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

  public void update() {
   asteroidHandler.update(screenWidth, screenHeight);
   ship.update(screenWidth, screenHeight, asteroidHandler.asteroids,
     pendingEvent, gameStatus);
   missileHandler.update(screenWidth, screenHeight, pendingEvent, ship.x,
     ship.y, ship.angle, asteroidHandler.asteroids, gameStatus);
   pendingEvent = EVENT_NONE;

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

  public void draw(Canvas canvas) {
   canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), blackPaint);
   asteroidHandler.draw(canvas);
   missileHandler.draw(canvas);
   ship.draw(canvas);
   gameStatus.draw(canvas, screenHeight, screenWidth);
  }

}

Then we do a similar change in GameStatus:

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 firedMissiles;
  private long levelStartTime;

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

   // level = 1;
   // thrusts = 0;
   // firedMissiles = 0;
   // levelStartTime = System.nanoTime();
  }

  public int getLevel() {
   return level;
  }

  public void setLevel(int level) {
   this.level = level;
   thrusts = 0;
   firedMissiles = 0;
   levelStartTime = System.nanoTime();
  }

  public void increaseThrusts() {
   thrusts++;
  }

  public void increaseFiredMissiles() {
   firedMissiles++;
  }

  public void draw(Canvas canvas, float screenHeight, float screenWidth) {
   long passedLevelTime = (System.nanoTime() - levelStartTime) / 1000000000L;

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

}

That's all that is needed in order to make the game a bit more exiting:

The ship is still immortal, though. That's the topic for an upcoming chapter.

I also realized that the missiles' velocities are relative to the screen and not to the ship. It gives an interesting effect if the ship is moving fast... I guess that will the topic for next chapter.

Mortality or not - we managed to implement no less than 7(!) requirements by introducing the "level" concept:

 

Requirements implemented in this chapter

✔ 10. The game starts at Level 1.

✔ 11. There shall be no upper limit on Level.

✔ 12. When a level starts, the ship shall be in the center of the screen.

✔ 13. When a level starts, the ship should have the velocity 0 and be pointing upwards.

✔ 14. When a level starts, there shall be one "full-size" asteroid per level (1 asteroid on level 1, 2 on level 2 and so on...) on the outer parts of the screen.

✔ 15. When a level starts, the asteroids shall have random velocity and direction.

✔ 29. When all asteroids are destroyed, the level is completed. 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer