Android Basic Game Loop






4.80/5 (18 votes)
This article is explaining how to implement a basic game fundamentals on Android platform.
Source:
GitHub : https://github.com/Pavel-Durov/CodeProject-Android-Basic-Game-Loop
Direct:
Table of Contents
- Introduction
- Basic game concept
- Displayed objects
- Game Activity
- OnTouch methods
- GameView class
- DisplayThread class
- GameEngine class
- Summary
Introduction
This article will explain how to create a simple game on Android platform using SurfaceView
class only by managed Java code.
We will examine the basic concepts and create a display interaction by touch events.
We are going to implement the fundamental stage of a 2D bubble game, which includes a cannon shooting bubbles to our touches direction.
Your game will look something like this:
Basic Game concept
We need to establish several modules in our program:
1. Get the input data from the user.
2. Store that data somewhere in our program.
3. Display the result on the screen.
Program modules communication diagram
DisplayThread loop diagram
Displayed objects
Each figure that is displayed on the screen of our Android device is a java object with its own logic. In our game we have 3 objects of that type: Cannon, Bubble and Aim.
Every object has its x and y coordinates that identifies its location on the screen. Objects that constantly move, such as bubbles, keep their deltas which identify their direction.
Game Activity
We are going to create an activity that will listen to touch events (user inputs) and set its content view as custom view that we will implement afterwards.
Let’s examine our GameActivity
onCreate() method:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//sets the activity view as GameView class
SurfaceView view = new GameView(this, AppConstants.GetEngine());
setContentView(view);
getActionBar().hide();
}
Regularly we will see something like this in onCreate
method:
//setting content view from resource xml file
setContentView(R.layout.main_activity);
This method sets the xml layout as the content view of the activity, which will be generated to a View
automatically.
In our case, we are setting the content view as our custom View class, because we need to implement our own methods and modifications on the View
that cannot be performed by the regular convention.
Next, we will listen to touch events by overriding the onTouchEvent(MotionEvent event)
method and accordingly to MotionEvent
value we’ll call our methods.
OnTouch methods
/*activates on touch move event*/
private void OnActionMove(MotionEvent event)
{
int x = (int)event.getX();
int y = (int)event.getY();
if(GetIfTouchInTheZone(x, y))
{
AppConstants.GetEngine().SetCannonRotaion(x, y);
}
AppConstants.GetEngine().SetLastTouch(event.getX(), event.getY());
}
/*activates on touch up event*/
private void OnActionUp(MotionEvent event)
{
int x = (int)event.getX();
int y = (int)event.getY();
if(GetIfTouchInTheZone(x, y))
{
AppConstants.GetEngine().SetCannonRotaion(x, y);
AppConstants.GetEngine().SetLastTouch(FingerAim.DO_NOT_DRAW_X
,FingerAim.DO_NOT_DRAW_Y);
AppConstants.GetEngine().CreateNewBubble(x,y);
}
}
/*activates on touch down event*/
private void OnActionDown(MotionEvent event)
{
AppConstants.GetEngine()
.SetLastTouch(event.getX(), event.getY());
}
These methods are invoked as a user touches the screen of our activity.
They call appropriate methods in our GameEngine
object. That will change our business logic and create an interaction between the user and our application.
GameView class
GameView
class extends SurficeView
that extends View
class, that’s why we could set it as our activity content view.
This class implements the logic for our display.
When created (when called new in our GameActvity
), GameView
initializes and starts the DisplayThread
object.
More on SurficeView
class:
http://developer.android.com/reference/android/view/SurfaceView.html
Our GameView
implements SurfaceHolder.Callback
interface:
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
{
/*DO NOTHING*/
}
In this method, we don’t do anything, since it’s not in our interest. But, we have to implement it in our class because it is a part of the interface.
All the overridden methods in GameView are part of the SurfaceHolder.Callback interface
.
@Override
public void surfaceCreated(SurfaceHolder arg0)
{
//Starts the display thread
if(!_displayThread.IsRunning())
{
_displayThread = new DisplayThread(getHolder(), _context);
_displayThread.start();
}
else
{
_displayThread.start();
}
}
surfaceCreated
()
is invoked when GameActivity
content view is initialized, when called OnCreate()
, onStart()
or onResume()
methods, it simply starts the DisplayThread
.
@Override
public void surfaceDestroyed(SurfaceHolder arg0)
{
//Stop the display thread
_displayThread.SetIsRunning(false);
AppConstants.StopThread(_displayThread);
}
SurfaceDestroyed
is called when GameActivity
OnPause(), OnStop() or OnDestroy() life cycle methods are called, this method stops the DisplayThread
.
DisplayThread class
DisplayThread
is the one that refreshes our screen. This magic happens in its run()
method.
DisplayThread Screen Rendering
@Override
public void run()
{
//Looping until the boolean is false
while (_isOnRun)
{
//Updates the game objects buisiness logic
AppConstants.GetEngine().Update();
//locking the canvas
Canvas canvas = _surfaceHolder.lockCanvas(null);
if (canvas != null)
{
//Clears the screen with black paint and draws
//object on the canvas
synchronized (_surfaceHolder)
{
canvas.drawRect(0, 0,
canvas.getWidth(),canvas.getHeight(), _backgroundPaint);
AppConstants.GetEngine().Draw(canvas);
}
//unlocking the Canvas
_surfaceHolder.unlockCanvasAndPost(canvas);
}
//delay time
try
{
Thread.sleep(DELAY);
}
catch (InterruptedException ex)
{
//TODO: Log
}
}
}
run()
method will loop in its while
loop as long as the boolean _isOnRun
variable is not set to false, which happens only by invoking the SetIsRunning(boolean state)
method.
SetIsRunning(boolean state)
is called only from SurfaceHolder.Callback
interface methods that implemented in our GameView
class.
First thing that run()
method does as it enters the while
loop, is to call the Update()
method in the GameEngine
object. That will update our business logic (in our case advance the balloons).
Next it locks the canvas (from our SurficeView
class), paints the whole view with the black background and passes the canvas to GameEngine Draw()
method. That will display our objects with updated parameters on our display.
Finally it unlocks the canvas, and goes to sleep for a specified amount of time, that calls fps (frames per second).
Our brain process about 20 frames per second, our code refreshes the screen every 45 millisecond, which makes it 22 fps.
In our implementation we are calling Thread.sleep()
with a hardcoded value, expecting it to stop for a while and then continue the logic execution.
We need to be aware of the fact that the time that it takes to render and update the game is not consistent.
For example, if you have one bubble to advance, it wouldn’t take the same time as to advance 550 bubbles on the screen. That makes our game rendering not smooth, since loop execution time is different from one another.
This problem can be solved by taking timestamp at the beginning of the loop and subtracting it from the sleep time, which will make our each loop execution time the same.
*I didn’t include that solution in our DisplayThread
implementation because I wanted to keep it simple.
GameEngine class
This object is responsible for all game business logic, it keeps the instances of all displayed objects (in our case: Bubbles, Aim and the Cannon).
Update() and Draw(Canvas canvas) methods
public void Update()
{
AdvanceBubbles();
}
Update()
method updates all our game’s objects, it advances the bubbles. However, it is not updating the Cannon
object, since its rotation value is changes only when the user touches the screen.
public void Draw(Canvas canvas)
{
DrawCanon(canvas);
DrawBubles(canvas);
DrawAim(canvas);
}
Draw(Canvas canvas)
method is called after the Update()
is called from the DisplayThread
class. Draw(Canvas canvas)
method is drawing all objects that are relevant to the game display, on the given canvas.
Other GameEngine methods
public void SetCannonRotaion(int touch_x, int touch_y) { float cannonRotation = RotationHandler .CannonRotationByTouch(touch_x, touch_y, _cannon); _cannon.SetRotation(cannonRotation); }
SetCannonRotaion()
method is called from the GameActivity when touch events occurred.
We are rotating the bitmap in direct relation to the touch event coordinates.
The first stage is the user input, which invokes SetCannonRotation()
in the GameEngine
that calls RotationHandler.CannonRotationByTouch()
and _cannon.SetRotation()
with the given result.
public static float CannonRotationByTouch(int touch_x, int touch_y, Cannon cannon)
{
float result = cannon.GetRotation();
if(CheckIfTouchIsInTheZone(touch_x, touch_y, cannon))
{
if(CheckIsOnLeftSideScreen(touch_x))
{
int Opposite = touch_x - cannon.GetX();
int Adjacent = cannon.GetY() - touch_y;
double angle = Math.atan2(Opposite, Adjacent);
result = (float)Math.toDegrees(angle);
}
else
{
int Opposite = cannon.GetX() - touch_x;
int Adjacent = cannon.GetY() - touch_y;
double angle = Math.atan2(Opposite, Adjacent);
result = ANGLE_360 - (float)Math.toDegrees(angle) ;
}
}
return result;
}
CannonRotationByTouch()
method uses a little bit of geometry, it identifies whether the touch occurred on the right or on the left side of the cannon, and accordingly to its location, calculates the angle.
We are using tangent to calculate the angle, since we can calculate the adjacent and opposite length of the created right triangle between the cannon location and the touch coordinates.
Notice that as we rotate the cannon bitmap using Matrix object:
public static Bitmap RotateBitmap(Bitmap source, float angle)
{
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap
(
source,
0, 0,
source.getWidth(),
source.getHeight(),
matrix,
true
);
}
We are fitting the rotated cannon to our original width and height. That’s why our cannon (android icon) is getting smaller on bitmap rotation.
That is not right to do so; we should fit the new dimensions of the rotated image appropriately. We did not implement it here, just to simplify our code.
public void CreateNewBubble(int touchX, int touchY)
{
synchronized (_sync)
{
_bubles.add
(
new Bubble
(
_cannon.GetX(),
_cannon.GetY(),
_cannon.GetRotation(),
touchX,
touchY
)
);
}
}
CreateNewBubble()
is called on touch event as well, it creates new Bubble object and added to the bubble list, on which the Update()
and Draw()
method will iterate.
Summary
We created a very basic game, of course there is much more that we could implement, right now it’s not a very attractive game for a user. But if you understood the basic idea of the game loop, you can continue to develop on that platform as you wish.
It can be easily changed to any other 2D game concept, keeping the game loop fundamentals as is, by changing only the GameEngine
implementation of the Update
and Draw
methods and involved objects.