You are here

Chapter 25: Making our ship controllable

Finally! Time to really introduce some interaction in our game.

If you are building a more complex game, where each and every event is important and lagging is unacceptable, the method I describe below is not the most optimal one. Robert Green has written an excellent article on how to use Input Pipelines, that are a bit more accurate (

But for a game like this, this way will be sufficient.

We will introduce changes through the whole stack, from GameSurface, through, GameEngine and Ship, all the way down to GfXObject.

Let's start with GfxObject. We need a new method in order to add velocity, and not just set it to a certain value:

public void addVelocity(double increment, int angle) {
  double radians = Math.toRadians(angle - 90);
  dX += Math.cos(radians) * increment;
  dY += Math.sin(radians) * increment;

Then we have modify the update method in Ship, so that it can receive the current event and rotate the ship or change it's velocity in the direction it's pointing:

public void update(float screenWidth, float screenHeight,
   List<Asteroid> asteroids, int event) {
  float dX;
  float dY;
  float distance;
  switch (event) {
  case GameEngine.EVENT_NONE: {
  case GameEngine.EVENT_LEFT: {
   angle -= 10;
   if (Math.abs(angle) >= 360)
    angle = 0;
  case GameEngine.EVENT_RIGHT: {
   angle += 10;
   if (Math.abs(angle) >= 360)
    angle = 0;
  case GameEngine.EVENT_THRUST: {
   addVelocity(0.5, angle);
  move(screenWidth, screenHeight);
  for (Asteroid asteroid : asteroids) {
   dX = Math.abs(x - asteroid.x);
   dY = Math.abs(y - asteroid.y);
   distance = (float) Math.sqrt(dX * dX + dY * dY);
   if (distance <= (bitmap.getWidth() / 2 + asteroid.bitmap.getWidth() / 2)) {
    collided = true;

In GameEngine we will add 5 constants and 1 variable:

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;

and add "pendingEvent" when we call ship.update:

public void update() {
  asteroidHandler.update(screenWidth, screenHeight);
  ship.update(screenWidth, screenHeight, asteroidHandler.asteroids, pendingEvent);
  pendingEvent = EVENT_NONE;

Finally, we remove three of the log-printouts (left, right and thrust) from GameSurface and send the events to GameEngine instead (I also changed the threshold from 20 to 10 for the events to trigger):

case MotionEvent.ACTION_MOVE: {
  final int pointerIndex = motionEvent
  final float x = motionEvent.getX(pointerIndex);
  final float y = motionEvent.getY(pointerIndex);
  final float dX = x - lastTouchX;
  final float dY = y - lastTouchY;

  lastTouchX = x;
  lastTouchY = y;

  if (dY < -10) {
   gameEngine.pendingEvent = GameEngine.EVENT_THRUST;
  if (dX > 10) {
   gameEngine.pendingEvent = GameEngine.EVENT_RIGHT;
  if (dX < -10) {
   gameEngine.pendingEvent = GameEngine.EVENT_LEFT;

That's it!

Keep in mind though, that this way of passing events, by directly setting a public member variable (pendingEvent) in another class without the use of "synchronized", is rather ugly. If our game was a bit more complex, we would have added a public Object in GameEngine, and wrapped all code reading or setting pendingEvent in a "synchronized(Object)" block, or used Robert Green's method.

But this will do perfectly ok for now.

Launch the app and try out a few sweeps and thrusts:


Next chapter will introduce the missiles!


Requirements implemented in this chapter

✔ 18. The ship shall rotate clockwise/counter clockwise when you "swipe" right/left on the screen, but not change its velocity in any way.

✔ 19. The ship shall increase its velocity in its nose's direction when you "swipe" up on the screen.

✔ 20. The ship's velocity shall not decrease automatically. The player will have to rotate 180° and accelerate in order to brake.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer