Table of ContentsMoving at an AngleCollision Detection

Advanced Motion

In the previous example, you had your ship flying around quite nicely. However, that example used a very simple flight model. The ship had a constant speed, and the direction it faced was the direction it moved. In Star Assault, you're going to make things a little more interesting by using a more complex flight model.

If you recall games such as Asteroids and Subspace, you'll remember that the flight mechanics were quite different than the previous example. The ship would move in a particular direction until it was pushed in another by some force (such as the ship's thrusters). To understand the mechanics of how this works, you need to look at a few physics concepts, namely velocity and acceleration.

Velocity

Velocity is simply the speed at which your ship is moving. In the previous example, the ship's velocity was the result of the calculations:

shipXFP = MathFP.add( shipXFP, MathFP.cos(dirRadians) );
shipYFP = MathFP.add( shipYFP, -MathFP.sin(dirRadians) );

The velocity was the value returned by the cosine and sine functions. As you changed direction, the velocity instantly adjusted to a new value relative to this new direction. The code then adjusted the ship's position according to this velocity.

Does this feel wrong to you? I know it did to me at first. Because velocity is really just the speed at which something is traveling (such as a speedboat cruising along at 80 mph), how can adjusting the velocity change the direction your ship (or speedboat) is going? If you change the velocity, shouldn't the vessel just speed up or slow down?

The difference is that you're dealing with velocity in two dimensions here. You can adjust the speed of movement in both the x and y directions, thus your speedboat can not only speed up and slow down going forward and backward, but it can also do the same going sideways! (I want one of these boats.)

Your ship floating through space is a much more reasonable example. You can easily imagine it flying along at a constant velocity. You can also see how the direction the ship is facing doesn't necessarily have anything to do with this velocity.

In the diagram in Figure 10.7, your ship is moving at a velocity of 0.7 on x and 0.7 on y, (equivalent to 315 degrees) even though it is facing toward 0 degrees. The only reason the ship changes direction (velocity) when you change the angle is because the code reacts to it instantly (hence the name instant velocity). The important point is that there's no difference between the direction the ship is floating and its velocity; they're the same thing.

Figure 10.7. Movement of a ship in a direction different to the one it is facing

graphic/10fig07.gif


However, for your space flight model, you don't want instant velocity; what you want is for the ship to float along until thrusters push it in a new direction. To simulate that, you'll need to adjust the ship's velocity using acceleration.

Acceleration

Acceleration is simply a measure of a change in velocity. If your ship is currently moving at a certain velocity, you can change that by applying acceleration to it. Like velocity, acceleration has both an x and a y component when you're dealing in two dimensions. Therefore, by applying acceleration to your ship's velocity, you can vary the direction in which it's traveling.

As you can see in Figure 10.8, the ship is traveling at a velocity of (x, y) 0.7, 0.7 (315 degrees) while facing 0 degrees. Assuming the thrusters push the ship in the direction it's facing (by thrusting out the back); applying that thrust will adjust the velocity toward the direction it's pointing. This way, the ship's velocity and the direction it's floating are pushed by the thrusters.

Figure 10.8. By applying acceleration to existing velocity an object moves "toward" a new direction.

graphic/10fig08.gif


NOTE

Tip

You can simulate your floating ship slowing down (due to the effects of friction) simply by decreasing the overall magnitude of its velocity over time. Of course, this won't have much of an effect if the thrusters are on full blast all the time. (It will never have a chance to slow down.) You could get around this by making thrust only apply when a key (such as the up arrow) is pressed.

Free Flight

I know this is all rather theoretical, so let's put it all together into some code. The changes are all in the GameScreen inner class.

NOTE

Tip

The complete code, including a working JAD/JAR for the advanced movement can be found on the CD under the Chapter 10 source code directory "Advanced Direction Movement".

/**
 * A Canvas used to draw and move a little ship sprite.
 */
class GameScreen extends Canvas
{
   // ship properties
   private int shipXFP;          // x position (as a MathFP)
   private int shipYFP;          // y position (as a MathFP)
   private int direction;        // current direction in degrees 
   private int xVelFP;          // x velocity (as a MathFP)
   private int yVelFP;          // y velocity (as a MathFP)

   private int maxVelFP = MathFP.toFP("2");
   private int thrustFP = MathFP.toFP("0.2");

   ...

   /**
    * Moves the ship based on the angle it's facing using directional
    * acceleration and velocity.
    */
   public void cycle()
   {
      // move the ship according to its current direction (in radians)
      int dirRadians = MathFP.div(MathFP.toFP(direction), FP_DEGREES_PER_RAD);

This code is where you can see the main difference between instant velocity and applying acceleration. Based on a thrust value the code calculates by how much we'll change the velocity (the acceleration).

      // Calculate the acceleration for this cycle by multiplying the direction
      // by the thrust on both x and y.
      int xAccFP = MathFP.mul(thrustFP, MathFP.cos(dirRadians));
      int yAccFP = MathFP.mul(thrustFP, -MathFP.sin(dirRadians));

You then just add this acceleration value to the current velocity.

      // Increase the velocity by the amount of acceleration in this cycle.
      xVelFP = MathFP.add(xVelFP, xAccFP);
      yVelFP = MathFP.add(yVelFP, yAccFP);

Because velocity is now increasing through acceleration you need to add some code to put a cap on it. Notice that I'm capping both positive and negative velocity. Without this, the ship would soon be moving at breakneck speed.

      // Cap our velocity to a controllable level.
      if (xVelFP > maxVelFP) xVelFP = maxVelFP;
      else if (xVelFP < -maxVelFP) xVelFP = -maxVelFP;
      if (yVelFP > maxVelFP) yVelFP = maxVelFP;
      else if (yVelFP < -maxVelFP) yVelFP = -maxVelFP;

Everything else is basically the same as the simple movement system

      // Move the ship according to its current velocity.
      shipXFP = MathFP.add(shipXFP, xVelFP); 
      shipYFP = MathFP.add(shipYFP, yVelFP);

      // Check our position and wrap around if we have to.
      if (MathFP.toInt(shipXFP) < 0)
         shipXFP = MathFP.toFP(myCanvas.getWidth() - 1);
      if (MathFP.toInt(shipXFP) > myCanvas.getWidth())
         shipXFP = MathFP.toFP(0);
      if (MathFP.toInt(shipYFP) < 0)
         shipYFP = MathFP.toFP(myCanvas.getHeight() - 1);
      if (MathFP.toInt(shipYFP) > myCanvas.getHeight())
         shipYFP = MathFP.toFP(0);
   }

   ...
}

Thankfully, not too much has changed. The major addition is to the cycle method, where you'll notice you now calculate acceleration based on a constant thrust value (an arbitrary number representing how much force is coming out of the back of the ship) that is multiplied by the directional component of the x and y-axes.

Flying the ship around using this flight model is actually quite a lot of fun. You can zoom around and do slide-outs all over the screen. It's amazing how such a small amount of code produces a cool physics model like this. Check out the example on the CD and if you'd like, adjust the thrust and maximum velocity to see the effects. If you reduce thrust especially you'll see how the Ship slides a lot more (due to the reduced effects of lower acceleration).

Next I'll show you how to make things even more interesting and take a look at using your velocity in a different way by making the ship bounce off the side of the screen when it hits. Sound hard? Actually, it's easier than you might think.

Adding Bounce

In the current example code, you detect whether the ship has moved beyond the screen by checking if its position is beyond any of the edges.

If you detect this has happened, you then react by changing the position of the ship so it will appear on the other side of the screen (wrapping around the world). If you want the ship to bounce off the edge instead, you need to adjust its velocity. The question is, what type of adjustment do you make?

Bouncing, believe it or not, is actually very similar to jumping. For example, suppose you're walking along a footpath and you decide to jump. What happens? Well, before you jumped you only had horizontal velocity (think of it like the x-axis). When you leap into the air, you effectively accelerate the vertical component of your velocity. This means you move both vertically and horizontally; hence your jump covers a certain amount of distance. While you're in the air, another force comes into playgravity. This force slowly pushes down the vertical velocity component. The combination of jumping acceleration and the pull of gravity creates the arc of movement when you jump (also known as a parabola). But we're getting ahead of ourselves….

Think about how this works for bouncing as well. If I have a ball and I throw it straight down, it has a certain vertical velocity. Assuming it's not a squash ball, it will rebound against the floor and then fly back up. When it impacts the floor, the vertical velocity is inverted. Note that I said the vertical velocity is changed, not the horizontal. Figure 10.9 illustrates what I mean.

Figure 10.9. Bouncing an object is carried out by reversing its y-axis direction.

graphic/10fig09.gif


If you throw the ball forward and down at the same time, it has both horizontal and vertical velocity. Thus when it hits the floor, the angle of the surface causes a reaction only in the vertical component. This causes the ball to bounce (the vertical component will reverse), but the horizontal velocity is not affected so it will continue in that direction (see Figure 10.10).

Figure 10.10. A bouncing ball with both horizontal and vertical motion

graphic/10fig10.gif


All right, so now you know that to bounce, all you need to do is figure out which component of your velocity to reverse. This is actually pretty easy because you're dealing with flat surfaces, so the component you reverse is the direction perpendicular to the plane you hit. Did I lose you? Actually, it's simpler than it sounds. In the diagram in Figure 10.11, you can see an example of perpendicular angles.

Figure 10.11. An example of a perpendicular direction (angle)

graphic/10fig11.gif


I want you to think about what's actually happening to the ball when it hits the floor. If you throw it straight down and it hits a flat surface, why does this only affect the vertical velocity? The answer is in the force created by that horizontal surface. Because it's flat, it cancels out (or in this case, reverses) the entire vertical component because the surface blocks anything from moving further downward. There's nothing blocking the horizontal movement, though, so it remains unaffected.

All right, so you know that if the ball hits a horizontal floor you reverse the vertical velocity, and if it hits a vertical wall you invert the horizontal velocity. The same thing applies to your ship; when you detect that it hits either side, you reverse the x-axis velocity. If it hits the top or bottom, you reverse the y component. You can add code to the cycle method of the previous example to do this. Notice also that I'm remembering the position before we collided so that we can back out of the collision state. If this wasn't there you would find the object "sticks" forever in a collision state when it hits the edge (forever bouncing against the wall)we'll see a more detailed example of this a little later in this chapter.

The first step is to remember the Ship's position before you move it. This is used later to back out of a collision if it occurs.

      // Save the original position (so if it collided we can back out of
      // this movement)
      int origXFP = shipXFP;
      int origYFP = shipYFP;

Next you can move the Ship using the same acceleration code from previous examples.

      // Calculate the acceleration for this cycle by multiplying the direction
      // by the thrust on both x and y.
      int xAccFP = MathFP.mul(thrustFP, MathFP.cos(dirRadians));
      int yAccFP = MathFP.mul(thrustFP, -MathFP.sin(dirRadians)); 
      // Increase the velocity by the amount of acceleration in this cycle.
      xVelFP = MathFP.add(xVelFP, xAccFP);
      yVelFP = MathFP.add(yVelFP, yAccFP);

      // Cap our velocity to a controllable level.
      if (xVelFP > maxVelFP) xVelFP = maxVelFP;
      else if (xVelFP < -maxVelFP) xVelFP = -maxVelFP;
      if (yVelFP > maxVelFP) yVelFP = maxVelFP;
      else if (yVelFP < -maxVelFP) yVelFP = -maxVelFP;

Everything else is basically the same as the simple movement system.

      // Move the ship according to its current velocity.
      shipXFP = MathFP.add(shipXFP, xVelFP);
      shipYFP = MathFP.add(shipYFP, yVelFP);

      // Check our position and if we're hitting an edge reverse a velocity
      // component.
      if (MathFP.toInt(shipXFP) < 1 ||
          MathFP.toInt(shipXFP)+SHIP_FRAME_WIDTH >= myCanvas.getWidth())
      {
         // back out of the collision position
         shipXFP = origXFP;
         xVelFP = MathFP.mul(xVelFP, MathFP.toFP("-1.0"));
      }
      if (MathFP.toInt(shipYFP) < 1 ||
          MathFP.toInt(shipYFP)+SHIP_FRAME_HEIGHT >= myCanvas.getHeight())
      {
         // back out of the collision position
         shipYFP = origYFP;
         yVelFP = MathFP.mul(yVelFP, MathFP.toFP("-1.0"));
      }

All I'm doing in this code is detecting the impact and then reversing a velocity component by multiplying it by 1. It's simple, but if you play with the demo it's also lots of fun. Feel free to play around with values less than 1 to reduce the level of bounce or greater than 1 to see much bigger bounces. I use a value of around 0.8 to let the bounce slowly fade after a few impacts.

Because Star Assault only deals with perfect horizontal and vertical surfaces, this about covers your requirements for bouncing objects. However, if you're developing a game in which you have surfaces at other angles, you will need to adjust the velocity components based on the relative parts of the x and y-axes affected by the angle of the impact.

    Table of ContentsMoving at an AngleCollision Detection