You are here

Chapter 34: Absolute...or relative?

Now when the game is beginning to be slightly playable, I noticed that it looks a bit awkward with missiles that always leave the ship at the same velocity regardless of what speed the ship has.

Remember how we create new missiles in MissileHandler if there is an "EVENT_FIRE" pending:

if (pendingEvent == GameEngine.EVENT_FIRE) {
   Missile newMissile = new Missile(bitmap, shipX, shipY, 6, shipAngle);
   missiles.add(newMissile);
   gameStatus.increaseFiredMissiles();
}

The hard coded figure "6" that we send to Missile's constructor do really look a bit alone among all variables...

The effect of having it like this, is that the ship might outrun the missiles if is has a velocity greater than 6.

In the screenshot below, the ship is moving straight upwards, while it's pointing "west-north-west". As you can see, the missiles' trajectories do indeed look a bit odd:

 

If we look at it from a "vector perspective" it is quite obvious why we get this effect.

Consider a ship moving "north-west" while pointing "north-east" and firing off a missile:

 

 

What is missed here is the momentum of the ship. The missile would have traveled along the exact same trajectory if the ship was standing still...

Instead of having the missiles' velocity hard coded to "6" at an angle that corresponds to the ship's, we should rather see it as two vectors; the ship's current trajectory and the base missile trajectory (green in the picture below). These two vectors should then be added in order to get a more appropriate velocity and angle for the missile: 

Code wise, this is not so complicated.

First of all we must edit GameEngine.update(), so that we not only pass on ship.x, ship.x and ship.angle to MissileHandler. We must also include ship.velocity and ship.direction. We might as well pass on the whole ship to make the code a bit more readable:

public void update() {
  gameStatus.update();
  if (gameStatus.getPassedLevelTime() > 0) {
   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);

  }
} 

To match this change, we also need to edit the definition of MissileHandler.update():

public void update(float screenWidth, float screenHeight, int pendingEvent,
  Ship ship, List<Asteroid> asteroids, GameStatus gameStatus) {

Now to the core part, the vector addition in MissileHandler. In MissileHandler.update, we shall edit the block:

if (pendingEvent == GameEngine.EVENT_FIRE) {
  Missile newMissile = new Missile(bitmap, shipX, shipY, 6, shipAngle);
  missiles.add(newMissile);
  gameStatus.increaseFiredMissiles();
}

and replace the "new Missile"-line with a bit more appropriate code:

if (pendingEvent == GameEngine.EVENT_FIRE) {

  // Get the Cartesian (x, y) representation of the base missile vector
  double missileBaseRadians = Math.toRadians(ship.angle - 90);
  double missileBaseVelocity = 6; // Here it is - the hard coded number "6"
  double missileBaseDX = Math.cos(missileBaseRadians) * missileBaseVelocity;
  double missileBaseDY = -Math.sin(missileBaseRadians) * missileBaseVelocity;

  // Get the Cartesian (x, y) representation of the ship's current vector
  double shipRadians = Math.toRadians(ship.getDirection() - 90);
  double shipVelocity = ship.getVelocity();
  double shipDX = Math.cos(shipRadians) * shipVelocity;
  double shipDY = -Math.sin(shipRadians) * shipVelocity;

  // The actual vector addition
  double missileDX = missileBaseDX + shipDX;
  double missileDY = missileBaseDY + shipDY;

  // Convert back to polar (velocity, direction) representation
  double velocity = Math.sqrt(missileDX * missileDX + missileDY * missileDY);
  double direction = Math.toDegrees(Math.atan2(missileDX, missileDY));

  Missile newMissile = new Missile(bitmap, ship.x, ship.y, velocity, direction);

  missiles.add(newMissile);
  gameStatus.increaseFiredMissiles();
}

One final change in GfxObject is also needed. In the "addVelocity"-method, I only updated the Cartesian "dX" and "dY" and forgot about the polar "velocity" and "direction". So we have to add it now:

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

That was the last of it!

Now the missiles are fired away in a bit more physically correct manner:

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer