A Basic Game Loop

Following the series so far you we have an understanding of the game architecture. Even if just briefly but we know that we need to take input in some form, update the internal state of the game and finally render it to the screen and also produce some sounds and/or vibrations.

Let’s keep it simple. Check the following diagram.

Game Loop

A basic game loop

We handle input, update the state of our internal objects and render the current state. The Update and Render are grouped logically. They are tied together and tend to be executed one after the other.


Anything in Android happens inside an Activity. The Activity will create a View. The View is where everything happens. It is where the touch takes place and the resulting image gets displayed. Think of the Activity as a table that holds a sheet of paper (the View) enabling us to draw something. We will use our pencil to draw something onto the paper. That will be our touch and the actual chemistry happens on the paper so the result of our interaction with the View produces an image. The same is with Activity and View. Something like the following diagram:

Android Game Loop

Let’s open up DroidzActivity.java from our project. We see the line

 setContentView(R.layout.main);

This does nothing more than assigns the default (R) view to the activity when it is created. In our case it happens at startup.

Let’s create a new View which we will use. A View is a simple class that provides us with event handling (like onTouch) and a visible rectangle shaped space to draw on. The simplest way is to extend Android’s own SurfaceView. We will also implement SurfaceHolder.Callback to gain access to surface changes, for example when it is destroyed or the orientation of the device has changed.

MainGamePanel.java

package net.obviam.droidz;

import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainGamePanel extends SurfaceView implements
		SurfaceHolder.Callback {

	public MainGamePanel(Context context) {
		super(context);
		// adding the callback (this) to the surface holder to intercept events
		getHolder().addCallback(this);
		// make the GamePanel focusable so it can handle events
		setFocusable(true);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}

	@Override
	protected void onDraw(Canvas canvas) {
	}
}

The above code is a plain class that overrides the methods we are interested in.
Nothing special apart from lines 15 and 17.

getHolder().addCallback(this);

This line sets the current class (MainGamePanel) as the handler for the events happening on the actual surface.

setFocusable(true);

The above line makes our Game Panel focusable, which means it can receive focus so it can handle events. We added the callback and made it focusable in the constructor so we won’t miss.

The over-riden methods (line 20 onwards) will all be used but currently keep them empty.

Let’s create the thread that will be our actual game loop.

MainThread.java

package net.obviam.droidz;

public class MainThread extends Thread {

	// flag to hold game state
	private boolean running;
	public void setRunning(boolean running) {
		this.running = running;
	}

	@Override
	public void run() {
		while (running) {
			// update game state
			// render state to the screen
		}
	}
}

As you can see this does not do much. It overrides the run() method and while the running flag is set to true it does an infinite loop.

Currently the thread is not instantiated so let’s start it up when the screen loads.
Let’s take a look at the modified MainGamePanel class.

package net.obviam.droidz;

import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainGamePanel extends SurfaceView implements
		SurfaceHolder.Callback {

	private MainThread thread;

	public MainGamePanel(Context context) {
		super(context);
		getHolder().addCallback(this);

		// create the game loop thread
		thread = new MainThread();

		setFocusable(true);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		thread.setRunning(true);
		thread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
				// try again shutting down the thread
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}

	@Override
	protected void onDraw(Canvas canvas) {
	}
}

We added the following lines:
Line 12 declares the thread as a private attribute.

	private MainThread thread;

In line 19 we instantiate the thread.

		thread = new MainThread();

In the surfaceCreated method we set the running flag to true and we start up the thread (lines 30 and 31). By the time the this method is called the surface is already created and the game loop can be safely started.

Take a look at the surfaceDestroyed method.

public void surfaceDestroyed(SurfaceHolder holder) {
	// tell the thread to shut down and wait for it to finish
	// this is a clean shutdown
	boolean retry = true;
	while (retry) {
		try {
			thread.join();
			retry = false;
		} catch (InterruptedException e) {
			// try again shutting down the thread
		}
	}
}

This method is called directly before the surface is destroyed. It is not the place to set the running flag but the code we put in ensures that the thread shuts down cleanly. We simply block the thread and wait for it to die.

If we now run our project in the emulator won’t be able to see much but we’ll use some logging to test it. Don’t worry about it as I will cover logging in a later chapter.
You can find more on the Android site.

Add interaction with the screen

We will exit the application when we touch the lower part of the screen. If we touch it anywhere else we’ll just log the coordinates.

In the MainThread class we add the following lines:

	private SurfaceHolder surfaceHolder;
	private MainGamePanel gamePanel;

	public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
		super();
		this.surfaceHolder = surfaceHolder;
		this.gamePanel = gamePanel;
	}

We declared the gamePanel and surfaceHolder variables and a constructor taking the instances as parameters.
It is important to have them both and not just the gamePanel as we need to lock the surface when we draw and that can be done through the surfaceHolder only.

Change the line int the constructor of the MainGamePanel that instantiates the thread to

		thread = new MainThread(getHolder(), this);

We are passing the current holder and the panel to its new constructor so the thread can access them. We will create the game update method in the game panel and we’ll trigger it from the thread but currently just leave it as it is.

Add the TAG constant to the MainThread class. Every class will have its own String constant called TAG. The value of the constant will be the name of the class containing it. We are using Android’s own logging framework and that takes two parameters. The firs is the tag which is just a string to identify the source of the log message and the second is the message we want to log. It’s a good practice to use the name of the class for the tag as it makes it simple to look up the logs.

A note on logging

To open the log viewer go to Windows -> Show View -> Other… and in the dialog select Android -> LogCat

Show View -> LogCat

Show View -> LogCat

Now you should see the LogCat view. This is nothing more than a console where you can follow Android’s log. It’s a great tool as you can filter for logs containing a specific text or logs with a certain tag which is quite useful.

Let’s get back to our code. The MainThread.java class looks like this:

package net.obviam.droidz;

import android.util.Log;
import android.view.SurfaceHolder;

public class MainThread extends Thread {

	private static final String TAG = MainThread.class.getSimpleName();

	private SurfaceHolder surfaceHolder;
	private MainGamePanel gamePanel;
	private boolean running;
	public void setRunning(boolean running) {
		this.running = running;
	}

	public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
		super();
		this.surfaceHolder = surfaceHolder;
		this.gamePanel = gamePanel;
	}

	@Override
	public void run() {
		long tickCount = 0L;
		Log.d(TAG, "Starting game loop");
		while (running) {
			tickCount++;
			// update game state
			// render state to the screen
		}
		Log.d(TAG, "Game loop executed " + tickCount + " times");
	}
}

In line 08 we define the tag for logging.
In the run() method we define tickCount which is incremented every time the while loop (the game loop) is executed.
We log the results.

Let’s go back to MainGamePanel.java class where we modified the onTouchEvent method so we handle touches on the screen.

	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			if (event.getY() > getHeight() - 50) {
				thread.setRunning(false);
				((Activity)getContext()).finish();
			} else {
				Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
			}
		}
		return super.onTouchEvent(event);
	}

Line 02 we check if the event on the screen is a start of a pressed gesture (MotionEvent.ACTION_DOWN). If so we check if the touch happened in the lower part of the screen. That is, the Y coordinate of the gesture is in the lower 50 pixels of the screen. If so we set the thread’s running status to false and call finish() on the main activity which basically exits the application.

Note: The screen is a rectangle with the upper left coordinates at (0,0) and the lower right coordinates at (getWidth(), getHeight()).

I have also modified the DroidzActivity.java class so we log its lifecycle.

package net.obviam.droidz;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;

public class DroidzActivity extends Activity {
    /** Called when the activity is first created. */

	private static final String TAG = DroidzActivity.class.getSimpleName();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // requesting to turn the title OFF
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // making it full screen
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // set our MainGamePanel as the View
        setContentView(new MainGamePanel(this));
        Log.d(TAG, "View added");
    }

	@Override
	protected void onDestroy() {
		Log.d(TAG, "Destroying...");
		super.onDestroy();
	}

	@Override
	protected void onStop() {
		Log.d(TAG, "Stopping...");
		super.onStop();
	}
}

Line 20 makes the display fullscreen.
The onDestroy() and onStop() methods were overridden just to log the activity’s lifecycle.

Let’s run the application by right-clicking on the project and select Run As -> Android application
You should see a black screen. If you click around a few time on the upper half and then you click on the bottom of your emulator’s screen the application should exit.
At this stage it is worth checking the logs.

LogCat

The highlighted lines are the most interesting as if you match look the logs up in the code you will see exactly the order of the method calls. You should also see how many times the thread’s while loop executed. It is a very high number but next time we will be more considerate about the cycles as we will introduce FPS and UPS. That is Frames Per Second and Updates Per Second. We will create a game loop that will actually draw something onto the screen and it will do it as many times per second as we specify it.

Things we did so far:

  • Create a full screen application
  • Have a separate thread controlling the application
  • Intercepting basic gestures like pressed gesture
  • Shutting down the application gratiously
Download the source code here.

Import it into eclipse and it should work right away.

How to implement a proper game loop with consideration for FPS and UPS can be found in this article

LinkedInRedditTumblrDiggTechnorati FavoritesShare

54 Responses - Add Yours+

  1. Mark says:

    For those of you that have the App crashing on leaving via home button: Add

    if (canvas != null)

    right before

    this.gamePanel.onDraw(canvas);

    in the run() method of the MainThread. This helped me.

  2. Richard says:

    Hi there,

    as already said this is a really good tutorial, easy to understand and good formatted. Problem is, I get a NullPointer in the onDraw() method because the canvas is null. I tried to fix it with the updates in the comments above but still the problem remains. If I understood it right, the expression

    canvas = this.surfaceHolder.lockCanvas();

    in MainThread.run() doesn’t provide a valid value for canvas. Instead it returns null. Any help would be appreciated. Thanks in advance!

  3. Praveen says:

    Hi,

    Nyc tutorial everything works fine . My question is that “What is the use of the MainThread ” in this program? I removed d calls to main thread and ran the program and its working fine .

  4. Hymbert says:

    Excellent article. I have one important question.
    What if i have several events off a different types. for instance onExplosion, onAttack etc how could i fit that in this construction above? thanks again

  5. [...] architecture based around an continuous loop. You can find out more about the architecture and the loop here and [...]

  6. [...] know from the typical game architecture that the main loop acts as a super controller, which updates the states and then renders the objects onto the screen [...]

  7. DURGESH says:

    02-04 12:34:02.043: E/AndroidRuntime(500): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.fortune4.Co_ordinates/com.fortune4.Co_ordinates.Co_ordinates}: java.lang.InstantiationException: com.fortune4.Co_ordinates.Co_ordinates
    I AM GETTING THIS ERROR

  8. Logan says:

    Great tutorial!

    You might want to add here the fact that you need to add the Activity library to your original code for MainGamePanel to work. The line ((Activity)getContext()).finish(); doesn’t work unless you import android.app.Activity. I was able to figure that out after downloading your final code.

  9. Jova says:

    Thanks fro nice tutorial!

    A question, how should control frame rate.
    Will be:

    Thread.sleep(1000/framerate);

    enough efficient?

  10. Germain says:

    I kept getting errors with @override in the MainGamePanel.java file. I found out that changing the project’s java compiler property from version 1.5 to 1.6 solves that issue. It worked, however I am not sure I understand why this is an issue with 1.5 in the first place. Can anybody offer some clarification?

  11. anusha says:

    I am getting the error like” Cannot instantiate the type MainGamePanel in SetContentView() ” can any one help my problem…thanks in advance.

  12. Bob says:

    Hi

    This thread issue still has not been fixed yet. Gonna try implement a solution myself.

    • Impaler says:

      Hi, sorry for not doing it, I’ve been really busy lately and will get back to the blog once I get some free time.
      Any help is appreciated.
      I

      • Bob says:

        I’ve managed to fix the crash issue relating to the threading. Dont know how efficient the code is, but at least it prevents the crash. I modified the “MainGamePanel.java” surfacedestroyed method with:

        public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(TAG, “Surface is being destroyed”);
        // tell the thread to shut down and wait for it to finish
        // this is a clean shutdown
        boolean retry = true;
        while (retry) {
        try {
        thread.setRunning(false);
        thread.join();
        ((Activity)getContext()).finish();
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        retry = false;
        }
        Log.d(TAG, “Thread was shut down cleanly”);
        }

  13. coded says:

    Create a new class. Right-click on the package where your Activity class is situated. Name it MainGamePanel

  14. Mesh says:

    ok, call me a newbie but i got confused on “Let’s create a new View which we will use. ”

    how to create that & where ? i mean the MainGamePanel.java

  15. pete says:

    Oh yes, silly me, there it is in the next game loop tutorial.

  16. pete says:

    Great tutorial! Thanks for the work you put into this. I’m using it as a reference as I work on my own projects.

    One thing I’ve had to fix in my own project, I’m not sure if it’s applicable here, is that the onDraw of my SurfaceView wasn’t being called. To mandate that it be called, I had to set the property setWillNotDraw(false) in the surfaceCreated method. Maybe I’m missing something…?

    Anyways, the gameloop is what I’m focusing on now, and your work is very clear and easy to follow.

  17. Chris says:

    Thx so much for the tutorial,
    I’m currently developing a game app for Android, and this tutorial helps me a lot :D
    especially for the finishing the activity.

  18. Chris says:

    Thx so much for the tutorial,
    I’m currently developing a game app for Android, and this tutorial helps me a lot :D
    especially for the finishing the activity

  19. Angel says:

    Hi thanks for this excellent article. I have a quick q’s abt this line

    ((Activity)getContext()).finish();

    Eclipse is giving me an error.. Any suggestion?

  20. Rolf says:

    Very nice article. I build my own game loop, but after reading this i changed it radical.

    But i have one question, SurfaceView does not support android menu, so how can i add a menu?

    Rolf

    • Impaler says:

      When you go down the Canvas or OpenGL ES route, you have to bear in mind that you will create your own UI components.
      If you want to use the Android View components, I am afraid you will have to skip between your custom view and the Android view, which won’t look that nice.
      Identify your menu components, create a class out of the menu and draw it when you are in the menu “state”.

  21. mila says:

    really great articles, i struggled on thread and surfaceview, now i am clear.
    very clear explaination, if you plan to write a book on android, 100% i will buy it.
    thank you so much

  22. Wrior says:

    Just Thought i’d point out this small typo:
    Currently the thread is not instantiated so let’s start it up when the screen loads.
    Let’s take a look at the modified MainThread class.

    Think you meant MainGamePanel class.

    PS:Great Tutorial

  23. [...] game loop is the heartbeat of every game. We used a very rudimentary one so far (you can find it here) without any control over how fast or slow we update our game state and which frames to render. To [...]

  24. Thank you for this tutorial. It’s clear and concise.

  25. [...] couple of weeks ago. I am a beginner so I followed this tutorial on making a simple 2D game engine:http://obviam.net/index.php/a-very-basic-the-game-loop-for-android/I implemented the onTouchEvent(MotionEvent event) method in the main game panel surface view. [...]

  26. Quen says:

    Wow, very helpful. This is the first tutorial, after which I understand how things works. Thanks

  27. Alan Owen says:

    Really good articles on Android game dev here, just what I need to get started – you’ve done the dev community a great service

  28. Hooman says:

    Hey just noticed a bug – if you run the program, click on the Home button, and then run your program again it will crash. This is because you cannot restart the same thread twice in Java. If you want to run a thread again, you need to create a new one. You should make this your new surfaceCreated function:

    public void surfaceCreated(SurfaceHolder holder) {
    thread = new MainThread();
    thread.setRunning(true);
    thread.start();
    }

    and remove thread = new MainThread(); from the contructor.

    • Impaler says:

      Thanks.
      I will amend the code to fix that.
      I will try fiddling with the surfaceDestroyed to stop the thread when the surface will be destroyed so it won’t be hanging around.

      • jimcracks says:

        I’m not sure if this was supposed to be fixed yet or not, but I was still having issues with returning to the game after using the Home or back button. The screen would just be empty.

        It may not be the best solution, but I resolved the issue by adding “thread.setRunning(false);” to the surfaceDestroyed method (like LunarLander) and by changing the code in the surfaceCreated method to:

        if (thread.getState() == Thread.State.TERMINATED) {
        thread = new MainThread(getHolder(), this);
        thread.setRunning(true);
        thread.start();
        }
        else {
        thread.setRunning(true);
        thread.start();
        }

        reference:
        http://code.google.com/p/android/issues/detail?id=972
        http://wonton-games.blogspot.com/2010/06/lunar-lander-resume-game-workaround.html

        This is an excellent series. For the last month, I’ve been looking at every Android game tutorial and book I could find/ This is best explanation I have seen yet. Please keep the content coming!

        • Impaler says:

          Just realised that it is not fixed yet.
          I will revisit the code and will update it.

          Thanks for the heads up!

          • bhart76 says:

            First of all thanks very much for this excellent tutorial, however I was wondering if you had any updates on the thread issue yet?

          • Impaler says:

            Sorry for not providing an update.
            I will try to do so in the week-end. Did you try to implement the fix yourself?

          • bhart76 says:

            Hi Impaler, I have been trying but my knowledge is not yet good enough. My problem is that I am trying to kill the process / thread if the user presses the back button. So far having no luck.
            Any updates you could provide would be really helpfull in the meantime I am trying to find out. Will post if I find it.

        • Sean A.O. Harney says:

          You could refactor that code to simply:

          if (thread.getState() == Thread.State.TERMINATED) {
          thread = new MainThread(getHolder(), this);
          }

          thread.setRunning(true);
          thread.start();

        • Sean A.O. Harney says:

          You could refactor that to this equivalent code:

          if (thread.getState() == Thread.State.TERMINATED) {
          thread = new MainThread(getHolder(), this);
          }
          thread.setRunning(true);
          thread.start();

  29. Hooman says:

    Great tutorial! I wish there were more articles like this – Google docs are OK but there’s way too much detail in their examples. I also like how your code is organized – a seperate file for each class, makes it a lot easier to read.

  30. bobrules says:

    Excellent tutorial. Really helpmed me thanks!

  31. Ramon says:

    It is the best article above games. I like more please. I desire design a game for Android ;)

    One question: I try your code in my emulator, and this only show the Log: “Starting Game Loop”.

    I am a noob programing in android, sorry…

    • Ramon says:

      I reply my coment. I didn’t found the exit in my aplication. For exit you need touch the screen very down.
      The code work perfectly. It is brillant!!!

      • Impaler says:

        Yep, it is just a basic loop to get yourself started.
        The code that exits the app is in the MainGamePanel.java in the onTouchEvent:

        if (event.getY() > getHeight() – 50) {
        thread.setRunning(false);
        ((Activity)getContext()).finish();
        }

        Checks if the touch happened in the lower part of the screen and exits.
        More to come soon.

Leave a Reply

You must be logged in to post a comment.