As mentioned before, the class SpaceBlasterGame extends ArcadeGame, which in turn extends the Android layout LinearLayout. In this way, we can define a game thread to update the state of the game and simply draw sprites in the onDraw method of LinearLayout, thus gaining a finer control over the drawing process.
SpaceBlasterGame performs the drawing, and it has all the game logic. This class is relatively complex compared to the others, but before we take a look at it, let’s see how the game thread is handled by
ArcadeGame.
ArcadeGame is the abstract base class for SpaceBlasterGame, and it deals with the game loop. This loop is infinite and invalidates the view to force a redraw of the display (see Listing 3-3).
Listing 3-3. Abstract Class ArcadeGame package ch03.game.sb; import java.util.Timer; import java.util.TimerTask; import ch03.common.AudioClip; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.AttributeSet; import android.widget.LinearLayout; /**
62
* Base class for all games. Extends {@link LinearLayout} and uses a * {@link TimerTask} to invalidate the view
*
* @author V. Silva *
*/
public abstract class ArcadeGame extends LinearLayout { // App context
private Context mContex;
// Update timer used to invalidate the view private Timer mUpdateTimer;
// Timer period
private long mPeriod = 1000; /**
* C *
* @param context */
public ArcadeGame(Context context) { super(context);
mContex = context; }
public ArcadeGame(Context context, AttributeSet attrs) { super(context, attrs); mContex = context; } /** * Fires on layout */
protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b);
try {
// Init game initialize(); /**
* start update task. Which will fire onDraw in the future */ startUpdateTimer(); } catch (Exception e) { // bug e.printStackTrace(); } } /**
63
* Set the update period *
* @param period */
public void setUpdatePeriod(long period) { mPeriod = period;
} /**
* A timer is used to move the sprite around */
protected void startUpdateTimer() { mUpdateTimer = new Timer();
mUpdateTimer.schedule(new UpdateTask(), 0, mPeriod); }
protected void stopUpdateTimer() { if (mUpdateTimer != null) { mUpdateTimer.cancel(); }
}
public Context getContex() { return mContex; } /** * Load an image * * @param id * @return */
protected Bitmap getImage(int id) {
return BitmapFactory.decodeResource(mContex.getResources(), id); } /** * Get AudioClip * * @param id * @return */
protected AudioClip getAudioClip(int id) { return new AudioClip(mContex, id); }
/**
* Overload this to update the sprites on the game */
64
/**
* Overload to initialize the game */
abstract protected void initialize(); abstract protected boolean gameOver(); abstract protected long getScore(); /**
* Canvas update task *
* @author vsilva *
*/
private class UpdateTask extends TimerTask { @Override
public void run() { updatePhysics(); /**
* Cause an invalidate to happen on a subsequent cycle * through the event loop. Use this to invalidate the View * from a non-UI thread. onDraw will be called sometime * in the future. */ postInvalidate(); } } /**
* Halt game. Stops the update task. Called by a parent activity to halt *
*/
public void halt() { stopUpdateTimer(); }
/**
* Resume Game */
public void resume() { initialize(); startUpdateTimer(); }
}
Any user-defined layout must define constructors with an Android Context and set of attributes for being able to be processed properly by the system:
65
public ArcadeGame(Context context) { super(context);
mContex = context; }
public ArcadeGame(Context context, AttributeSet attrs) { super(context, attrs);
mContex = context; }
Note that ArcadeGame extends LinearLayout and sends its arguments to the parent via
super(context) and super(context, attrs). This class also overloads the parent’s method onLayout(), which fires when the view should assign a size and position to each of its children:
/**
* Fires on layout */
protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b);
try {
// Init game initialize(); /**
* start update task. Which will fire onDraw in the future */ startUpdateTimer(); } catch (Exception e) { // bug e.printStackTrace(); } }
onLayout is in charge of initializing the game and starting the update timer. Note that initialize()
is an abstract method that must be overloaded by the child class (SpaceBlasterGame) to perform initialization. The update timer, on the other hand, will be defined within AbstractGame. To define a game loop, we can use a simple TimerTask that will update itself using a period, as shown in Listing 3-4.
Listing 3-4. Defining a Game Loop Using a Timer Task // Timer period
private long mPeriod = 1000; /**
* Canvas update task *
* @author vsilva *
*/
private class UpdateTask extends TimerTask { @Override
66
public void run() { updatePhysics(); /**
* Cause an invalidate to happen on a subsequent cycle * through the event loop. Use this to invalidate the View * from a non-UI thread. onDraw will be called sometime * in the future. */ postInvalidate(); } } /**
* A timer is used to move the sprite around */
protected void startUpdateTimer() { mUpdateTimer = new Timer();
mUpdateTimer.schedule(new UpdateTask(), 0, mPeriod); }
protected void stopUpdateTimer() { if (mUpdateTimer != null) { mUpdateTimer.cancel(); }
} /**
* Set the update period *
* @param period */
public void setUpdatePeriod(long period) { mPeriod = period;
}
When the layout initializes, the onLayout() method will fire and call initialize() and
startUpdateTimer(). These methods will start the Timertask:run() method, which updates the physics of the child class and invalidates the view by calling postInvalidate(). Invalidating the view will, in turn, tell the system UI thread to refresh the display. This cycle will repeat itself until the value of the update period (mPeriod) is reached, and this value can be set by the main class (SpaceBlaster) using the
setUpdatePeriod method. In this way, we have created a simple refresh loop for the game.