Mobile Zone is brought to you in partnership with:

Scott McCain is an independent software developer and consultant. He works primarily with mobile an embedded devices using Java and C/C++. He is knowledgeable with Symbian, Palm OS, and BREW. He is also experienced in WAP and WML. Scott is a DZone Zone Leader. Scott has posted 3 posts at DZone. View Full User Profile

Beginning Android Game Programming

10.01.2010
| 198596 views |
  • submit to reddit

Introduction

Game development on the Android platform is challenging and rewarding and comes with it's own set of pitfalls and hard learned lessons. In this series of articles I hope to show you some of those pitfalls and maybe teach a lesson or two along the way. While you will be challenged I also hope to help you see the rewards that can come from those challenges.

Background

Mobile Java applications are not a new concept and in fact were one of the original focuses of Sun when it because the java project. There have been mobile java games since the first JVM was put on a mobile device. In the case of the Android things are a little bit different. Android uses what is called the Dalvik Virtual Machine, or DVM, which is an opensource implementation of a JVM. There are several differences between Dalvik and a standard JVM, some subtle, some not so subtle. The DVM is also not aligned to either Java SE or Java ME, but to an apache implementation called Apache Harmony Java. All of this makes for a slight learning curve if you happen to be transitioning from Java ME.

Basic Game Architecture

This example requires three basic classes in order to implement a basic game. The game logic can be extended or changed and the resources can be replaces easily using the same patterns. The file DrawablePanel.java contains the code for DrawablePanel which extends SurfaceView and provides a full screen canvas. The DrawablePanel contains an AnimationThread. This class extends Thread (you could also provide a runnable, whatever pattern is more familiar). The AnimationThread class holds a reference to the DrawablePanel described above and updates the DrawablePanel for game logic and forces a redraw of the panel.

 Block Diagram

 

First Steps

I will be using Eclipse as the compilation tool for this tutorial. I am also using the Android Eclipse plugin. Eclipse can be found at www.eclipse.org/downloads and you can find the ADT plugin at developer.android.com/sdk/eclipse-adt.html

First create an Android project:




Since I will be demonstrating an animated sprite implementation we need a sprite strip. Here is the sprite strip we will be using:

  Animated Sprite


You have to add the asset to the project:

 

 

Code

Now we come to the actual coding. First we will create the AnimatedSprite class since that has the least amount of dependencies.



AnimatedSprite class

package com.android.tutorial;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

public class AnimatedSprite {
private Bitmap animation;
private int xPos;
private int yPos;
private Rect sRectangle;
private int fps;
private int numFrames;
private int currentFrame;
private long frameTimer;
private int spriteHeight;
private int spriteWidth;

public AnimatedSprite() {
sRectangle = new Rect(0, 0, 0, 0);
frameTimer = 0;
currentFrame = 0;
xPos = 80;
yPos = 200;
}

public void Initialize(Bitmap bitmap, int height, int width, int fps, int frameCount) {
this.animation = bitmap;
this.spriteHeight = height;
this.spriteWidth = width;
this.sRectangle.top = 0;
this.sRectangle.bottom = spriteHeight;
this.sRectangle.left = 0;
this.sRectangle.right = spriteWidth;
this.fps = 1000 / fps;
this.numFrames = frameCount;
}

public int getXPos() {
return xPos;
}

public int getYPos() {
return yPos;
}

public void setXPos(int value) {
xPos = value;
}

public void setYPos(int value) {
yPos = value;
}

public void Update(long gameTime) {
if( gameTime > frameTimer + fps) {
frameTimer = gameTime;
currentFrame += 1;

if( currentFrame >= numFrames ) {
currentFrame = 0;
}

sRectangle.left = currentFrame * spriteWidth;
sRectangle.right = sRectangle.left + spriteWidth;
}
}

public void draw(Canvas canvas) {
Rect dest = new Rect(getXPos(), getYPos(), getXPos() + spriteWidth,
getYPos() + spriteHeight);
canvas.drawBitmap(animation, sRectangle, dest, null);
}
}

Here we can see that AnimateSprite is a fairly simple class. It basically keeps track of what frame it's currently displaying and increments it during update if the appropriate time has elapsed. If it reaches the end of the list it will wrap around and begin rendering at the start of the list again.

I've abstracted the interaction between the animation logic and display logic with an interface called ISurface. Let's look at that interface now.




ISurface interface

package com.android.tutorial;

import android.graphics.Canvas;

public interface ISurface {
void onInitalize();
void onDraw(Canvas canvas);
void onUpdate(long gameTime);
}

Here we can see that the ISurface interface provides some basic functions. One for initialization which is called at startup or when the sprite is first initialized. The second is the draw function which is pretty self explanatory. The final function is the update function which is what is used by the animation thread to update it's animation(s).

Let's look at the AnimationThread now



AnimationThread class

package com.android.tutorial;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class AnimationThread extends Thread {
private SurfaceHolder surfaceHolder;
private ISurface panel;
private boolean run = false;

public AnimationThread(SurfaceHolder surfaceHolder, ISurface panel) {
this.surfaceHolder = surfaceHolder;
this.panel = panel;

panel.onInitalize();
}

public void setRunning(boolean value) {
run = value;
}

private long timer;

@Override
public void run() {

Canvas c;
while (run) {
c = null;
timer = System.currentTimeMillis();
panel.onUpdate(timer);

try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
panel.onDraw(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) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}

This class isn't doing much either, but what it is doing is very important. This class extends Thread. You could also create a runnable and implement threading that way. This way is more obvious which is why I chose it. The run function of this class is where all the work is being done. In this case the function retrieves the current system time and calls it's dependent panel's update function. It then obtains an exclusive lock on the containing surface's canvas and passes that canvas to the surface view's draw function. This call is synchronized to ensure stability.

Now we need to provide a concrete implementation of the ISurface interface for our tutorial. This will be the responsibility of the DrawablePanel class. Let's look at that class now:


DrawablePanel class

package com.android.tutorial;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public abstract class DrawablePanel
extends SurfaceView
implements SurfaceHolder.Callback, ISurface {

private AnimationThread thread;

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

this.thread = new AnimationThread(getHolder(), this);
}

@Override
public void onDraw(Canvas canvas) {
}

@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;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
}
 

This class extends SurfaceView and implements the SurfaceHolder.Callback interface. This is one technique to create a custom view. This class also implements our ISurface interface. It also has an AnimationThread. During construction the class does some standard plumbing to hookup the SurfaceHolder.Callback interface and starts the AnimationThread we looked at earlier. One of the parameters of the AnimationThread is an ISurface. This is where we can hook up our update and draw events to the animation thread. In this case we don't have to perform any logic for either event so they are left as empty functions. The final point of interest is surface creation or destruction. This will occur if your app goes out of focus for any reason. This could be because the user hit the home button, the app is being put to sleep, or the operation system is cleaning up memory for a larger foreground app. There are other reasons but those are the most common. In this case we have to make sure the thread no longer pumps animations and tries to draw to the canvas since the canvas will no longer exist. This is done by stopping the thread in the surfaceDestroyed callback. The thread is started in the surfaceCreated callback. This is the main reason for implementing the SurfaceHolder.Callback interface.

Finally edit the main activity class AndroidTutorial.java and replace the code with the following:

AndroidTutorial activity

package com.android.tutorial;


import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Bundle;

public class AndroidTutorial extends Activity {
AnimatedSprite animation = new AnimatedSprite();

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new AndroidTutorialPanel(this));
}

class AndroidTutorialPanel extends DrawablePanel {

public AndroidTutorialPanel(Context context) {
super(context);
}

@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
AndroidTutorial.this.animation.draw(canvas);
}

@Override
public void onInitalize() {
AndroidTutorial.this.animation.Initialize(
BitmapFactory.decodeResource(
getResources(),
R.drawable.explosion),
32, 32, 14, 7);
}

@Override
public void onUpdate(long gameTime) {
AndroidTutorial.this.animation.Update(gameTime);
}
}

}

In this class we create an inner class called AndroidTutorialPanel which extends the DrawablePanel class we looked at above. Since DrawablePanel is an ISurace AndroidTutorialPanel overrides the ISurface members and provides custom implementation. In this case the class creates, updates, and draws an instance of an AnimatedSprite which we looked at previously. One point of interest is the creation of the sprite. The constructor expects a resource id, a resource height and width, and finally some timing values. These values control how many times per second the animation is drawn and how many frames to draw. You can use this to create many animations from a single sprite sheet and customize how fast those animations run.

Conclusion

You can easily extend this framework with your own panels or animation threads even. You can even customize your animation sprites how you see fit such as adding velocity, acceleration, or even a complete physics framework. There is no limit accept your own imagination.

Here is a screen shot of the demo in action:

AttachmentSize
Block Diagram.img_assist_custom-300x388.png27.82 KB
New_Android_Project-09.27.2010-09.20.05PM.png42.79 KB
explosion.png3.73 KB
SS-09.27.2010-09.27.45PM.jpg17.42 KB
New_Java_Class-09.27.2010-09.34.44PM.img_assist_custom-300x359.png46.99 KB
New_Java_Interface-09.27.2010-09.37.16PM.img_assist_custom-300x299.png73.09 KB
New_Java_Class-09.27.2010-09.41.27PM.img_assist_custom-300x359.png47.02 KB
New_Java_Class-09.27.2010-09.39.59PM.img_assist_custom-300x359.png46.96 KB
5554GPSTester-09.29.2010-08.52.43AM.img_assist_custom-300x213.png36.6 KB
AndroidGameTutorial.zip6.47 KB
Published at DZone with permission of its author, Scott Mccain.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

And Bod replied on Tue, 2010/10/05 - 4:27am

It would have bee nice if your example didn t mix in the controller logic with the view , I mean it s not up to the view class to pace the fps / ups update times. Also why not address some of the Android specific issues when creating a game, like the 16 / 24 mb memory limit and how to cope with that, especially when loading images. Also what should be done when the user presses home or exist and then relaunches the game.

Scott Mccain replied on Tue, 2010/10/05 - 4:59pm in response to: And Bod

Thank you for the feedback And. To address your specific questions, the idea was to present the user with a basic framework from which she can extend upon for game development. My goal wasn't a treatise on MVC. I appreciate that you are cognizant of the limitations of this platform as well, but, again, this article is more geared for someone who wants to get their feet wet in Android game development. The examination of the platforms strengths and weaknesses could very well be another article in and of itself. My hope is that someone can read this article and build upon it (change it, rewrite it complete, whatever) and make something better than I could have ever dreamed of. My goal is to supply the wings, it's up to the individual to actually use them and fly. :) Thanks!

Bastian Buch replied on Mon, 2010/10/11 - 4:16pm

Thank you for your post. It is an helpful skeleton to start with.

 You should add those lines to AnimatedSprite.draw to clear the screen before the next tile is drawn - looks better ;)

Paint p = new Paint();
		p.setColor(Color.BLACK);
		canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p);

 

Sébastien Dubois replied on Tue, 2010/11/16 - 5:49pm in response to: Bastian Buch

Hey Bastian, An easier approach to clear the screen is to use the following, which does basically the same thing: canvas.drawColor(Color.BLACK);

Larry Hart replied on Wed, 2011/03/23 - 3:58pm

So where would the game logic/engine go? Btw this is good; no other site comes this close to Android game programming :)

Dan Ju replied on Sun, 2011/07/31 - 5:11am

any ideas why my .png image is being printed out at 150% on the 2.1 android emulator?

Poopy Pants replied on Fri, 2011/09/09 - 7:11am

If you actually run this app you will see a glitchy animation.  This is because the image provided only has 6 frames, yet the author has incorrectly specified 7 frames on line 38 of AndroidTutorial.java.
Also there are some minor grammatical errors in his write-up - he is confused about the proper use of it's/its and accept/except.
But overall, a good tutorial.

Ahmed Hashem replied on Tue, 2014/07/22 - 2:25am

it's so difficult but i will try to start it

many thanx

http://www.photolightpro.com/

Bez Sil replied on Mon, 2014/08/04 - 9:07am

The code runs smoothly but displays nothing on my android device nor on my emulator. I get no app icon on my device.  Am I supposed to see the graphics right away?

Sam Su replied on Tue, 2014/08/26 - 11:46pm in response to: Bez Sil

Either does not work on my Android 4.4.2 ulefone android device ,will try again later.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.