Android Game Framework using Properties

Introduction

See the preceding page for notes on language features and a version of the code with getter and setter methods for private variables (fields). Here we use public properties and private fields. The read and write keywords map each property to the private field containing its value. You can instead map a property to an accessor method e.g. with the code write setWidth instead of write fWidth for the Width property of the AlienObject class below. You might then include in the setWidth method some validation to ensure acceptable values. The code that uses the property would still work. (Instead of using properties you can handle public fields directly. Although such code is more difficult to modify and is considered to be bad practice, it saves time for a beginner).

In the read specifier for Bounds we take advantage of Oxygene's in-line facility to write this neat code:
property Bounds : Rect read new Rect(X, Y,  X + Width, Y + Height);

The absence of a write specifier makes Bounds a read-only property. Note that it is conventional to begin the property identifier with a capital letter and that otherwise the compiler will provide a warning.

The code of MainActivity.pas

namespace org.me.game_framework2;

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 fCollided : Boolean := false;  
  protected
    method onCreate(savedInstanceState : Bundle); override;
    method ButtonOnClick(v : View);
  public
    class property Collided : Boolean read fCollided write fCollided;
  end;

  DrawingPanel = class(SurfaceView, SurfaceHolder.Callback)
  private   
    fAlien, fFinalAlien : AlienObject;   
    alien_id : Integer;
    imgAlien : Drawable;
    fBMP : Bitmap;
    rectObstacle : Rect := new Rect(150, 250, 250, 260);
    paint : Paint;
    fThread : PanelThread;
  public
    property PThread : PanelThread read fThread write fThread;
    property Alien : AlienObject read fAlien write fAlien;
    property FinalAlien : AlienObject read fFinalAlien write fFinalAlien;
    property BMP : Bitmap read fBMP write fBMP;
    constructor(context : Context);
    method onDraw(canvas : Canvas); override;
    method surfaceChanged(arg0 : SurfaceHolder; arg1, arg2, arg3 : Integer);
    method surfaceCreated(holder : SurfaceHolder);
    method surfaceDestroyed(holder : SurfaceHolder);
  end;

  AlienObject = class(Object)
  private
    fX, fY, fWidth, fHeight : Integer; 
    fCrashed : Boolean := false;
  public 
    property X : Integer read fX write fX;
    property Y : Integer read fY write fY;
    property Width : Integer read fWidth write fWidth;
    property Height : Integer read fHeight write fHeight; 
    property Bounds : Rect read new Rect(X, Y,  X + Width, Y + Height);
    constructor(newX, newY, newWidth, newHeight : Integer);
  end;

  PanelThread = class(Thread)
  private
    fSurfaceHolder : SurfaceHolder;
    fPanel : DrawingPanel;
    fRun : Boolean := false;
  public
    property S_Holder : SurfaceHolder read fSurfaceHolder write fSurfaceHolder;
    property D_Panel : DrawingPanel read fPanel write fPanel;
    property IsRunning : Boolean read fRun write fRun;
    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.Alien;
  if v.equals(btnUp) then
    tempAlien.Y := tempAlien.Y - DY
  else if v.equals(btnDown) then
    tempAlien.Y := tempAlien.Y + DY
  else if v.equals(btnLeft) then
    tempAlien.X := tempAlien.X - DX
  else if v.equals(btnRight) then
    tempAlien.X := tempAlien.X + DX
  else if v.equals(btnRestart) and Collided then
    begin
      tempAlien.X := 10;
      tempAlien.Y := 20;
      setCollided(false);
    end; 
   dp.Alien := 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;

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);
  imgAlien := Resources.getDrawable(alien_id);
  BMP := BitmapDrawable(imgAlien).getBitmap;
  Alien := new AlienObject(10, 20, BMP.Width, BMP.Height); 
  FinalAlien := new AlienObject(0, 0, BMP.Width, BMP.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.Collided then
    begin
      canvas.drawBitmap(BMP, Alien.X, Alien.Y, paint);
      if Rect.intersects(Alien.Bounds, rectObstacle) then
        begin 
          FinalAlien.X := Alien.X;
          FinalAlien.Y := Alien.Y;
          MainActivity.Collided := true;
        end; 
    end
  else
    begin
      canvas.drawBitmap(BMP, FinalAlien.X, FinalAlien.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);
begin
  setWillNotDraw(false); //Allows us to use postInvalidate to call onDraw
  PThread := new PanelThread(getHolder, self); //Start the thread that
  PThread.setRunning(true);                    //will make calls to
  PThread.start;                               //onDraw
end;

method DrawingPanel.surfaceDestroyed(holder : SurfaceHolder);
begin
  try
    PThread.setRunning(false); //Tells thread to stop
    PThread.join; //Removes thread from mem.
  except
    on e : InterruptedException do
      begin
      end;
  end;
end; 

constructor AlienObject(newX, newY, newWidth, newHeight : Integer);
begin
  fX := newX;
  fY := newY;
  fWidth := newWidth;
  fHeight := newHeight;
end;

constructor PanelThread(surfaceHolder : SurfaceHolder; panel : DrawingPanel);
begin
  S_Holder := surfaceHolder;
  D_Panel := panel;
end;

method PanelThread.setRunning(running : Boolean);  //Allow us to stop the thread
begin
  IsRunning := running;
end;

method PanelThread.run;
var
  c : Canvas;
  tempAlien : AlienObject;
begin
  while IsRunning do //When setRunning(false) occurs, IsRunning is
    begin         //set to false and loop ends, stopping thread
       c := nil;
       try
         c := S_Holder.lockCanvas(nil);
         locking S_Holder do
           begin
             tempAlien := D_Panel.Alien;
             tempAlien.X := tempAlien.X + 1;
             if (tempAlien.X + tempAlien.Width) > D_Panel.Right then 
               tempAlien.X := D_Panel.Left;
             tempAlien.Y := tempAlien.Y + 1;
             if (tempAlien.Y + tempAlien.Height) > S_Holder.SurfaceFrame.bottom  then 
               tempAlien.Y := S_Holder.SurfaceFrame.top;
                 D_Panel.Alien := tempAlien;
             D_Panel.postInvalidate;
             sleep(5);
           end;
       finally
         if not (c = nil) then
           S_Holder.unlockCanvasAndPost(c);
       end;
    end;
end;

end.
 

Strings File

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">Game Framework</string>
</resources>
Programming - a skill for life!

Using a Thread and Android canvas for game development in Oxygene for Java