make an android game 3
Creation of an Android game – #2 Gathering the tools
January 22, 2012
make an android game 1
Porting an Android game to WP7 – #1 Introduction
January 25, 2012

Creation of an Android game – #3 Game Activity, Game View, The Main Loop

make an android game 2

In my nature I like to go straight to the core of the software I’m doing, post poning other less important aspects to a later stage of the development.

In the game development, the core is the Game Activity, the view that contains the running game itself. Let’s forget for now the main menu, options views, leaderboards and other tasks.

Basically the Game Activity is just a container for the Game View, a custom control which will contain the graphics of the game.  So the class itself will be empty, while the layout code will be something like:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <com.nex.andro.game.GameView
      android:id="@+id/gameView"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"/>

</RelativeLayout>

 

The GameView control will face as the owner of two important objects:

–          The Game Thread, running the main loop, handling the running, stopped and paused states of the game

–          The Game Engine class, an object handling everything that happens during the game

To understand what is the main loop of a game, please follow these links which I found useful:

–          Link 1

–          Link 2

An important thing to keep in mind, while preparing the main loop, is that it is stricly related to the maximum number of loops per second you can achieve. This number depends on many factors, for example the hardware running the game (CPU, Screen resolution, …) and the quantity and quality of the graphics (more and detailed graphics require more time to be drawn).

In my case, I had no heavy graphics so I concentrated on limiting the number of loops, to avoid a game too fast and unusable. There are plenty of solutions around, but in my opinion sometimes the simplest way is the solution.

So let’s say I want a maximum of 15 loops per second, a value that makes the game fast enough for my taste.  First we calculate the minimum time for a single loop:

int minLoopTime = 1000 / limitFps;

 

This gives me about 66 milliseconds to paint all the graphics.. plenty of time.

Then at the end of each loop, I will measure the time elapsed, and make the thread sleep for the remaining time:

long sleepTime = minLoopTime - (endTime - startTime);

if (sleepTime > 0) {

  Thread.sleep(sleepTime);

}


Simple, right?

And now, the final code of the GameView class (not perfect, but a good starting point)

 

 

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

	class GameThread extends Thread {

		private SurfaceHolder mSurfaceHolder;

		private Handler handler;

		private Context context;

		private GameEngine gameEngine;

		private boolean running;

		private boolean paused;

		private int startingLevel;

		public GameThread(SurfaceHolder surfaceHolder, Context context, GameEngine gameEngine, Handler handler) {
			this.mSurfaceHolder = surfaceHolder;
			this.handler = handler;
			this.context = context;
			this.gameEngine = gameEngine;
		}

		public void setStartingLevel(int startingLevel) {
			this.startingLevel = startingLevel;
		}

		public boolean isRunning() {
			return running;
		}

		public void setRunning(boolean running) {
			this.running = running;
		}

		public boolean isPaused() {
			return paused;
		}

		public void setPaused(boolean paused) {
			this.paused = paused;
		}

		@Override
		public void run() {
			try {
				long startTime = 0;
				long endTime = 0;
				int limitFps = AndConsts.LIMIT_FPS;
				int minLoopTime = 1000 / limitFps;

				// Initialise level
				if (startingLevel <= 0) {
					startingLevel = 1;
				}
				gameEngine.initLevel(startingLevel);

				boolean doPause = true;
				boolean doResume = false;

				while (running) {
					startTime = System.currentTimeMillis();

					Canvas c = null;
					try {
						if (!paused) {
							c = mSurfaceHolder.lockCanvas(null);
							synchronized (mSurfaceHolder) {
								if (doResume) {
									doResume = false;
									doPause = true;
									gameEngine.resume();
								}
								// Update Input
								gameEngine.updateInput();
								// Update physics
								gameEngine.updatePlayerPhysics();
								// Update AI
								gameEngine.updateAIPhysics();
								// Update AI
								gameEngine.updatePhysicsChecks();
								// Update game UI
								gameEngine.updateUI(c);
								// Update sounds
								gameEngine.updateSound();
								// Finalize loop
								gameEngine.finalizeLoop();
							}
						} else {
							if (doPause) {
								doResume = true;
								doPause = false;
								gameEngine.pause();
								// Draw an opacity layer to make the user feel the game is paused
								c = mSurfaceHolder.lockCanvas(null);
								synchronized (mSurfaceHolder) {
									Paint p = new Paint();
									p.setStyle(Paint.Style.FILL);
									p.setColor(Color.BLACK);
									p.setAlpha(125);
									c.drawRect(0, 0, ScreenManager.screenWidthPx, ScreenManager.screenHeightPx, p);
									p = null;
								}
							}
						}
					} catch (Exception ex) {
						// TODO Handle errors
						running = false;
					} 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);
						}
					}

					endTime = System.currentTimeMillis();

					try {
						long sleepTime = minLoopTime - (endTime - startTime);
						if (sleepTime > 0) {
							Thread.sleep(sleepTime);
						}
					} catch (InterruptedException e) {
					// TODO Handle errors
					}

				}
			} catch (Exception ex) {
				// TODO Handle errors
			}
		}
	}

	private GameEngine gameEngine;
	private GameThread thread;

	public GameView(Context context, AttributeSet attrs) {
		super(context, attrs);
		gameEngine = new GameEngine((Activity) context);
		try {
			// Load game
			gameEngine.initGame(this);
			// Register our interest in hearing about changes to our surface
			SurfaceHolder holder = getHolder();
			holder.addCallback(this);
			thread = new GameThread(holder, context, gameEngine, new Handler() {
				@Override
				public void handleMessage(Message m) {
					//Do nothing for now
				}
			});

			setFocusable(true); // Make sure we get key events
			setFocusableInTouchMode(true);// Make sure we get key events

		} catch (AbnormalException e) {
			//TODO Handle errors
		}
	}

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

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		stop();
	}

	public void stop() {
		// Stop the gaming thread
		thread.setRunning(false);
		try {
			thread.join(500);
		} catch (InterruptedException e) {
		}
	}

	public void pause() {
		thread.setPaused(true);
	}

	public void resume() {
		thread.setPaused(false);
	}

	public void start() {
		thread.setRunning(true);
		thread.start();
	}

	public void restartLevel() {
		gameEngine.restartLevel();
	}
}


3 Comments

  1. Profesor says:

    Thanks for the info.

  2. Ese says:

    Thank you for this beautiful tutorial….However the GameEngine is not available…..I will like to see the implementation of the GameEngine class