Table of ContentsAdding the GraphicsInput Handling

The Actors

What your game needs now is some action. To get things moving, you need what I refer to (rather abstractly) as actors. According to my definition, an actor is an object that can move around. So for your game, the player's object (the wombat) and the cars and trucks that speed along the road are all actors. To be able to move, all of these objects must have a 2D position (x and y) in your world. Movement is just a change in this position over time.

Although having a position is common to all of these objects, there are still a few differences for your game. First, you draw each actor differently on the screen, so you need a way to track the type of actor and thus do the correct rendering. The cars and trucks also differ in that they move at varying speeds along the highway.

Many Roles

With all this in mind, take the object-oriented high road and create a hierarchy that appropriately represents what you need. As you can see in Figure 7.6, the Actor class is an abstract base class for your WombatActor and VehicleActor classes. WombatActor adds its unique rendering, and the VehicleActor class adds the concept of speed.

Figure 7.6. The action in RoadRun is implemented using a hierarchy of actors.

graphic/07fig06.gif


The Actor class represents an on-screen object in the game that will move around. To han-dle this it first needs to maintain a position (in the following source code you can see this represented using the x and y integers). In order to give it life you add a cycle method which you can call from the game loop and finally a render method so it can draw itself on the screen. Since the functionality is common to the vehicles, I've implemented code to handle getting and setting positions. The cycle and render methods however are left ready to be overridden to implement the WombatActor and VehicleActor specifics.

import javax.microedition.lcdui.Graphics;

/**
 * An abstract base class for higher level Actors. This class handles basic
 * position as well as maintaining a link to the GameScreen. A class extending
 * this needs to implement the cycle and render methods.
 */
abstract public class Actor
{
   protected GameScreen gameScreen;
   private int x, y;

   /**
    * Constructs a new Actor.
    * @param gsArg The GameScreen this Actor belongs to.
    * @param xArg The starting x position.
    * @param yArg The starting y position.
    */
   public Actor(GameScreen gsArg, int xArg, int yArg)
   {
      gameScreen = gsArg;
      x = xArg;
      y = yArg;
   }

   /**
    * Called by the main game loop to let the Actor have a life. Typically an
    * implementation should use the deltaMS (the number of milliseconds that
    * have passed since the cycle method was last called) to carry out movement
    * or other actions relative to the amount of time that has passed. The
    * default implementation in the base class does nothing.
    * @param deltaMS The number of milliseconds that have passed since the last
    * call to cycle.
    */
   public void cycle(long deltaMS) { } 
   /**
    * Called by the main game loop to draw this Actor to the screen. It is
    * intended that a child class override this implementation in order to
    * draw a representation of the actor (in a way it sees fit).
    * @param g The Graphics context upon which to draw the actor.
    */
   public void render(Graphics g) { }

   /**
    * Returns the width of the Actor (can be overriden by a child class to
    * return a different size).
    * @return Width of the actor.
    */
   public int getActorWidth()
   {
      // square by default
      return getActorHeight();
   }

   /**
    * Returns the height of the Actor (can be overriden by a child class to
    * return a different size). The default implementation (the most common
    * case) is to use a value slightly smaller than the lane height.
    * @return Height of the actor.
    */
   public int getActorHeight()
   {
      return gameScreen.getLaneHeight() - 2;
   }

   /**
    * @return The current x position of the actor.
    */
   public int getX() { return x; }

   /**
    * Sets the current x position of the actor. We also check to see if the
    * actor has moved off the edge of the screen and wrap it around.
    */
   public void setX(int newX)
   {
      x = newX; 
      // we wrap on the x-axis on a constant number to maintain an
      // equal distance between all vehicles
      if (x < -32)
         x = gameScreen.getWidth();

      if (x > gameScreen.getWidth())
         x = -getActorWidth();
   }

   /**
    * @return The current y position of the actor.
    */
   public int getY() { return y; }

   /**
    * Sets the current y position of the actor. We also check to see if the
    * actor has moved off the edge of the screen and wrap it around.
    */
   public void setY(int newY)
   {
      y = newY;

      // we don't wrap on the y-axis
      if (y < gameScreen.getLaneYPos(0))
         y = gameScreen.getLaneYPos(0);

      if (y > gameScreen.getLaneYPos(8))
         y = gameScreen.getLaneYPos(8);
   }

}

As you can see, the Actor class nicely wraps up an x and y position in your game world. As a convenience for later use, you also have each actor object keep a reference to the GameScreen object that created it. The interesting part of the Actor class is in the two base methodscycle and render. Your subclasses will override these methods to implement any cycling or rendering they need to do. (You'll see this in action a little later.)

I've included the methods for getting and setting the position of the actor. The set methods also test to make sure the object isn't off the edge of your world and reset the position if required. Take note that the x and y integer fields for position are declared private, not protected. This is intentional, to ensure that the only access to these fields is via the set and get methods.

The Wombat

The code for the next object, the WombatActor, follows. For this class you extend Actor and override the render method to draw your wombat (a green square).

import javax.microedition.lcdui.Graphics;

/**
 * The main player actor, the Wombat.
 * @author Martin J. Wells
 */
public class WombatActor extends Actor
{
   /**
    * Constructs a new WombatActor object using the specified GameScreen and
    * position.
    * @param gsArg The GameScreen this Actor is associated with.
    * @param xArg The x starting position.
    * @param yArg The y starting position.
    */
   public WombatActor(GameScreen gsArg, int xArg, int yArg)
   {
      super(gsArg, xArg, yArg);
   }

   /**
    * Renders the actor to the screen by drawing a rectangle.
    * @param graphics The graphics context to draw to.
    */
   public void render(Graphics graphics)
   {
      graphics.setColor(0x0044FF44);
      graphics.fillRect(getX(), getY(), getActorWidth(), getActorHeight());
   }

   /**
    * An overridden version of the Actor setX method in order to stop the
    * wrapping it does. You want the wombat to stop on the edge of the screen.
    * @param newX The new x position.
    */
   public void setX(int newX)
   {
      super.setX(newX); 
      // non-wrapping version for the wombat
      if (getX() < 0)
         setX(0);

      if (getX() > gameScreen.getWidth()-getActorWidth()-2)
         setX(gameScreen.getWidth() - getActorWidth()-2);
   }
}

The only significant change here is the setX method. Since you don't want your wombat to wrap to the other side when it moves beyond the edge of the screen, you override setX to put on the brakes.

Movement

The VehicleActor object is a little bit different. The purpose of the class is to implement movement for the cars and trucks that speed along the expressway. Here's the code:

/**
 * An Actor that serves as a base class for the vehicles (such as CarActor and
 * TruckActor). The common functionality is the movement in the cycle method.
 * @author Martin J. Wells
 */
abstract public class VehicleActor extends Actor
{
   protected int speed;         // speed (along x axis only)
   private long fluff = 0;

   /**
    * Constructs a new Vehicle setting the GameScreen, starting position (x, y)
    * and the speed at which it should move.
    * @param gsArg GameScreen Actor is associated with.
    * @param xArg The starting x position.
    * @param yArg The starting y position.
    * @param speedArg The speed of the vehicle.
    */
   public VehicleActor(GameScreen gsArg, int xArg, int yArg, int speedArg)
   {
      super(gsArg, xArg, yArg);
      speed = speedArg;
   }

   /** 
    * A cycle method that moves the Actor a distance relative to its current
    * speed (the value of the speed int) and the amount of time that has passed
    * since the last call to cycle (deltaMS). This code uses a fluff value in
    * order to remember values too small to handle (below the tick level).
    * @param deltaMS The number of milliseconds that have passed since the last
    * call to cycle.
    */
   public void cycle(long deltaMS)
   {
      long ticks = (deltaMS + fluff) / 100;

      // remember the bit we missed
      fluff += (deltaMS - (ticks * 100));

      // move based on our speed in pixels per ticks
      if (ticks > 0)
         setX( getX() - (int) (speed * ticks) );
   }

}

This is a good example of the purpose of a cycle method. In your VehicleActor's case, you move the actor along the x-axis. The question is how far do you move it? You'll notice that I have added a speed field to the class and initialized it using a revised constructor. The cycle method uses this field to move the actor at a rate of pixels per tenths of a second. When the GameScreen calls the cycle method for this actor, you also get the total time that has passed since the last cycle call. The amount you need to move is simply the time since the last cycle (in tenths of a second) multiplied by your movement speed.

It all sounds easy, doesn't it? There's a little problem, though. In your game, the GameScreen class will call the cycle method with a gap of about 20 milliseconds. However, because you want to move in tenths of a second, there will be no movement in any single call. Are you with me on this? It comes down to how you figure the number of tenths of a second. (In the code I refer to these as ticks.) If you take the number of milliseconds the GameScreen gives you each cycle (about 20) and divide it by 100 to get number of ticks, you'll naturally get a number that is less than zero (for example, 20 / 100 = 0.2). The problem is that you're throwing away the remainder of the calculation because you don't have any support for floating-point numbers. Therefore, the solution is to use a technique known as remainder memory, or more cutely as fluffing.

Doing this is easier than it sounds. Notice the fluff field I've added to the class. On each pass through the cycle, you use this to remember any leftovers from the calculation, and then add that on to your total the next time. After a few cycles, you'll accumulate enough fluff to have at least one full tick. The good news is that this method is safe no matter what timing you use in the game. If the frame rate is changed, you won't have to adjust your speed; it will always remain time-relative.

Cars and Trucks

Your final two classes, CarActor and TruckActor, are both derived from VehicleActor. In these you override the render method to carry out drawing specific to each type.

import javax.microedition.lcdui.Graphics;

/**
 * A VehicleActor that represents a truck (the only customization is the
 * size and the drawing code).
 * @author Martin J. Wells
 */
public class TruckActor extends VehicleActor
{
   /**
    * Constructs a new truck setting the GameScreen, starting position (x, y)
    * and the speed at which it should move.
    * @param gsArg GameScreen Actor is associated with.
    * @param xArg The starting x position.
    * @param yArg The starting y position.
    * @param speedArg The speed of the vehicle.
    */
   public TruckActor(GameScreen gsArg, int xArg, int yArg, int speedArg)
   {
      super(gsArg, xArg, yArg, speedArg);
   }

   /**
    * Get the Actor width (overriden to set the width properly).
    * @return The width of the truck.
    */
   public int getActorWidth()
   {
      return 28;
   }

   /**
    * Draws a truck using rectangles.
    * @param graphics The graphics context on which to draw the car. 
    */
   public void render(Graphics graphics)
   {
      int u = getActorHeight();

      // the front
      graphics.setColor(0x00aa9922);
      graphics.fillRect(getX(), getY(), 4, u);
      // the trailer
      graphics.setColor(0x00aa9922);
      graphics.fillRect(getX()+9, getY(), 18, u);
      // the cab
      graphics.setColor(0x00ffcc66);
      graphics.fillRect(getX() + 4, getY(), u-3, u);

   }
}
The CarActor is very similar.import javax.microedition.lcdui.Graphics;

/**
 * A VehicleActor that represents a little car (the only customization is the
 * size and the drawing code).
 * @author Martin J. Wells
 */
public class CarActor extends VehicleActor
{
   /**
    * Constructs a new car setting the GameScreen, starting position (x, y)
    * and the speed at which it should move.
    * @param gsArg GameScreen Actor is associated with.
    * @param xArg The starting x position.
    * @param yArg The starting y position.
    * @param speedArg The speed of the vehicle.
    */
   public CarActor(GameScreen gsArg, int xArg, int yArg, int speedArg)
   {
      super(gsArg, xArg, yArg, speedArg);
   }

   /**
    * Get the Actor width (overriden to set the width properly). 
    * @return The width of the car.
    */
   public int getActorWidth()
   {
      return 12;
   }

   /**
    * Draws a car using rectangles.
    * @param graphics The graphics context on which to draw the car.
    */
   public void render(Graphics graphics)
   {
      int u = getActorHeight();

      graphics.setColor(0x00aa9922);
      graphics.fillRect(getX(), getY(), u, u);
      graphics.fillRect(getX() + (u / 2) + 5, getY(), u, u);
      graphics.setColor(0x00ffcc66);
      graphics.fillRect(getX() + u - 2, getY(), u, u);
   }
}

Due to the power inherited in the base classes (VehicleActor and Actor), there's very little to do in these classes other than draw the car and truck graphics.

Bringing Them to Life

At this point you need to take a step back. Before you can use your actors, you need to set up the GameScreen class to support them. Start by adding a Vector collection to store all your actors, as well as a field for your main character, the hapless wombat.

/**
 * The main drawing and control class for the game RoadRun.
 * @author Martin J. Wells
 */
public class GameScreen extends Canvas implements Runnable
{
   private Vector actorList;
   private WombatActor wombat;
   …

You then modify the initResources method to set up the actorList vector with the starting actors. I've added a convenience method to calculate the Y pixel position of a given lane.

public int getLaneYPos(int lane)
{
   // convenience method to return the y position of a lane number
   return (lane * laneHeight)+1+topMargin;
}

/**
 * Initialize the resources for the game. Should be called to setup the game
 * for play.
 */
private void initResources()
{
   …

   actorList = new Vector();

   // add the wombat
   wombat = new WombatActor(this, getWidth() / 2, getLaneYPos(8));
   actorList.addElement( wombat );

   // add the top vehicles
   for (int i=1; i < 4; i++)
   {
      actorList.addElement(new TruckActor(this, 1, getLaneYPos(i), (i * 2) + 1));
      actorList.addElement(new CarActor(this, getWidth()/2, getLaneYPos(i), (i*2)+1));
   }

   // add the bottom vehicles
   for (int i = 5; i < 8; i++)
   {
      actorList.addElement(new TruckActor(this, 0, getLaneYPos(i), i));
      actorList.addElement(new CarActor(this, getWidth() / 2, getLaneYPos(i), i));
   }
}

To breathe life into all of your actors, you need a master cycle method in the GameScreen class.

private long lastCycleTime;
/** 
 * Handles the cycling of all the Actors in the world by calculating the
 * elapsed time and then call the cycle method on all Actors in the local
 * Vector. At the end this method also checks to see if any Actor struck
 * the Wombat.
 */
protected void cycle()
{
   if (lastCycleTime > 0)    // since cycling is time dependent we only do it
      // with a valid time
   {
      long msSinceLastCycle = System.currentTimeMillis() - lastCycleTime;

      // cycle all the actors
      for (int i = 0; i < actorList.size(); i++)
      {
         Actor a = (Actor) actorList.elementAt(i);
         a.cycle((int)msSinceLastCycle);
      }
   }
   lastCycleTime = System.currentTimeMillis();
}

Not a lot to this, really. The main processing is the looping through of all the actors within the vector and calling the cycle method (passing in the delta time since you last called). To make this work we then need to modify the run method to call cycle on every pass. Here's the complete revised run method.

/**
 * Called when thread is started. Controls the main game loop including the
 * framerate based on the timing set in MS_PER_FRAME. On every cycle it
 * calls the cycle and repaint methods.
 */
public void run()
{
   while(running)
   {
      // remember the starting time
      long cycleStartTime = System.currentTimeMillis();

      // run the cycle
      cycle();
      repaint(); 
      // update the CPS
      if (System.currentTimeMillis() - lastCPSTime > 1000)
      {
         lastCPSTime=System.currentTimeMillis();
         cps = cyclesThisSecond;
         cyclesThisSecond = 0;
      } else
         cyclesThisSecond++;

      // Here we calculate how much time has been used so far this cycle. If
      // it is less than the amount of time we should have spent then we
      // sleep a little to let the MIDlet get on with other work.
      long timeSinceStart = (cycleStartTime - System.currentTimeMillis());
      if (timeSinceStart < MS_PER_FRAME)
      {
         try
         {
            Thread.sleep(MS_PER_FRAME - timeSinceStart);
         }
         catch(java.lang.InterruptedException e)
         { }
      }
   }

   // If we've reached this point then the running boolean has been set to
   // false by something (such as a quit command) and it's time to fall back
   // to the menu system. The gameOver method displays an alert telling the
   // user their time has come and then returns to the menu.
   theMidlet.gameOver();
}

Now there's one final thing to do before your world will come alive. You need to draw these actors on the screen. To do this, modify the renderWorld method to cycle through the current actor list and call each actor's render method.

/**
 * Draws the background graphics for the game using rudimentary drawing
 * tools. Note that we draw to the offscreenBuffer graphics (osg) not the
 * screen. The offscreenBuffer is an image the size of the screen we render
 * to and then later "flip" (draw) onto the display in one go (see the paint
 * method).
 */ 
private void renderWorld()
{
   ...

   // now draw all the actors
   for (int i=0; i < actorList.size(); i++)
   {
      Actor a = (Actor)actorList.elementAt(i);
      a.render(osg);
   }
} 

Things are moving along nicely now. As you can see in Figure 7.7, if you run this code you'll see lots of cars and trucks whizzing by. Pretty cool, huh?

Figure 7.7. The Actors in action.

graphic/07fig07.gif


    Table of ContentsAdding the GraphicsInput Handling