Click here to Skip to main content
15,868,016 members
Articles / Mobile Apps / Android

Android Basic Game Loop

Rate me:
Please Sign up or sign in to vote.
4.80/5 (20 votes)
9 Oct 2014CPOL6 min read 76.1K   2.3K   37   11
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

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:

Image 1

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

Image 2

DisplayThread loop diagram

Image 3

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:

Java
@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:

Java
//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

Java
/*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:

Java
@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.

Java
@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.

Java
@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

Java
@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

Java
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.

Java
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.

 

Java
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.

Image 4

Notice that as we rotate the cannon bitmap using Matrix object:

Java
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
            );
}

Image 5

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.

Java
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThis is not working !!! Pin
Member 1247913522-Apr-16 13:34
Member 1247913522-Apr-16 13:34 
AnswerRe: This is not working !!! Pin
Pavel Durov29-Apr-16 23:49
professionalPavel Durov29-Apr-16 23:49 
This code suppos to start a thread in GameView constructor, and to destroy it as surfaceDestroyed method called. Anyway it's not a multithreading tutorial - I've tried to explain the basic game loop, which you can build manually. Didn’t mention lifecycle event either…
Actually I don’t remember if I tried to go back by pressing back button (since it was written 2 years ago) – but it’s really not that relevant to the article subject.
QuestionCannon Pin
Member 1228309817-Feb-16 9:57
Member 1228309817-Feb-16 9:57 
QuestionChanging the layout Pin
Member 1151588318-Mar-15 0:05
Member 1151588318-Mar-15 0:05 
AnswerRe: Changing the layout Pin
Pavel Durov22-Mar-15 2:04
professionalPavel Durov22-Mar-15 2:04 
QuestionProgram Pin
SergVoloshyn28-Nov-14 10:31
SergVoloshyn28-Nov-14 10:31 
AnswerRe: Program Pin
Pavel Durov11-Dec-14 23:47
professionalPavel Durov11-Dec-14 23:47 
AnswerRe: Program Pin
Pavel Durov7-Jan-15 20:02
professionalPavel Durov7-Jan-15 20:02 
Question[My vote of 2] GameLoop as Unfortunately Stoped Pin
Shreyash S Sarnayak8-Nov-14 6:23
Shreyash S Sarnayak8-Nov-14 6:23 
AnswerRe: [My vote of 2] GameLoop as Unfortunately Stoped Pin
Pavel Durov7-Jan-15 19:58
professionalPavel Durov7-Jan-15 19:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.