Simple Java Android Game Loop
So, you want to program a steady game? This post is specifically about programming in Java for the Android, but the concepts can be applied to any game in any language. This post explains how to write a gameLoop() that allows a lot of control without using a refreshHandler or without calling the invalidate() method to force the game to draw (which can do so at an inconstant rate). It also explains how to create a steady loop that sleeps for a variable amount of time, taking into consideration the time required to update and render the actual game. There’s a couple of things you need to take into consideration first, so I’ll break it down and show you how I did it.
There’s many ways to go about this. Some of the methods I’ve found have the OS redrawing the screen, or invoking the OS to do so. I find the best way is to create a ‘tick’ (repeated every game loopin) that calls the gameUpdate() and gameRender() methods within a self contained gameLoop(), which then repeats until the game is done. Going this route, we can time how long it takes to update and render and then subtract that from the desired amount of sleep time (time between updates). This makes for a steady game.
Creating an Android game in this manner requires three things:
STEP 1: You need a game engine. I’ll assume you’ve created this already or know how to implement one. I’m only going to show you how to create a consitent loop which draws to the Android’s screen. Your game engine should have two public methods that will be called from within the game loop; an update() method, and a draw(Canvas) method.
STEP 2: A class that extends Android’s SurfaceView. Inheriting from this class will give us all the methods and variables we need to draw to the screen.
STEP 3: A separate class which extends Java’s Thread class. This class will contain the actual game loop. It will be initialized when the SurfaceView object is initialized and loops until the game finishes. At the end of every game loop the Thread will call sleep() for the desired amount of time, minus the time it took to actually update and render the game. This allows our game to be more consistent than if we had just slept for the same amount of time every loop.
First, I’ll explain the SurfaceView class. You’ll want to create a class like this to interact with the cell phone’s screen:
public class GameEngineView extends SurfaceView implements SurfaceHolder.Callback
{
//used to keep track of time between updates and amount of time to sleep for
long lastUpdate = 0;
long sleepTime=0;
//Game engine
GameEngine gEngine;
//objects which house info about the screen
SurfaceHolder surfaceHolder;
Context context;
//our Thread class which houses the game loop
private PaintThread thread;
//initialization code
void InitView(){
//initialize our screen holder
SurfaceHolder holder = getHolder();
holder.addCallback( this);
//initialize our game engine
gEngine = new GameEngine();
gEngine.Init(context.getResources());
//initialize our Thread class. A call will be made to start it later
thread = new PaintThread(holder, context, new Handler(), gEngine);
setFocusable(true);
}
//class constructors
public GameEngineView(Context contextS, AttributeSet attrs, int defStyle){
super(contextS, attrs, defStyle);
context=contextS;
InitView();
}
public GameEngineView(Context contextS, AttributeSet attrs){
super(contextS, attrs);
context=contextS;
InitView();
}
…
[Various code to capture key presses, screen changes and screen touch events]
…
//these methods are overridden from the SurfaceView super class. They are automatically called
when a SurfaceView is created, resumed or suspended.
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
boolean retry = true;
//code to end gameloop
thread.state=PaintThread.PAUSED;
while (retry) {
try {
//code to kill Thread
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
if(thread.state==PaintThread.PAUSED){
//When game is opened again in the Android OS
thread = new PaintThread(getHolder(), context, new Handler(), gEngine);
thread.start();
}else{
//creating the game Thread for the first time
thread.start();
}
}
}The SurfaceCreated() and surfaceDestroyed() methods are called when the operating system launches, resumes, or suspends the application. Since the game engine is a separate class it is untouched. However, you may want to make sure that in your Android application .xml file, you only allow a single instance of the application and prevent the system from destroying it when suspended by including the following code in your <activity> tag:
android:launchMode=“singleTask” android:alwaysRetainTaskState=“true”
Now we’ll take a look at the Thread class. Mine is called PaintThread:
public class PaintThread extends Thread{
private SurfaceHolder mSurfaceHolder;
private Handler mHandler;
private Context mContext;
private Paint mLinePaint;
private Paint blackPaint;
GameEngine gEngine;
//for consistent rendering
private long sleepTime;
//amount of time to sleep for (in milliseconds)
private long delay=70;
//state of game (Running or Paused).
int state = 1;
public final static int RUNNING = 1;
public final static int PAUSED = 2;
public PaintThread(SurfaceHolder surfaceHolder, Context context, Handler handler,
GameEngine gEngineS) {
//data about the screen
mSurfaceHolder = surfaceHolder;
this.mHandler = handler;
this.mContext = context;
//standard game painter. Used to draw on the canvas
mLinePaint = new Paint();
mLinePaint.setARGB(255, 0, 255, 0);
//black painter below to clear the screen before the game is rendered
blackPaint = new Paint();
blackPaint.setARGB(255, 0, 0, 0);
//mLinePaint.setAntiAlias(true);
gEngine=gEngineS;
}
//This is the most important part of the code. It is invoked when the call to start() is
//made from the SurfaceView class. It loops continuously until the game is finished or
//the application is suspended.
@Override
public void run() {
//UPDATE
while (state==RUNNING) {
//time before update
long beforeTime = System.nanoTime();
//This is where we update the game engine
gEngine.Update();
//DRAW
Canvas c = null;
try {
//lock canvas so nothing else can use it
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
//clear the screen with the black painter.
c.drawRect(0, 0, c.getWidth(), c.getHeight(), blackPaint);
//This is where we draw the game engine.
gEngine.Draw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
//SLEEP
//Sleep time. Time required to sleep to keep game consistent
//This starts with the specified delay time (in milliseconds) then subtracts from that the
//actual time it took to update and render the game. This allows our game to render smoothly.
this.sleepTime = delay-((System.nanoTime()-beforeTime)/1000000L);
try {
//actual sleep code
if(sleepTime>0){
this.sleep(sleepTime);
}
} catch (InterruptedException ex) {
Logger.getLogger(PaintThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}The code is documented so it should be pretty self explanatory. It took 5+ hours to figure all of this out since I couldn’t find a clear, easy to follow article anywhere that explained how to design a game loop with this much control over timing and rendering. If you have any questions leave a comment at the bottom of the page and I’ll do my best to help you out or make the post clearer.
[The code was adapted and modified from the Lunar Lander example that comes with the Android SDK. Since that code is open source, feel free to use this code without my permission, but also feel free to show me anything cool you’ve designed by using this article. ;) ]
James