Download now




BLOG

Introduction

Date: 01 Sep 2016

Building Three Point Bandits has been a challenging journey that took well over a thousand hours of tinkering. The entire game, including the gaming loop, physics, animation and sound engines, were designed and constructed in a piecemeal manner from the ground up. This involved multiple coding and re-coding of many elements; the physics engine itself was torn down and rebuilt a dozen times to improve performance and resolve faults.

In this blog, I attempt to recall some of the missteps and lessons gained from this exercise to hopefully save aspiring developers many, many hours of frustration. In a way, the official title of this blog should be ’How to build a game that doesn’t crash all the time.’ A situation that is unfortunately not uncommon, even for name titles coming out of publishing houses. Obviously, all parts may not apply to your situation, but I hope readers will at least get some value from this blog.

As a disclaimer, this is not a how-to instructional on Android development. Others have already covered it somewhat adequately. Here, I discuss concepts that I feel are important, and by listing them, I hope to turbo charge the learning curve for fellow enthusiasts.

TPB began life as I lay in bed one night, thinking about planets and how they interacted with each other. I had earlier watched a documentary on this topic and it stuck in my brain. Having zero mobile development experience to draw from, I was foolhardy enough to think it would be fun (and easy) to model gravity on my Android tablet.

Jumping out of bed to install Eclipse while frantically skimming through tutorials and examples that I don’t now recall, I relied on Java programming memories from the distant past to turn out my first app. It could be argued that this is also the first iteration of Three Point Bandits.


Proof of concept


Despite being quite pleased with this first effort, I held back the impulse to dive in. Instead, I would start with something easy. The orange ball in ’Bouncy’ went on to form the basis for the idea of a basketball shooting game. It is also the default ball in the finished product. As development progressed, the realisation that it would not be a quick project became evident.


Android Fundamentals: UI Thread

The Android operating system is a multithreaded environment. The system manages the UI main thread, which is responsible for drawing the screen, detecting inputs and all other management tasks related to the mobile device. Typically, this thread runs 60 times per second, therefore it is important to not block or slow it down too much.

The idea of the UI main thread is important because certain tasks can only be started here:

Fortunately, Android provides functions and the Handler class to bypass this rule. But, it is worth bearing in mind the underlying platform architecture to help you design better systems. This becomes apparent when we introduce the gaming loop later, which runs on a different thread.

TIP: Use runOnUiThread() to effectively call any function from any thread. It is not elegant, but works by sending the task to the UI loop for execution at a later time.


Android Fundamentals: View class

Date: 08 Sep 2016

The View class is your primary interface with the mobile device. It detects user interaction with the touch screen and it lets you control how the screen is drawn. This class is, more or less, the UI loop (or UI main thread). Override these to functions to perform custom game operations:

TIP: Activity and View classes form the core of the game engine.

public class GameActivity extends Activity
{
   // Create the View
   protected GameView myGameView = new GameView(this);

   // Inform system where the View is to handle screen touch and draw events
   setContentView(myGameView);
   ...
}

public class GameView extends View
{
   ...
   @Override
   public boolean onTouchEvent(MotionEvent event)
   {
      // Retrieve user touch events here
      int action = event.getAction();
      float currentX = event.getX();
      float currentY = event.getY();
      ...

   }
   ...

   @Override
   protected void onDraw(Canvas canvas)
   {
      // Draw screen here
      ...

   }
   ...

   // This is called by the gaming loop to advance the game by one step
   protected void stepGame()
   {
      // 1) Process touch events here
      // 2) Game processing here
      // 3) Calculate current screen for drawing
      ...

   }
   ...
}

Going through the literature, it is not uncommon to encounter mentions of a class called SurfaceView that supposedly allows you to control when to draw frames. This comes in handy if your screen is so complex that drawing it holds up the UI loop. Using SurfaceView, you continuously draw the frame on a separate thread and throw it to the screen when done. Some claim to achieve higher frame rates using this method.

I found frame rates decreased from around 60 FPS to 24 FPS using this method. No gains were had even on older devices that start with much lower frames. My guess is this class is no longer effective compared to the speed of modern graphics hardware. Further reading reavealed that Android had markedly improved the graphics pipeline over many iterations. This likely contributed to the performance gains of the View class as observed.

Specifically, improved low-level buffering of graphic assets probably gives the View class an advantage over the SurfaceView class. SurfaceView manages its own drawing and, by implication, does not utilise this inbuilt feature. This is especially pertinent because TPB is a memory-intensive app that relies heavily on raster graphics. Efficient access to drawing objects has shown to be an important factor in graphics performance. As such, the standard View class will likely produce faster drawing speeds if hardware acceleration is enabled (without going into OpenGL ES).

TIP: One android pixel uses 4 bytes (red, green, blue and alpha). That means a Nexus 7 (2012) device uses 3.9 MB to cover one screen while the 2013 version needs 8.8 MB to do the same. When background layering and scolling, and character animation are taken into account, it is not hard to see the memory adding up quickly. On the Nexus 7 (2012), a typical TPB game level takes up more than 50 MB of main memory for graphics alone.


Gaming Loop

Date: 09 Sep 2016

The gaming loop is an important mechanism for two reasons:

If your game integrates in-app purchases, advertisements and other APIs, some tasks here will spawn additional threads as well. If any data can be simultaneously accessed by more than one thread, controls must be implemented to prevent data inteference.

For example, if the ball location is updated by the gaming loop while it is also being drawn by the main UI loop, jitters can appear during game play.

TIP: Use synchronized keyword and class AtomicBoolean to manage the drawing of game elements. While the gaming loop (GamingLoopClass.run()) is updating ball location, the UI loop (View.onDraw()) is periodically drawing the ball object in parallel. Implement a lock to ensure atomic access to ball position to prevent visual jitters.

// This is the gaming loop
class GamingLoopClass implements Runnable
{
   public void run()
   {
      // Gaming loop implementation here!
      while(condition)
      {
         // Do stuff
         ...
         myGameView.stepGame();
         ...
      }
   }
}

class GameView extends View
{
   // These assets are used to implement a synchronization lock to prevent
   // collision of the gaming and UI loops
   protected final Object writeLockView = new Object();
   protected final AtomicBoolean writeLockViewIsOpen = new AtomicBoolean(true);
   ...

   // This is called by the gaming loop to advance the game by one step
   protected void stepGame()
   {
      // Synchronize on writeLockView
      synchronized(writeLockView)
      {
         // Acquire syncronization lock to prevent UI loop from accessing data
         while(!writeLockViewIsOpen.compareAndSet(true, false))
         {
            // Wait for lock to open
            writeLockView.wait();
         }

         // Lock acquired
         // Update data
         // These two variables should be updated atomically as a pair
         this.ball.positionX += this.ball.speedX;
         this.ball.positionY += (this.ball.speedY + gravity);
         ...

         // Release lock
         writeLockViewIsOpen.set(true);

         // Wake up any threads waiting for the lock
         writeLockView.notify();
      }  // synchronized(writeLockView)
      ...
   }
   ...


   // This is automatically called by the UI loop to draw the game
   @Override
   protected void onDraw(Canvas canvas)
   {
      // Synchronize on writeLockView
      synchronized(writeLockView)
      {
         boolean getLockSuccess = true
         int waitCount = 0;

         // Acquire syncronization lock to prevent gaming loop from accessing data
         // In order to not lock up the main UI thread, don't wait too many times
         // But, to guard against "spurious wakeup" we should allow some additional
         // wait() calls if required
         while(!writeLockViewIsOpen.compareAndSet(true, false))
         {
            // Lock is in use by another thread. Wait and try again
            // Allow no more than 5 waits to prevent locking up main UI thread
            if(waitCount < 5)
            {
               // Wait for lock, maximum about 5 ms
               // Attention! timing is not guaranteed to be accurate
               writeLockView.wait(5);

               // If code reaches here, it means waiting time has elapsed
               // Or a "spurious wakeup" occurred
               // We have no way of knowing what happened here!
               waitCount++;
            }
            else
            {
               // Maximum number of waits used up
               // Lock was not obtained
               getLockSuccess = false;

               // Stop trying
               break;
            }
         }

         // Draw ball no matter if lock was obtained or not
         drawBall(this.ball.positionX, this.ball.positionY);
         ...

         // Lock was obtained
         if(getLockSuccess)
         {
            // Release syncronzation lock
            writeLockViewIsOpen.set(true);

            // If any other threads are waiting for
            // this lock, wake them up
            writeLockView.notify();
         }
      }  // synchronized(writeLockView)

      // Invalidate previous screen and request new screen drawing
      invalidate();
   }
   ...
}

The example above is not optimal and will likely result in an undesirable amount of collisions. If onDraw() is blocked while waiting for stepGame() to release the lock, the screen refresh rate will be impacted. If stepGame() is blocked while waiting for onDraw() to release the lock, the gaming loop speed might be affected.

My approach has been to reduce the amount of processing time inside the synchronized() blocks as much as possible. This not only cuts down on the chances of collision, but also reduces waiting time when it occurs. This is accomplished by performing all the costly calculations and drawing operations outside the synchronization blocks, which are only used to send drawing information (from the gaming loop) and to receive drawing information (by the UI loop).

TIP: Both the gaming loop (stepGame()) and the UI loop (onDraw()) have their own copies of the drawing information. Once ’Ball location’ is calculated, the synchronisation lock is obtained to simply transfer data to ’Lock Data.’ This happens very quickly to avoid blocking the drawing thread. Now, when the drawing thread is ready, it acquires the lock to quickly copy ’Ball location’ into ’Draw Data’ before releasing the lock. At this point whatever happens on the other thread does not affect onDraw()’s ability to render the frame accurately.


Sync data


This is a simple example, but as more sprites and elements are brought in, it becomes an exercise in tradeoffs. On the one hand, putting all the processing and drawing operations inside the synchronization block ensures jitter-free performance, but hurts frame rate. Using my implementation complicates the mechanism with limits to the number of objects that can be treated this way.

In a more meaningful scenario, if two threads (for example, in-app purchase and gaming loop) modify the coin counter simultaneously, the consequences are more serious. Therefore, access to data that is visible on more than one thread can be managed using synchronization locks as shown in the above example. Complex data structures, such as a coin counter with inbuilt checksum, need to be updated atomically, so this treatment is also appropriate.

TIP: With sufficient planning, complex data structures can also be made thread safe by appropriately applying object level locking.

public class coinCounter
{
   private int counter;
   private int checksum;

   // Synchronized function ensures only one thread can use this at a time
   // This means checksum will always be consistent in a multi-threaded environment
   public synchronized int updateCounter(int value)
   {
      counter += value;     // Update counter
      checksum = ~counter;  // Checksum is the bitwise compliment

      return counter;
   }
}

An exception exists for simple data, such as primitive types like boolean, int, long, etc., that can simply be declared volatile. This impacts program caching performance, but helps avoid complex and time-intensive synchronization.


Summary So Far

  1. Your game will be based on at least two threads running in parallel:
    • UI Loop. This is managed by Android
    • Gaming loop. This is managed by you
    • Other threads come into play when you intergrate in-app purchases, ads and other complications
  2. Keep in mind that some functions can only be called from the UI loop
    • runOnUiThread() lets you effectively get around this limitation
    • Handler.post() will also work
  3. If you have data that is accessible by more than one thread, protect it:
    • Primitive data types can be declared volatile
    • Complex data structures can be protected with synchronization locks to ensure atomicity
    • Complex data structures can also be made thread safe by applying object level locking
  4. The drawing function must be synchronised with game processing. If using a synchronisation lock to achieve this, be careful to avoid blocking this mechanism for too long. This impacts the frame rate and game speed.
  5. If timing is important, use System.nanoTime(). Although it is more efficient, System.currentTimeMillis() has an accuracy no greater than 15 ms.


More Topics to Come


HoHo-Holidays is out today

Date: 11 Dec 2016

Three Point Bandits HoHo-Holidays is out today for the festive season. Download now on Google Play. Also avaliable via direct download on the landing page.


SuperDunk! is the latest Three Point Bandits update

Date: 20 Mar 2017

Three Point Bandits SuperDunk! is now available on Google Play.


SuperDunk!


Basketball SuperDunk!

Three Point Bandits now has trampolines. Bounce over cars, robots and trouble to achieve legendary SuperDunk! status. Basketball will never be the same as you throw, spin, alley-oop and slam dunk, dunk, dunk through 14 stages using just one finger. Watch out for rampaging robots.

WHAT'S NEW






Follow Us on Social Media
Facebook
YouTube
Instagram



Copyright © 2017 Three Point Games Ltd. All rights reserved