Android Game Framework
This is an attempt to provide the skeleton of an Android motion graphics game which you will be able to adapt for your own games. It is based on Daniel Nadeau's beginner's tutorial, Android Canvas. We show how to make an image move and how you can use standard buttons to control the motion. As usual, changing existing code is much easier than producing your own program from scratch. We learned with applets to carry out calculations in the run method of the Runnable interface then to use the results in the paint method. Here we carry out calculations in the run method of our thread (and also in a button event-handling method) and display the results of the calculations in the onDraw method of the SurfaceView (our DrawingPanel class). The run method calls onDraw with the instruction postInvalidate. Daniel finds that this performs better than calling onDraw directly.
The program demonstrates several facilities of the language:
- setting the layout in code (instead of using a layout file) as demonstrated in the sample app HelloWorldManualUI supplied with Oxygene for Java;
- displaying text with the drawText method of a canvas;
- obtaining an image from resources (one medium size alien.png image in the folder \res\drawable-mdpi) and displaying it with the drawBitmap method of a canvas;
- object-oriented code with private fields and public getter and setter methods in a Java-like manner (instead of using properties as demonstrated on the following page);
- callbacks to instruct the called method to call a method implemented by the calling class. The interface SurfaceHolder.Callback has the methods surfaceChanged, surfaceCreated and surfaceDestroyed. All methods in the interface will be called and therefore must be implemented even if, as in surfaceChanged, the method body is empty.
The aim of this trivial game is either to guide the alien away from the yellow obstacle, or towards it when you have had enough. We found the motion graphics in the Nexus S emulator to be painfully slow so now run the game in AndroBOX.

Game playing in the AndroBOX VM
The next screenshot shows the display of text as the game finishes.

End of game
- create your own player and/or alien and/or missile classes similar to AlienObject;
- control their movement by performing calculations in the thread run method and in the ButtonOnClick method;
- render them all in the onDraw method.
This is our first attempt at a framework and we expect that some of you will offer suggestions for improvement as you gain experience in game development. We look forward to feedback and to publishing our first student Android apps later this year (2013).
Download here a zip file containing the code below (in game_framework1.txt), the code of the program adapted to use properties that we show on the next page (in game_framework2.txt) and alien.bmp.
The code of MainActivity.pas
namespace org.me.game_framework1; interface uses android.app, android.os, android.util, android.view, android.widget, android.content.*, android.graphics.*, java.util.concurrent; type MainActivity = public class(Activity) private const DX = 30; DY = 30; var dp : DrawingPanel; btnUp, btnDown, btnLeft, btnRight, btnRestart : Button; myAlien : AlienObject; class var collided : Boolean := false; // Must be class var to be acccessible by getCollided and setCollided protected method onCreate(savedInstanceState : Bundle); override; method ButtonOnClick(v : View); public class method getCollided : Boolean; class method setCollided(collision : Boolean); end; DrawingPanel = class(SurfaceView, SurfaceHolder.Callback) private _alien, finalAlien : AlienObject; alien_id : Integer; alien : Drawable; bmpAlien : Bitmap; rectObstacle : Rect := new Rect(150, 250, 250, 260); paint : Paint; _thread : PanelThread; public constructor(context : Context); method onDraw(canvas : Canvas); override; method surfaceChanged(arg0 : SurfaceHolder; arg1, arg2, arg3 : Integer); method surfaceCreated(holder : SurfaceHolder); method surfaceDestroyed(holder : SurfaceHolder); method getBMP : Bitmap; method setAlienObject(ao : AlienObject); method getAlienObject : AlienObject; method setThread(pt : PanelThread); method getThread : PanelThread; end; AlienObject = class(Object) private x, y, width, height : Integer; crashed : Boolean := false; public constructor(newX, newY, newWidth, newHeight : Integer); method set_X(newX : Integer); method set_Y(newY : Integer); method get_X : Integer; method get_Y : Integer; method get_Width : Integer; method get_Height : Integer; method getBounds : Rect; end; PanelThread = class(Thread) private _surfaceHolder : SurfaceHolder; _panel : DrawingPanel; _run : Boolean := false; public constructor (surfaceHolder : SurfaceHolder; panel : DrawingPanel); method setRunning(running : Boolean); //Allow us to stop the thread method run; override; end; implementation method MainActivity.ButtonOnClick(v : View); var tempAlien : AlienObject; begin tempAlien := dp.getAlienObject; if v.equals(btnUp) then tempAlien.set_Y(tempAlien.get_Y - DY) else if v.equals(btnDown) then tempAlien.set_Y(tempAlien.get_Y + DY) else if v.equals(btnLeft) then tempAlien.set_X(tempAlien.get_X - DX) else if v.equals(btnRight) then tempAlien.set_X(tempAlien.get_X + DX) else if v.equals(btnRestart) and getCollided then begin tempAlien.set_X(10); tempAlien.set_Y(20); setCollided(false); end; dp.setAlienObject(tempAlien); end; method MainActivity.onCreate(savedInstanceState : Bundle); var mainLayout, buttonLayout : LinearLayout; begin inherited; mainLayout := new LinearLayout(Self); //Make a linear layout that fills the screen and centres child views horizontally & vertically mainLayout.Orientation := LinearLayout.VERTICAL; mainLayout.LayoutParams := new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); mainLayout.Gravity := Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL; //Make a linear layout for a row of four buttons buttonLayout := new LinearLayout(Self); buttonLayout.Orientation := LinearLayout.HORIZONTAL; buttonLayout.LayoutParams := new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); buttonLayout.Gravity := Gravity.CENTER_VERTICAL or Gravity.FILL_HORIZONTAL; //Create 4 buttons btnUp := new Button(Self); btnUp.Text := 'Up'; btnUp.Width := 100; btnDown := new Button(Self); btnDown.Text := 'Down'; btnDown.Width := 100; btnLeft := new Button(Self); btnLeft.Text := 'Left'; btnLeft.Width := 100; btnRight := new Button(Self); btnRight.Text := 'Right'; btnRight.Width := 100; btnRestart := new Button(self); btnRestart.Text := 'Restart'; btnRestart.Width := 100; //Add buttons to their layout buttonLayout.addView(btnUp); buttonLayout.addView(btnDown); buttonLayout.addView(btnLeft); buttonLayout.addView(btnRight); buttonLayout.addView(btnRestart); mainLayout.addView(buttonLayout); //Adds button layout to main layout dp := new DrawingPanel(self); //Creates drawing panel mainLayout.addView(dp); //Adds drawing panel to main layout ContentView := mainLayout; //Set listeners btnUp.setOnClickListener(@ButtonOnClick); btnDown.setOnClickListener(@ButtonOnClick); btnLeft.setOnClickListener(@ButtonOnClick); btnRight.setOnClickListener(@ButtonOnClick); btnRestart.setOnClickListener(@ButtonOnClick); end; class method MainActivity.getCollided : Boolean; begin result := collided; end; class method MainActivity.setCollided(collision : Boolean); begin collided := collision; end; constructor DrawingPanel(context : Context); begin inherited; getHolder.addCallback(self); paint := new Paint; paint.setStyle(paint.Style.FILL); alien_id := Resources.getIdentifier('alien', "drawable", context.PackageName); alien := Resources.getDrawable(alien_id); bmpAlien := BitmapDrawable(alien).getBitmap; _alien := new AlienObject(10, 20, bmpAlien.Width, bmpAlien.Height); finalAlien := new AlienObject(0, 0, bmpAlien.Width, bmpAlien.Height); end; method DrawingPanel.onDraw(canvas : Canvas); begin paint.setColor($ff000060); canvas.drawPaint(paint); // Makes the entire canvas dark blue paint.setColor(Color.YELLOW); canvas.drawRect(rectObstacle, paint); if not MainActivity.getCollided then begin canvas.drawBitmap(getBMP, _alien.get_X, _alien.get_Y, paint); if Rect.intersects(_alien.getBounds, rectObstacle) then begin finalAlien.set_X(_alien.get_X); finalAlien.set_Y(_alien.get_Y); MainActivity.setCollided(true); end; end else begin canvas.drawBitmap(getBMP, finalAlien.get_X, finalAlien.get_Y, paint); paint.setColor(Color.MAGENTA); paint.TextSize := 48; canvas.drawText('Game Over', 100, 250, paint); end; end; method DrawingPanel.surfaceChanged(arg0 : SurfaceHolder; arg1, arg2, arg3 : Integer); begin end; method DrawingPanel.surfaceCreated(holder : SurfaceHolder); var newThread : PanelThread; begin setWillNotDraw(false); //Allows us to use postInvalidate to call onDraw newThread := new PanelThread(getHolder, self); //Start the thread that newThread.setRunning(true); //will make calls to newThread.start; //onDraw setThread(newThread); end; method DrawingPanel.surfaceDestroyed(holder : SurfaceHolder); begin try getThread.setRunning(false); //Tells thread to stop getThread.join; //Removes thread from mem. except on e : InterruptedException do begin end; end; end; method DrawingPanel.getBMP : Bitmap; begin result := bmpAlien; end; method DrawingPanel.setAlienObject(ao : AlienObject); begin _alien := ao; end; method DrawingPanel.getAlienObject : AlienObject; begin result := _alien; end; method DrawingPanel.setThread(pt : PanelThread); begin _thread := pt; end; method DrawingPanel.getThread : PanelThread; begin result := _thread; end; constructor AlienObject(newX, newY, newWidth, newHeight : Integer); begin x := newX; y := newY; width := newWidth; height := newHeight; end; method AlienObject.set_X(newX : Integer); begin x := newX; end; method AlienObject.set_Y(newY : Integer); begin y := newY; end; method AlienObject.get_X : Integer; begin result := x; end; method AlienObject.get_Y : Integer; begin result := y; end; method AlienObject.get_Width : Integer; begin result := width; end; method AlienObject.get_Height : Integer; begin result := height; end; method AlienObject.getBounds : Rect; begin result := new Rect(get_X, get_Y, get_X + width, get_Y + height); end; constructor PanelThread(surfaceHolder : SurfaceHolder; panel : DrawingPanel); begin _surfaceHolder := surfaceHolder; _panel := panel; end; method PanelThread.setRunning(running : Boolean); //Allow us to stop the thread begin _run := running; end; method PanelThread.run; var c : Canvas; tempAlien : AlienObject; tempX, tempY : Integer; begin while _run do //When setRunning(false) occurs, _run is begin //set to false and loop ends, stopping thread c := nil; try c := _surfaceHolder.lockCanvas(nil); locking _surfaceHolder do begin tempAlien := _panel.getAlienObject; tempX := tempAlien.get_X + 1; if (tempX + tempAlien.get_Width) > _panel.Right then tempX := _panel.Left; tempY := tempAlien.get_Y + 1; if (tempY + tempAlien.get_Height) > _surfaceHolder.SurfaceFrame.bottom then tempY := _surfaceHolder.SurfaceFrame.top; tempAlien.set_X(tempX); tempAlien.set_Y(tempY); _panel.setAlienObject(tempAlien); _panel.postInvalidate; sleep(5); end; finally if not (c = nil) then _surfaceHolder.unlockCanvasAndPost(c); end; end; end; end.
Strings File
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Game Framework</string> </resources>