GLGame Implementing the Game Interface

In the previous chapter, we implemented the AndroidGame class, which ties together all the submodules for audio, file I/O, graphics, and user input handling. We want to reuse most of this for our upcoming 2D OpenGL ES game, so let's implement a new class called GLGame that implements the Game interface we defined earlier.

The first thing you will notice is that we can't possibly implement the Graphics interface with our current knowledge of OpenGL ES. Here's a surprise: we won't implement it. OpenGL does not lend itself well to the programming model of our Graphics interface. Instead we'll implement a new class, GLGraphics, which will keep track of the GL10 instance we get from the GLSurfaceView. Listing 7-2 shows the code.

Listing 7-2.; Keeping Track of the GLSurfaceView and the GL10 Instance package com.badlogic.androidgames.framework.impl;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

public class GLGraphics { GLSurfaceView glView; GL10 gl;

GLGraphics(GLSurfaceView glView) { this.glView = glView;

public GL10 getGL() { return gl;

public int getWidth() {

return glView.getWidth();

public int getHeight() {

return glView.getHeight();

This class has just a few getters and setters. Note that we will use this class in the rendering thread set up by the GLSurfaceView. As such, it might be problematic to call methods of a View, which lives mostly on the UI thread. In this case it's OK, though, as we only query for the GLSurfaceView's width and height, so we get away with it.

The GLGame class is a bit more involved. It borrows most of its code from the AndroidGame class. The only thing that is a little bit more complex is the synchronization between the rendering and UI threads. Let's have a look at it in Listing 7-3.

Listing 7-3., the Mighty OpenGL ES Game Implementation package com.badlogic.androidgames.framework.impl;

import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;


import android.content.Context;

import android.opengl.GLSurfaceView;

import android.opengl.GLSurfaceView.Renderer;

import android.os.Bundle;

import android.os.PowerManager;

import android.os.PowerManager.WakeLock;

import android.view.Window;

import android.view.WindowManager;

import com.badlogic.androidgames.framework.Audio; import com.badlogic.androidgames.framework.FileIO; import com.badlogic.androidgames.framework.Game; import com.badlogic.androidgames.framework.Graphics;

import com.badlogic.androidgames.framework.Input; import com.badlogic.androidgames.framework.Screen;

public abstract class GLGame extends Activity implements Game, Renderer {

enum GLGameState { Initialized, Running, Paused, Finished, Idle

GLSurfaceView glView;

GLGraphics glGraphics;

Audio audio;

Input input;

FilelO filelO;

Screen screen;

GLGameState state = GLGameState.Initialized;

Object stateChanged = new Object();

long startTime = System.nanoTime();

WakeLock wakeLock;

The class extends the Activity class and implements the Game and GLSurfaceView.Renderer interface. It has an enum called GLGameState that keeps track of the state the GLGame instance is currently in. We'll see how those are used in a bit.

The members of the class consist of a GLSurfaceView and GLGraphics instance. The class also has Audio, Input, FilelO, and Screen instances, which we need for writing our game, just as in the AndroidGame class. The state member keeps track of the state via one of the GLGameState enums. The stateChanged member is an object we'll use to synchronize the UI thread and the rendering thread. Finally we have a member to keep track of the delta time and a WakeLock we'll use to keep the screen from dimming.

^Override public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); glView = new GLSurfaceView(this); glView.setRenderer(this); setContentView(glView);

glGraphics = new GLGraphics(glView); filelO = new AndroidFileIO(getAssets()); audio = new AndroidAudio(this); input = new AndroidInput(this, glView, 1, 1); PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GLGame");

In the onCreate() we perform the usual setup routine. We make the Activity go fullscreen and instantiate the GLSurfaceView, setting it as the content View. We also instantiate all the other classes that implement framework interfaces, such as the AndroidFileIO or AndroidInput classes. Note that we reuse the classes we used in the AndroidGame class, except for AndroidGraphics. Another important point is that we no longer let the AndroidInput class scale the touch coordinates to a target resolution, as in AndroidGame. The scale values are both 1, so we will get the real touch coordinates. It will become clear later on why we do that. The last thing we do is create the WakeLock instance.

public void onResume() { super.onResume(); glView.onResume(); wakeLock.acquire();

In the onResume() method we let the GLSurfaceView start the rendering thread with a call to its onResume() method. We also acquire the WakeLock.

@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { glGraphics.setGL(gl);

synchronized(stateChanged) {

if(state == GLGameState.Initialized)

screen = getStartScreen(); state = GLGameState. Running; screen.resume(); startTime = System. nanoTime();

The next thing that will be called is the onSurfaceCreate() method. The method is invoked on the rendering thread, of course. Here we can see how the state enums are used. If the application is started for the first time, the state will be GLGameState.Initialized. In this case we call the getStartScreen() method to return the starting screen of the game. If the game is not in an initialized state, but was already been running, we know that we have just resumed from a paused state. In any case we set the state to GLGameState.Running and call the current Screen's resume() method. We also keep track of the current time so we can calculate the delta time later on.

The synchronization is necessary, since the members we manipulate within the synchronized block could be manipulated in the onPause() method on the UI thread. That's something we have to prevent, so we use an object as a lock. We could have also used the GLGame instance itself here, or a proper lock.

@Override public void onSurfaceChanged(GL10 gl, int width, int height) { }

The onSurfaceChanged() method is basically just a stub. There's nothing for us to do here.

@Override public void onDrawFrame(GL10 gl) { GLGameState state = null;

synchronized(stateChanged) { state = this.state;

synchronized(stateChanged) { state = this.state;

if(state == GLGameState.Running) {

float deltaTime = (System.nanoTime()-startTime) / 1000000000.0f; startTime = System.nanoTime();

screen.update(deltaTime); screen.present(deltaTime);

if(state == GLGameState.Paused) { screen.pause(); synchronized(stateChanged) {

this.state = GLGameState.Idle; stateChanged.notifyAll();

if(state == GLGameState.Finished) { screen.pause(); screen.dispose(); synchronized(stateChanged) {

this.state = GLGameState.Idle; stateChanged.notifyAll();

The onDrawFrame() method is were the bulk of all the work is performed. It is called by the rendering thread as often as possible. Here we check which state our game is currently in and react accordingly. As the state can be set on the onPause() method on the UI thread, we have to synchronize the access to it.

If the game is running we calculate the delta time and tell the current Screen to update and present itself.

If the game is paused we tell the current Screen to pause itself as well. We then change the state toGLGameState.Idle, indicating that we have received the pause request from the UI thread. Since we wait for this to happen in the onPause() method in the UI thread, we notify the UI thread that it can now really pause the application. This notification is necessary, as we have to make sure that the rendering thread is paused/shut down properly in case our Activity is paused or closed on the UI thread.

If the Activity is being closed (and not paused), we react to GLGameState.Finished. In this case we tell the current Screen to pause and dispose of itself, and then send another notification to the UI thread, which waits for the rendering thread to properly shut things down.

@Override public void onPause() {

synchronized(stateChanged) { if(isFinishing())

state = GLGameState.Finished;

else state = GLGameState.Paused; while(true) { try {

stateChanged.wait(); break;

} catch(InterruptedException e) { }




The onPause() method is our usual Activity notificaton method that's called on the UI thread when the Activity is paused. Depending on whether the application is closed or paused, we set the state accordingly and wait for the rendering thread to process the new state. This is achieved with the standard Java wait/notify mechanism.

Finally we release the WakeLock and tell the GLSurfaceView and the Activity to pause themselves, effectivley shutting down the rendering thread and destroying the OpenGL ES surface, which triggers the dreaded OpenGL ES context loss mentioned earlier.

public GLGraphics getGLGraphics() { return glGraphics;

The getGLGraphics() method is a new method that is only accessible via the GLGame class. It returns the instance of GLGraphics we store so that we can get access to the GL10 interface in our Screen implementations later on.

^Override public Input getInput() { return input;

^Override public FileIO getFileIO() { return fileIO;

^Override public Graphics getGraphics() {

throw new IllegalStateException("We are using OpenGL!");

^Override public Audio getAudio() { return audio;

^Override public void setScreen(Screen screen) { if (screen == null)

throw new IllegalArgumentException("Screen must not be null");

this.screen.pause(); this.screen.dispose(); screen.resume(); screen.update(0); this.screen = screen;

@Override public Screen getCurrentScreen() { return screen;

The rest of the class works as before. In case we accidentally try to access the standard Graphics instance, we throw an exception, though, as it is not supported by GLGame. Instead we'll work with the GLGraphics method we get via the GLGame.getGLGraphics() method.

Why did we go through all the pain of synchronizing with the rendering thread? Well, it will make our Screen implementations live entirely on the rendering thread. All the methods of Screen will be executed there, which is necessary if we want to access OpenGL ES functionaility. Remeber, we can only access OpenGL ES on the rendering thread.

Let's round this out with an example. Listing 7-4 shows how our first example in this chapter looks when using GLGame and Screen.

Listing 7-4.; More Screen Clearing, Now with 100 Percent More GLGame package com.badlogic.androidgames.glbasics;

import java.util.Random;

import javax.microedition.khronos.opengles.GL10;

import com.badlogic.androidgames.framework.Game; import com.badlogic.androidgames.framework.Screen; import com.badlogic.androidgames.framework.impl.GLGame; import com.badlogic.androidgames.framework.impl.GLGraphics;

public class GLGameTest extends GLGame { @Override public Screen getStartScreen() { return new TestScreen(this);

class TestScreen extends Screen { GLGraphics glGraphics; Random rand = new Random();

public TestScreen(Game game) { super(game);

glGraphics = ((GLGame) game).getGLGraphics();

@Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

gl.glClearColor(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 1); gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

@Override public void

@Override public void

@Override public void

@Override public void

This is the same program as in our last example, except that we now derive from GLGame instead of Activity, and we provide a Screen implementation instead of a GLSurfaceView.Renderer implementation.

In the following examples, we'll only have a look at the relevant parts of each example's Screen implementation. The overall structure of our examples will stay the same. Of course, we have to add the example GLGame implementations to our starter Activity, as well as to the manifest file.

With that out of our way, let's render our first triangle.

0 0

Post a comment