About Impaler

Android Game Development – 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. Furthermore we have created an example Android project for our first game. In this article we are going to discuss and implement the basic game loop.

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

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

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 graciously

Download the source code here.

Import it into eclipse and it should work right away.

Reference: A Basic Game Loop from our JCG partner Tamas Jano from “Against The Grain” blog.

Do not forget to check out our new Android Game ArkDroid (screenshots below). You feedback will be more than helpful!
Related Articles:

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

27 Responses to "Android Game Development – A Basic Game Loop"

  1. at MainGamePanel I just add this to quit the problem with gettin out and in of the app

    if(!thread.isAlive()){
                thread.setRunning(true);
                thread.start();
    }

  2. dennstein says:

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

    It keeps giving me the error:  Activity cannot be resolved to a type.  is there a typo here or did i miss a step?

    Thanks, great tutorial so far!

    Den

  3. Philip Ullersted says:

    Thanks MED for the solution :D I’m a new programmer, so I have spent hours trying to works this thing out! You just saved me for many more ;)

  4. Sorry to sound a complete n00b but where do I type the  MainGamePanel.java code into??

  5. Mihir Sewak says:

    For some reason 

  6. Siddharth Bakshi says:

    Weeeeee! Ran on emulator. Onwards to the next part! :)

  7. Pradeep says:

    Eclipse might show the following error message:

    ‘Must Override a Superclass Method’ Errors after importing a project into Eclipse.

    This is due to the Java ‘compiler compliance level’ issue.  The compliance level should be 1.6 which can be set from Project->Properties->Java Compiler menu.

  8. Wont accept Thread for some reasom

  9. kevinm-92 says:

    erm. I have re implemented the code countless times but the emulator still stuck on the “HelloWorld, DroidzActivity” screen. frustrating tbf as i have had to start from scratch many times

    • drowlord101 says:

      I’m having the same issue.  You probably need to edit the /res/layout/main.xml file.  It’s not mentioned in the article.  I’d double-check, but for some reason, I’m unable to download the project source right now.  Will try again when I’m home.

    • drowlord101 says:

      Ah… It’s in the Activity file.  He changed a line that wasn’t explicitly called out in the text.  Line 22.
              setContentView(new MainGamePanel(this));

  10. Doug Royal says:

    Would it be better to put setRunning(false) in the MainGamePanel’s surfaceDestory? This might not matter since you’re killing the app when you switch views, but I was trying to learn how to write a multi-threaded, multi-view application and I notice my threads weren’t stopping. Thanks for these posts, they’ve been helpful.

    • Robert F says:

       In my opinion, of course. When I pressed back without it and I started application again, I had an error. With this application is more stable.

  11. Issac Balaji says:

    i tried lot but i can get only blank screen only, i passed my more then hour(Good Timepass)

  12. Everything worked!! Thanks man!
    Its a great tutorial!

  13. shanny2412 says:

    just getting the black screen ??

  14. installed it and it does not open…! black screen! :/

  15. Gaurav says:

    If i want to do a game that basically just updates on touch, not other wise, and has no moving images, would it be better not to use threading?

  16. parkimedes says:

    Thank you, thank you thank you!!! After searching far and wide, your comment helped me solve this problem. My game is based on the above tutorial, and I finally got rid of the crash on back button problem. By the way, I didn’t need your onCreate() function. Mine is different, but seems to work just the same. Its the onKeyDown() one that did the trick, as well as making the thread public.

  17. Amita says:

    i tried the same code but evrytime getting a force close error at the time of activity execution. I am getting the following error :-

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): FATAL EXCEPTION: main

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.game/com.game.DroidzActivity}: android.util.AndroidRuntimeException: requestFeature() must be called before adding content

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1622)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1638)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread.access$1500(ActivityThread.java:117)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:928)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.os.Handler.dispatchMessage(Handler.java:99)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.os.Looper.loop(Looper.java:123)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread.main(ActivityThread.java:3647)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at java.lang.reflect.Method.invokeNative(Native Method)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at java.lang.reflect.Method.invoke(Method.java:507)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at dalvik.system.NativeStart.main(Native Method)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): Caused by: android.util.AndroidRuntimeException: requestFeature() must be called before adding content

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at com.android.internal.policy.impl.PhoneWindow.requestFeature(PhoneWindow.java:181)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.Activity.requestWindowFeature(Activity.java:2729)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at com.game.DroidzActivity.onCreate(DroidzActivity.java:16)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1586)

    04-03 19:34:56.044: ERROR/AndroidRuntime(356): … 11 more

    04-03 19:34:56.103: WARN/ActivityManager(70): Force finishing activity com.game/.DroidzActivity

    04-03 19:34:56.713: WARN/ActivityManager(70): Activity pause timeout for HistoryRecord{40635228 com.game/.DroidzActivity}

    04-03 19:34:57.444: INFO/ActivityManager(70): Displayed com.android.launcher/com.android.launcher2.Launcher: +1m52s867ms

    04-03 19:35:07.536: WARN/ActivityManager(70): Activity destroy timeout for HistoryRecord{40635228 com.game/.DroidzActivity}

    04-03 19:39:11.709: DEBUG/SntpClient(70): request time failed: java.net.SocketException: Address family not supported by protocol

    04-03 19:39:56.376: INFO/Process(356): Sending signal. PID: 356 SIG: 9

    04-03 19:39:56.502: INFO/ActivityManager(70): Process com.game (pid 356) has died.

    04-03 19:44:11.745: DEBUG/SntpClient(70): request time failed: java.net.SocketException: Address family not supported by protocol

    I am a beginner and very much intrested in developing a game. Please help me with the error.

  18. RestlessJai says:

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

    } else {

    Log.d(TAG, “Coords: x=” + event.getX() + “,y=” + event.getY());The above code shows an error as “activity cant be resolved to a type”
    n moreover TAG cant be resolved to a variable….
    Plz suggest me some solutions…

  19. DarkMoon says:

    Great articles, I’m enjoying following along. One thing I would suggest/recommend is updating this for the latest versions of Eclipse, ADK and Android. :-) But so far, it’s worked without needing much change.

    There were a few things of note that I needed to make this work:

    In DroidzActivity.java, the line “setContentView(R.layout.main)” changed to “setContentView(new MainGamePanel(this));” in the final code, but this change wasn’t mentioned in the article.

    In MainGamePanel.java, a TAG variable needs to be created similar to the ones in the other two files (“private static final String TAG = MainGamePanel.class.getSimpleName();”). Again, not mentioned in the article.

    I’m running this on a real device, not an emulator, and it appears that if I change the device’s orientation after starting the app, the touch events stop registering. I had to make sure I kept the device in one orientation to make it register touch events and stop the app.

    Looking forward to the rest of the series! :-)

Leave a Reply


7 + = sixteen



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close