Home Game Development Mastering Android Game Development

Mastering Android Game Development

By Raul Portales
books-svg-icon Book
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Setting Up the Project
About this book
Publication date:
June 2015
Publisher
Packt
Pages
372
ISBN
9781783551774

 

Chapter 1. Setting Up the Project

In this chapter, we will describe the situations in which it makes sense to use the Android SDK for a game and those where it is best to use an external engine, explaining the pros and cons of each case.

We will create a simple project that we will be improving throughout the book, until it becomes a complete game. The particular game we are going to build is a Space Shooter.

A few top-level decisions will be made and explained, such as which orientation to use and how are we going to use activities and fragments.

We will describe the top-level architecture of a game engine, study how it is different from a typical app's, explaining why there is an UpdateThread and how it interacts with the user input and why it is separated from the DrawThread; we will include those elements in our project.

Once the game engine is completed, we will expand the project to show a pause dialog, handle the Android back key properly, be consistent with the Activity lifecycle, and make it fullscreen.

Finally, we will summarize some best practices in writing code for games.

Topics that will be covered in this chapter are as follows:

  • The right tool for the right game

  • Setting up the project with Android Studio

  • Game architecture

  • Alert dialogs

  • Handling the back key

  • Dealing with the fullscreen mode

  • Good practices for game developers

 

The right tool for the right game


Before we begin entering the details about making games with the Android SDK, let's first take a step back and consider why are we doing this and what the other alternatives are for making a game that runs on Android.

People tend to reinvent the wheel quite often and developers use to do it ever more, especially in the case of video games. While creating a complete engine from scratch is a great learning experience, it also takes a lot of time. So, if you want to just make a game, it may be more cost-efficient for you to use one of the existing engines instead.

We are in a golden age of tools for creating video games. Not only are there lots of them, but most of them are free as well. This makes choosing the right one a little bit more complicated.

Let's take a look at several questions to help us decide which tool to use to suit the needs of a specific game. Since you are already reading this book, I consider that multiplatform is not high on your list of priorities and that reusing your existing Java and Android knowledge is a plus.

Do you want to use 3D?

If the answer is yes; I would definitely recommend you to use an already existing engine. There are some well-known tasks you'll need to implement to build even the simplest 3D engine such as loading models, loading and applying textures, handling transformations, and dealing with cameras. On top of this, you'd need to be writing OpenGL. All this is a lot of work.

Writing an OpenGL engine is the very definition of reinventing the wheel. It is fine if what you want is to learn the internals of a 3D engine, but if you go this road you'll spend a few months before you can even start with the game. If you want to go straight into making the game, you'd better start with an existing 3D engine.

The second question on this road is: do you prefer to work with code or are you more comfortable with a complete editor? For code, you can use jPCT-AE and libGDX, while, on the editor side, the most common alternative is Unity.

Do you want to use physics?

An affirmative answer to this question should point you straight to an existing engine.

Physics simulation is a very well-known area where there is a lot of documentation, and you should be able to implement your own physics engine. Again, this is a great learning experience, but if you want to go straight into making the game it is much more convenient to use an existing engine that supports physics. The most used physics engine around is Box2D, which is written in C++ and it has been ported to Android using the NDK.

While we are going to talk about collision detection later in the book, physics is out beyond the scope of this book. Anything more complex than two spheres colliding can become quite complex to handle.

Once again, it depends whether you prefer to work with code or if you want a complete editor. To work with code, AndEngine should be your weapon of choice. In the case of an editor, Corona and Unity are among the most popular choices.

Do you want to use Java?

Most of the feature-rich environments we are mentioning have their own environment, including a specific IDE. It takes effort to learn them and some of them use a different language (for example Unity has its own environment and uses JavaScript or C#).

On the other hand, the frameworks are simpler. You just have to include them and you'll still be writing an Android game. This is an interesting middle ground, where you still can reuse your Android and Java knowledge and make use of features such as physics or 3D models. In this section, we can mention AndEngine for 2D and physics and jPCT-AE for 3D as good options.

Pros of building games with the Android SDK

There are several advantages to building games using the Android SDK:

  • It is faster to build a prototype

  • You have full control over the engine

  • It has a smaller learning curve (you already know Android, Java, and Android Studio)

  • Most of your knowledge can be applied to apps

  • You can use Google Play services and other libraries natively

Cons of building games with the Android SDK

Of course, not everything is awesome. There are some serious disadvantages, most of them already mentioned, such as:

  • The code is not portable to other platforms (namely iOS).

  • Performance can be an issue. If the game gets to a certain complexity, you may need to use OpenGL.

  • It lacks a physics engine; you'd need to write it yourself.

  • The support for OpenGL is just primitives; you need to build everything (or use a library).

I want the Android SDK!

Are you still here? Congratulations, you have chosen the right book!

If you want to explore other options, there are books available for Unity, AndEngine, and libGDX, and published by Packt.

Now that we are all on the same page, let's get down to business.

 

The project – YASS (Yet Another Space Shooter)


Along the book, we will be building a game as a demo of the concepts we will be studying in each chapter. The game is going to be a classic Space Shooter arcade game. We'll call it YASS—Yet Another Space Shooter.

This means some decisions will be taken for this particular type of game, but other options will also be commented since the book is meant for generic video game development.

Activities and Fragments

We are going to create a project with a single Activity and we will add fragments when necessary.

In the versions prior to Android 5.0 Lollipop, the transitions between activities could be modified, but only in a very limited way. The user can even disable them in a setting. All in all, this will make your game look clunky while transitioning from one Activity to another. You will need to save the state of the Activity in case it gets destroyed. Since each Activity is a separate instance, you will need to take care of communication among them, if required.

On the other hand, when you work with fragments, you never exit the Activity and you have complete control over the transition animations. In addition to these, you still have the code and layout of each section separated, so modularity and encapsulation are not compromised.

Finally, when it comes to handling third-party libraries such as In-App Billing or Google Play services, you have to take care if initialization and configuration only once, since those are linked at the Activity level.

Note

For games, it is more efficient to use only one Activity with multiple Fragments.

One good practice is to have a base Fragment for our game (YassBaseFragment) from which all the other fragments will inherit. One good use of this fragment is to have a method to replace getActivity that returns our specific Activity, but there are other cases in which having a common base fragment is handy.

Project setup

We are going to use Android Studio as the IDE. We are going to create the project with minSDK 15 (Ice Cream Sandwich—ICS). As a good practice, we don't want to move the minimum SDK, unless we are using some features that were not available before. By keeping the minSDK low, you make your game available to as many devices as possible.

The two main features we are going to use from ICS are Fragments, ValueAnimators, and ViewPropertyAnimators. All of these were already available in Honeycomb, but 3.x is considered little more than a test for ICS; it was not mature and has been replaced by ICS in almost all devices.

In the unlikely case that you want to support older versions such as Gingerbread, you can make use of the compatibility library and NineOldAndroids to add backwards-compatibility for the features we are using.

Creating the stub project

Let's go on and navigate to File > New Project. We are going to use YASS as the Application name and example.com as the Company Domain.

We include support for Android TV, since we want to be able to run our game on the big screen. This will create an extra module that we can compile for, but we are not going to touch this until the last chapter.

As explained before, we will use Minimum SDK version 15 for phones and 21 for Android TV, since this is when it was made available.

For the Package name of the application, we are going to use com.example.yass.

We are not going to use any of the default wizards, since all of them include the action bar/toolbar that is great for apps, but of no use for games. So, we'll go with the empty project options:

Similarly, we are not going to create any Activity for TV:

Once the project is created, we will create a single Activity with one Fragment. This is done via the menu option New > Activity > Blank Activity with Fragment.

We are going to customize the Activity by filling the dialog as follows:

  • Activity Name: YassActivity

  • Layout Name: activity_yass (will be the default as soon as we change the Activity name)

  • Fragment Layout Name: fragment_yass (will be the default as soon as we change the Activity name)

  • Title: YassActivity

This will create the following files:

  • YassActivity.java with the code for YassActivity and PlaceholderFragment

  • activity_main.xml: A FrameLayout with @+id/container, which will be used to load the fragments into

  • fragment_main.xml: A placeholder layout with the text Hello World!

Since we did not tell Android Studio that this activity is going to be our launch activity, we need to edit the AndroidManifest.xml to configure it as such, by adding the proper intent filter:

<intent-filter>
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

Cleaning up

We are not going to use menus at all, so there are a few methods and files we will not need and we can delete them. You can leave all those methods there if you want, but it is better to have a clean environment, free of unused code.

So, we can remove the menu folder under resources and the files in it, which are meant to be the menu for the YassActivity.

The methods that handle menu-creation and menu-item-selection are also useless, so we can remove the following methods from YassActivity:

  • onCreateOptionsMenu: Invoked when the menu is created

  • OnOptionsItemSelected: Invoked when an option from the menu is selected

Choosing an orientation

Deciding the orientation of a game is a very important point. Given the diversity of Android phones, the resolution and aspect ratio are a couple of things we have to deal with.

Gaming is traditionally done in landscape orientation: computers have monitors in landscape mode, and so do TV screens when you play with your gaming console. Almost all handheld consoles are designed with landscape orientation as well. Even more, most tablets consider landscape to be the default orientation.

Note

Landscape is the traditional orientation for gaming.

YASS is going to be a landscape game. The key reason why we are doing it is to be able to port the game to Android consoles later on, both on Android TV and OUYA. This does not mean that the portrait mode is not a valid orientation for games, but it is a less familiar one for players.

We are going to use sensorLandscape instead of just landscape, so the device can rotate 180 degrees to adjust to whatever side is down. We have to update the AndroidManifest.xml to look like this:

<application
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme" >
  <activity
    android:screenOrientation="sensorLandscape"
    android:name=".YassActivity"
    android:label="@string/title_activity_yass" >
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
</application>

As you probably know, when an Activity changes orientation on Android, it is destroyed and recreated and so are all the fragments inside it. This means that, unless you explicitly save and restore information, the fragments will not remember the previous state.

Note

The sensorLandscape and sensorPortrait modes do not destroy activities on rotation.

Some good news here: while using sensorLandscape, the rotation does not kill the Activity, so no extra work is required. This happens because the layout is exactly the same and nothing needs to be recreated.

If you plan to make a game that can rotate, you must pay extra attention to saving and restoring the status of the game when the orientation changes. This in itself is another good reason to keep the game locked to a particular orientation, be it landscape or portrait.

Dealing with aspect ratios

Android devices come in a lot of different aspect ratios, form 4:3 to 16:9 at least. This is not counting the number of pixels.

While designing a game for multiple aspect ratios, there are basically two ways of doing it. For each of them, we design for the most extreme aspect ratio. We will be using the extra space for "smart letterboxes," which means that we can have more game view.

Several ways of designing for different aspect ratios

The most common option is to make the camera centered and fix the smallest size (the height for the landscape orientation). This allows for more view space on the sides, while making sure that the smallest screen will have enough display space. This is the equivalent of viewing 4:3 images on a 16:9 screen.

You can also fix the bigger size if the game design makes sense. This will add extra space on the top and bottom if the screen is square. This is the equivalent of viewing 16:9 images on a 4:3 screen.

There is an alternative approach: simply having "more camera space." We can, as well, make the game view a certain size and use the extra space for other controls such as scores, levels, and so on.

If you take this approach to the extreme, you can design the game area completely square and put the extra information in "smart letterboxes" for both landscape and portrait. One very good example of this approach is done by Candy Crush Saga. This is the best approach for versatility, but it is also the one that requires the most work.

For our game, we are going to use a "more camera space" approach with fixed size letterboxes to display scores and lives.

For the difference in resolution and pixel density, we will be designing for a low density screen. We will read the resolution of the device programmatically and apply a conversion factor. Some in-depth details of this approach are given in the chapters dedicated to low-level drawing, menus, and dialogs.

 

Game architecture


Games have a different architecture and control flow than apps. Both seem to respond to user input instantly, but while an app does this by setting listeners and reacting to events with method calls (most commonly the onClick method calls the OnClickListener), this approach is not valid for a real-time game (although it is valid for non-real-time games).

Once a game is running, it must evaluate and update everything as fast as possible. This is the reason why it cannot be interrupted by user events. Those events or states should be recorded instead and then read by the game objects during its update.

The game engine should be created inside the fragment that runs the game, because we only need the game engine running while we are playing. This has the advantage that we can use our existing Android knowledge to create and handle the rest of the screens of the game.

Simplified architecture of a game engine

The basic Game Engine architecture is composed of an Update Thread, a Draw Thread, and a series of Game Objects that belong to the Game Engine.

The Game Engine is the component through which the rest of the program interacts with the game. Its mission is also to encapsulate the existence of the update and draw threads as well as to handle the game objects.

A game is composed of Game Objects that are both updated and drawn. These objects are held inside the Game Engine.

The Update Thread is responsible for updating the state of the game objects as fast as it can. It will run through all the game objects calling an update method.

The UI has to also be constantly updating and be independent of the update thread. It will draw all the game objects by calling a draw method on them.

Let's analyze each component in detail.

GameEngine and GameObjects

The GameEngine contains the three elements already mentioned.

GameObject is an abstract class that all game objects in our game must extend from. This interface connects them with the Update and Draw threads.

public abstract class GameObject {
  public abstract void startGame();
  public abstract void onUpdate(long elapsedMillis, GameEngine gameEngine);
  public abstract void onDraw();
  public final Runnable mOnAddedRunnable = new Runnable() {
    @Override
    public void run() {
      onAddedToGameUiThread();
    }
  };

  public final Runnable mOnRemovedRunnable = new Runnable() {
    @Override
    public void run() {
      onRemovedFromGameUiThread();
    }
  };

  public void onRemovedFromGameUiThread(){
  }

  public void onAddedToGameUiThread(){
  }
}
  • startGame is used for the initialization of the object before a game can start.

  • onUpdate is called by the game engine as fast as possible, providing the number of milliseconds that have passed since the previous call and a reference to the GameEngine itself for future uses such as accessing user input.

  • onDraw makes the component render itself. We are not using any parameters just yet, but later we will pass a Canvas to draw on.

  • onRemovedFromGameUiThread contains code that must be run on the UIThread when the object is removed from the game.

  • onAddedToGameUiThread contains code that must be run on the UIThread when the object is added to the game.

  • The two Runnable objects are used to call onRemovedFromGameUiThread and onAddedToGameUiThread inside the UIThread.

The GameEngine will provide us with easy methods to start, stop, pause, and resume the game, so we don't have to worry about the threads or the game objects from the outside.

The game engine is composed of three items: the list of game objects, the UpdateThread, and the DrawThread.

private List<GameObject> mGameObjects = new ArrayList<GameObject>();

private UpdateThread mUpdateThread;
private DrawThread mDrawThread;

Let's take a look at the different methods of the engine to handle a game.

Starting a game

The code to start a game from the GameEngine is as follows:

public void startGame() {
  // Stop a game if it is running
  stopGame();

  // Setup the game objects
  int numGameObjects = mGameObjects.size();
  for (int i=0; i<numGameObjects; i++) {
    mGameObjects.get(i).startGame();
  }

  // Start the update thread
  mUpdateThread = new UpdateThread(this);
  mUpdateThread.start();

  // Start the drawing thread
  mDrawThread = new DrawThread(this);
  mDrawThread.start();
}

First of all, we have to make sure that no game is running, so we call stopGame at the beginning to stop a game if there is one in progress.

Secondly, we reset all the game objects that are linked to the engine. It is important to do this before we start the threads, so everything starts from the initial position.

Finally, we create and start the UpdateThread and the DrawThread.

Stopping a game

Stopping a game is even simpler. We just have to stop the Update and Draw threads if they exist:

public void stopGame() {
  if (mUpdateThread != null) {
    mUpdateThread.stopGame();
  }
  if (mDrawThread != null) {
    mDrawThread.stopGame();
  }
}

We also have methods for pauseGame and resumeGame that are functionally equivalent to this one. In these methods, the logic of the action belongs to each thread. We are not including the code of these methods here, because they are redundant.

Managing game objects

The engine has to manage the addition and removal of game objects. We cannot just handle the list directly, since it will be used intensively during onUpdate and onDraw.

public void addGameObject(final GameObject gameObject) {
  if (isRunning()){
    mObjectsToAdd.add(gameObject);
  }
  else {
    mGameObjects.add(gameObject);
  }
  mActivity.runOnUiThread(gameObject.mOnAddedRunnable);
}

public void removeGameObject(final GameObject gameObject) {
  mObjectsToRemove.add(gameObject);
  mActivity.runOnUiThread(gameObject.mOnRemovedRunnable);
}

We use the lists mObjectsToAdd and mObjectsToRemove to keep track of the objects that must be added or removed. We will do both as the last step of the onUpdate method with the exception of when the game engine is not running, in which case it is safe to add and remove them directly.

We are also running the corresponding Runnable object from the GameObject on the UIThread.

To update the game objects from the engine, we just call onUpdate on all of them. Once the update loop has finished, we take care of the objects that must be removed or added to mGameObjects. This part is done using a synchronized section that is also important for the onDraw method.

public void onUpdate(long elapsedMillis) {
  int numGameObjects = mGameObjects.size();
  for (int i=0; i<numGameObjects; i++) {
    mGameObjects.get(i).onUpdate(elapsedMillis, this);
  }
  synchronized (mGameObjects) {
    while (!mObjectsToRemove.isEmpty()) {
      mGameObjects.remove(mObjectsToRemove.remove(0));
    }
    while (!mObjectsToAdd.isEmpty()) {
       mGameObjects.add(mObjectsToAdd.remove(0));
    }
  }
}

We do the same for drawing, except that the drawing must be done on the UIThread. So, we create a Runnable object that we pass to the runOnUIThread method of the activity.

private Runnable mDrawRunnable = new Runnable() {
  @Override
  public void run() {
    synchronized (mGameObjects) {
      int numGameObjects = mGameObjects.size();
      for (int i = 0; i < numGameObjects; i++) {
        mGameObjects.get(i).onDraw();
      }
    }
  }
};

public void onDraw(Canvas canvas) {
  mActivity.runOnUiThread(mDrawRunnable);
}

Note that we synchronize the run method using mGameObjects. We do it so we are sure that the list is not modified while we iterate it.

It is also important that only the last part of the onUpdate is synchronized. If no objects are added or removed, the threads are independent. If we synchronize the complete onUpdate method, we will be losing all the advantages of having the Update and Draw threads separated.

UpdateThread

UpdateThread is a thread that continuously runs updates on the game engine. For each call to onUpdate, it provides the number of milliseconds since the previous execution.

The basic run method of the update thread is as follows:

@Override
public void run() {
  long previousTimeMillis;
  long currentTimeMillis;
  long elapsedMillis;
  previousTimeMillis = System.currentTimeMillis();

  while (mGameIsRunning) {
    currentTimeMillis = System.currentTimeMillis();
    elapsedMillis = currentTimeMillis - previousTimeMillis;           
    mGameEngine.onUpdate(elapsedMillis);
    previousTimeMillis = currentTimeMillis;
  }
}

The thread stays in a loop for as long as the game is running. On each iteration, it will get the current time, calculate the elapsed milliseconds since the previous run, and call onUpdate on the GameEngine object.

While this first version works and is very simple to follow, it can only start and stop a game. We want to be able to pause and resume it as well.

To pause and resume the game, we need a variable that we read inside the loop to check when to pause the execution. We'll need to keep track of the elapsed milliseconds and discount the time spent paused. A simple way to do it is like this:

while (mGameIsRunning) {
  currentTimeMillis = System.currentTimeMillis();
  elapsedMillis = currentTimeMillis - previousTimeMillis;
  if (mPauseGame) {
    while (mPauseGame) {
      try {
        Thread.sleep(20);
      } catch (InterruptedException e) {
        // We stay on the loop
      }
    }
    currentTimeMillis = System.currentTimeMillis();
  }
  mGameEngine.onUpdate(elapsedMillis);
  previousTimeMillis = currentTimeMillis;
}

The code for the pauseGame and resumeGame methods is just setting the variable mPauseGame to true or false.

If the game is paused, we enter a while loop in which we will remain until the game is resumed. To avoid having an empty loop that runs continuously, we can put the thread to sleep for a short amount of time (20 milliseconds). Note that Thread.sleep can trigger an InterruptedException. If that happens we can just continue since it is going to be run in 20 milliseconds again. Besides, we are going to improve it right now.

This approach works, but there is still a lot of idle processing being done. For threads, there are mechanisms to pause and resume in a much more efficient way. We are going to improve this using wait/notify.

The code can be updated to be like this:

while (mGameIsRunning) {
  currentTimeMillis = System.currentTimeMillis();
  elapsedMillis = currentTimeMillis - previousTimeMillis;
  if (mPauseGame) {
    while (mPauseGame) {
      try {
        synchronized (mLock) {
          mLock.wait();
        }
      } catch (InterruptedException e) {
        // We stay on the loop
      }
    }
    currentTimeMillis = System.currentTimeMillis();
  }
  mGameEngine.onUpdate(elapsedMillis);
  previousTimeMillis = currentTimeMillis;
}

The pauseGame method is the same as before, but we need to update resumeGame to be at the place from where the lock is notified and released:

public void resumeGame() {
  if (mPauseGame == true) {
    mPauseGame = false;
    synchronized (mLock) {
      mLock.notify();
    }
  }
}

With the use of wait/notify, we ensure that the thread will not do any work while it is idle and we also know that it will be woken up as soon as we notify it. It is important to first set mPauseGame to false and then awake the thread, otherwise the main loop could stop again.

Finally, to start and stop the game, we just need to change the values of the variables:

public void start() {
  mGameIsRunning = true;
  mPauseGame = false;
  super.start();
}

public void stopGame() {
  mGameIsRunning = false;
  resumeGame();
}

The game never starts in a paused state. To stop a game, we just need to set the mGameIsRunning value to false and the loop inside the run method will end.

It is important to call resumeGame as a part of the stopGame method. If we call stop while the game is paused, the thread will be waiting, so nothing will happen unless we resume the game. If the game is not paused, nothing is done inside resumeGame, so it does not matter if we called it.

DrawThread

There are several ways to implement DrawThread. It could be done in a similar way to the update thread, but we are going to use a much simpler approach that does not use a Thread.

We are going to use the Timer and TimerTask classes to send the onDraw callback to the game engine with a high-enough frequency to render at 30 frames per second:

private static int EXPECTED_FPS = 30;
private static final long TIME_BETWEEN_DRAWS = 1000 / EXPECTED_FPS;

public void start() {
  stopGame();
  mTimer = new Timer();
  mTimer.schedule(new TimerTask() {
    @Override
    public void run() {
      mGameEngine.onDraw();
    }
  }, 0, TIME_BETWEEN_DRAWS);
}

We have this method called every 33 milliseconds. In simple implementations, this method will just call invalidate in the GameView, which will cause a call to the onDraw method of the View.

This implementation relies on one feature of the Android UI. To redisplay views, Android has a contingency system that is built in to avoid recurrent invalidates. If an invalidation is requested while the view is being drawn, it will be queued. If more than one invalidations are queued, they will be discarded as they won't have any effect.

With this, if the view takes longer than TIME_BETWEEN_DRAWS to be drawn, the system will fall back to fewer frames per second automatically.

Later in the book, we will revisit this thread for more complex implementations but, for now, let's keep it simple.

Stopping, pausing, and resuming the DrawThread is also simple:

public void stopGame() {
  if (mTimer != null) {
    mTimer.cancel();
    mTimer.purge();
  }
}

public void pauseGame() {
  stopGame();
}

public void resumeGame() {
  start();
}

To stop the game, we only need to cancel and purge the timer. The cancel method will cancel the timer and all scheduled tasks, while purge will remove all the canceled tasks from the queue.

Since we do not need to keep track of any state, we can just make the pauseGame and resumeGame equivalents to stopGame and start.

Note that, if we want to have a smooth game at 30fps, the drawing of all the items on the screen must be performed in less than 33 milliseconds. This implies that the code of these methods usually needs to be optimized.

User input

As we mentioned, user input is to be processed by some input controller and then read by the objects that need it, when they need it. We will go into the details of such an input controller in the next chapter. For now, we just want to check whether the game engine works as expected and handles the start, stop, pause, and resume calls properly.

Pause, resume, and start are different from the other user inputs, because they affect the state of the engine and threads themselves instead of modifying the state of the game objects. For this reason, we are going to use standard event-oriented programming to trigger these functions.

 

Putting everything together


Let's pick up our stub project, add all the classes we need to have a working game engine, and then modify the code so it allows us to start, stop, pause, and resume the game engine and display the number of milliseconds since the game was started.

We will put our current implementation of GameEngine, UpdateThread, DrawThread, and GameObject inside the com.example.yass.engine package.

Next, we will create another package named com.example.yass.counter, which we will use for the code of this example.

Inside YassActivity, we have an inner class named PlaceholderFragment. We are going to rename it to GameFragment, refactor it to a separate file, and put it under the com.example.yass.counter package.

We are going to add a TextView that will show the number of milliseconds and two buttons: one to start and stop the game engine and another one to pause and resume it.

We are going to add them to the layout of fragment_yass_main.xml, which will look like this:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:padding="@dimen/activity_horizontal_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  tools:context="com.example.yass.counter.PlaceholderFragment">

  <TextView
    android:id="@+id/txt_score"
   android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />

  <Button
    android:id="@+id/btn_start_stop"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/start" />

  <Button
    android:id="@+id/btn_play_pause"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/pause" />
</LinearLayout>

For the game fragment, we need to add the following code inside onViewCreated:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
  mGameEngine = new GameEngine(getActivity());
  mGameEngine.addGameObject(
    new ScoreGameObject(view, R.id.txt_score));
  view.findViewById(R.id.btn_start_stop)
    .setOnClickListener(this);
  view.findViewById(R.id.btn_play_pause)
    .setOnClickListener(this);
}

Once the view is created, we create the game engine and add a new ScoreGameObject to it. Then we set the current fragment as the listener for the two buttons we have added.

The code for onClick is very simple; just decide which method to call for each button:

@Override
public void onClick(View v) {
  if (v.getId() == R.id.btn_play_pause) {
    playOrPause();
  }
  if (v.getId() == R.id.btn_start_stop) {
    startOrStop();
  }
}

Deciding whether the game should be paused or resumed is as simple as this:

private void playOrPause() {
  Button button = (Button)
  getView().findViewById(R.id.btn_play_pause);
  if (mGameEngine.isPaused()) {
    mGameEngine.resumeGame();
    button.setText(R.string.pause);
  }
  else {
    mGameEngine.pauseGame();
    button.setText(R.string.resume);
  }
}

We also handle a name change on the button to make sure the UI is consistent. In the code, we are making use of the isPaused method from GameEngine. This method just returns the status of the UpdateThread object as long as it is not null:

public boolean isPaused() {
  return mUpdateThread != null && mUpdateThread.isGamePaused();
}

Similarly, to play/pause the game and keep the state of the buttons, we will add this method:

private void startOrStop() {
  Button button = (Button)
    getView().findViewById(R.id.btn_start_stop);
  Button playPauseButton = (Button)
    getView().findViewById(R.id.btn_play_pause);
  if (mGameEngine.isRunning()) {
    mGameEngine.stopGame();
    button.setText(R.string.start);
    playPauseButton.setEnabled(false);
  }
  else {
    mGameEngine.startGame();
    button.setText(R.string.stop);
    playPauseButton.setEnabled(true);
    playPauseButton.setText(R.string.pause);
  }
}

Once again, we need a method in the GameEngine to know whether it is running or not. As we did for the previous one, we just mirror the status of UpdateThread:

public boolean isRunning() {
  return mUpdateThread != null && mUpdateThread.isGameRunning();
}

Once the basic connections are done, we can move to the really interesting bit: the game object we are creating. This object illustrates the use of each method from the GameObject class that we have been talking about:

public class ScoreGameObject extends GameObject {

  private final TextView mText;
  private long mTotalMilis;

  public ScoreGameObject(View view, int viewResId) {
    mText = (TextView) view.findViewById(viewResId);
  }

  @Override
  public void onUpdate(long elapsedMillis, GameEngine gameEngine)
  {
    mTotalMilis += elapsedMillis;
  }

  @Override
  public void startGame() {
    mTotalMilis = 0;
  }

  @Override
  public void onDraw() {
    mText.setText(String.valueOf(mTotalMilis));
  }
}

The onUpdate method just keeps adding milliseconds to the total. The total is reset when a new game starts and onDraw sets the value of the total number of milliseconds in the text view.

As expected, onUpdate is called a lot more often than onDraw. On the other hand, onDraw is executed on the UIThread, which is something we cannot afford to do with onUpdate.

We can now compile and run the example and check that the timer starts and stops when we start and stop the game engine. We can also check that pause and resume work as expected.

 

Moving forward with the example


Now we are going to change the example a bit. We are going to make a pause dialog from which we can resume or stop the game. This dialog will be shown if the user taps on the pause button and if he or she hits the back key.

Finally, we are going to add one fragment from which the player can start the game and we will separate the game fragment from the menu.

So, we'll be creating MainMenuFragment.java and fragment_main_menu.xml. The content of the layout will be extremely simple:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent" 
  android:layout_height="match_parent">

  <TextView
    android:layout_gravity="center_horizontal|top"
    style="@android:style/TextAppearance.DeviceDefault.Large"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:text="@string/game_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

  <Button
    android:id="@+id/btn_start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="@string/start" />

</FrameLayout>

This includes the app title on the screen and a button to start playing:

Inside this fragment, we add a listener to the start button and we make it call the startGame method. The code of the startGame method is very simple as well:

public void startGame() {
  getFragmentManager()
    .beginTransaction()
    .replace(R.id.container, new GameFragment(), TAG_FRAGMENT)
    .addToBackStack(null)
    .commit();
}

We are using the fragment manager to transition from the current fragment to GameFragment.

The beginTransition method creates the transition itself and we can configure it with chained methods.

We are replacing the fragment inside the view with the R.id.container id with a GameFragment. This will remove the old fragment. If we use add, both fragments will be shown instead.

Then, we add the fragment to the back stack with no tag, since we don't need any. This is very important, because it allows the system to handle the back key properly. Everything that is on the back stack of the fragment manager will pop up when the back key is pressed.

If we do not add the fragment to the back stack, the default behavior when we tap on the back key will be to close the app. With the fragment on the back stack, we can just rely on the system to handle fragment navigation properly.

Finally, we commit the transition so the fragment is replaced.

Inside the game fragment we have already, we will remove the start/stop dialog and modify the pause button to show a dialog from where we can resume or exit the current game.

We want the game to start immediately, so the onViewCreated method of the GameFragment will now look like this:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
  mGameEngine = new GameEngine(getActivity());
  mGameEngine.addGameObject(
    new ScoreGameObject(view, R.id.txt_score));
  view.findViewById(R.id.btn_play_pause)
    .setOnClickListener(this);
  mGameEngine.startGame();
}

We will also modify the onClick method, removing the old code to start or stop, so it looks like this:

@Override
public void onClick(View v) {
  if (v.getId() == R.id.btn_play_pause) {
    pauseGameAndShowPauseDialog();
  }
}

This simpler version only cares about pausing the game and showing a dialog when the pause button is clicked.

For now, we are going to create a default dialog using the AlertDialog framework:

private void pauseGameAndShowPauseDialog() {
  mGameEngine.pauseGame();
  new AlertDialog.Builder(getActivity())
  .setTitle(R.string.pause_dialog_title)
  .setMessage(R.string.pause_dialog_message)
  .setPositiveButton(R.string.resume, 
  new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
      dialog.dismiss();
      mGameEngine.resumeGame();
    }
  })
  .setNegativeButton(R.string.stop, 
    new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
      dialog.dismiss();
      mGameEngine.stopGame();
      ((MainActivity)getActivity()).navigateBack();
    }
  })
  .create()
  .show();
}

The positive button will resume the game, so it calls resumeGame in the game engine.

The negative button will exit the game, so it calls stopGame in the GameEngine and then navigateBack in the parent Activity.

The navigateBack method is nothing more than handling a back key pressed in the activity:

public void navigateBack() {
  super.onBackPressed();
}

Since we put the fragment in the navigation stack, the MainMenuFragment will be loaded again and the GameFragment will be destroyed. The following is how the Pause dialog looks:

Handling the back key

One of the things we want to do is to handle the back key properly. This is something that upsets Android users when it does not work as expected inside games, so we'll be paying some special attention to it. There are two places where it does not work as expected right now.

Note

Handling the back key properly is very important on Android.

  • If we dismiss the Pause dialog using the back key, the game will not resume.

  • While in the game fragment, the back key should pause the game. At the moment, the back key goes back to the GameFragment.

For the first problem, we need to add an OnCancelListener to the dialog. This is different from OnDismissListener, which is called every time the dialog is dismissed. The cancel method is only called when the dialog is canceled.

Also, OnDismissListener was introduced in API level 17. Since we don't need it, we will not worry about raising the minSDK of the game.

We update the creation of the Pause dialog with the following code:

new AlertDialog.Builder(getActivity())
  [...]
  .setOnCancelListener(new DialogInterface.OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
      mGameEngine.resumeGame();
    }
  })
  .create()
  show();

The remaining item is to pause the game when the back key is pressed during the game. This is something that needs to be handled in the fragment. As it happens, onBakPressed is a method available only for activities. We need to code a way to expand this to the current fragment.

We are going to make use of our YassBaseFragment, the base class for all the fragments in our game, to add the support to onBackPressed. We will create one onBackPressed method here:

public class YassBaseFragment extends Fragment {
  public boolean onBackPressed() {
    return false;
  }
}

In the Activity, we update onBackClicked to allow the fragments to override it if needed:

@Override
public void onBackPressed() {
  final YassFragment fragment = (YassFragment)
    getFragmentManager().findFragmentByTag(TAG_FRAGMENT);
  if (!fragment.onBackPressed()) { 
    super.onBackPressed();
  }
}

If the fragment does not handle the back key press, it will return false. Then, we just call the super method to allow the default behavior.

TAG_FRAGMENT is very important; it allows us to get the fragment we are adding and it is set when we add the fragment to FragmentTransition. Let's review the onCreate method of MainActivity, which was created by the wizard, and add the TAG_FRAGMENT to the initial FragmentTransition:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_yass);
  if (savedInstanceState == null) {
    getFragmentManager().beginTransaction()
      .add(R.id.container, new MainMenuFragment(), TAG_FRAGMENT)
      .commit();
  }
}

It is also very important that all the fragments of the application must extend from YassBaseFragment, otherwise this method will throw a ClassCastException.

With all the pieces in place, we now override the onBackPressed method inside GameFragment to show the Pause dialog:

@Override
public boolean onBackPressed() {
  if (mGameEngine.isRunning()) {
    pauseGameAndShowPauseDialog();
    return true;
  }
  return false;
}

With this, the Pause dialog is shown when we click back while in the GameFragment. Note that we will only show the pause dialog if the GameEngine is running. When it is not running, we return false. The default behavior of Android will trigger and the Pause dialog, which must be showing, will be canceled.

Honoring the lifecycle

Our game should also be consistent with the Activity lifecycle; especially, it should pause whenever the Activity pauses. This is very important for mainly two reasons:

  • If the game is put in the background, the user wants it to be paused when it returns

  • As long as the game is running, the update thread will be updating as fast as it can, so it will make the phone feel slower

With the current implementation, none of this will happen. You can try pressing the home button, you will see that the device does not feel responsive. Also, if you put the game again in the foreground using the recent activities button, you will see that the timer is still counting.

Note

Not respecting the fragment lifecycle will result in performance problems and unhappy players.

Solving this is very simple, we just need to be consistent with the fragment lifecycle, by adding this code to the GameFragment:

@Override
public void onPause() {
  super.onPause();
  if (mGameEngine.isRunning()){
    pauseGameAndShowPauseDialog();
  }
}

@Override
public void onDestroy() {
  super.onDestroy();
  mGameEngine.stopGame();
}

With this, whenever the fragment is paused, we pause the game and show the dialog, so the player can resume again. Also, whenever the fragment is destroyed, we stop the game engine.

It is important to check whether the game engine is running or not before we pause it, since onPause is also called when we exit the game. So, if we forget to do this, exiting via the pause dialog will make the app crash.

Using as much screen as we can

We are building a game. We want to have all the screen space of the device and no distractions. There are two items that take this from us:

  • The Status bar: The bar on the top of the screen where the time, battery, WiFi, mobile signal, and notifications are displayed.

  • The Navigation bar: This is the bar where the back, home, and recent buttons are placed. It may be located in different places according to the orientation of the device.

The Status and Navigation bars take up a significant amount of space on the screen

The Navigation bar was introduced on Ice Cream Sandwich as a replacement for physical buttons. But, even today, some manufacturers decide to use physical buttons instead, so it may or may not be there.

The first thing we can do is to tell the system that we want to be fullscreen. There is a flag with the SYSTEM_UI_FLAG_FULLSCREEN name, which seems to be what we are looking for.

The problem is that this flag was introduced in the early versions of Android when there was no Navigation bar. Back then, it really meant fullscreen but, from Ice Cream Sandwich onwards, it just means "remove the Status bar".

Note

The SYSTEM_UI_FLAG_FULLSCREEN mode is not really fullscreen.

Fullscreen only makes the Status bar go away.

Along with the Navigation bar, some ways to handle fullscreen were added. The approach was revisited in KitKat. So, let's look at our options.

Before Android 4.4 – almost fullscreen

On Android 4.0, together with the Navigation bar, two new flags were added to handle the Navigation bar in addition to the existing fullscreen flag:

  • SYSTEM_UI_FLAG_HIDE_NAVIGATION: This tells the system to hide the Navigation bar

  • SYSTEM_UI_FLAG_LOW_PROFILE: This puts the device in "low profile" mode, dimming the icons on the Navigation bar and replacing them with just dots

While it is true that the "hide navigation" flag hides the Navigation bar completely, the bar will reappear as soon as you touch anywhere on the screen, since this mode is designed to be used for noninteractive activities such as video playback. So, SYSTEM_UI_FLAG_HIDE_NAVIGATION is not much use to us.

Using low profile to dim the navigation bar is a much more logical solution. Although we are not getting any extra screen space, the fact that the icons on the bar are reduced to small dots allows players to focus a lot more on the content. These icons will show when necessary (essentially, when the user taps on the bar) and dim again as soon as they are not needed.

Note

Hiding the navigation bar will only work fine for noninteractive apps. The Navigation bar will appear again as soon as you touch the screen.

All in all, we have to be happy with just dimming the Navigation bar and getting rid of the Status bar.

The low profile mode dims the Navigation bar so it is less obtrusive

This is the code we need to add to the MainActivity to remove the Status bar and put the device in a low profile mode:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus) {
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
      | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
      | View.SYSTEM_UI_FLAG_FULLSCREEN
      | View.SYSTEM_UI_FLAG_LOW_PROFILE);
  }
}

We are overriding the onWindowFocusChanged method in the main Activity. This is the recommended place to handle the flags, since it is called whenever the window focus changes. When the app regains focus, we don't know in which status the bars are. So, it is a good practice to ensure that things are the way we want them.

There are two more flags we haven't mentioned yet. They were introduced in API level 16 and are designed to take care of how the layout reacts to the appearance and disappearance of elements.

The SYSTEM_UI_FLAG_LAYOUT_STABLE flag means that the layout will be consistent, independent of the elements being shown or hidden.

The SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flag tells the system that our stable layout will be the one in the fullscreen mode—without the navigation bar.

This means that if/when the status bar is shown, the layout will not change, which is good, otherwise it will look like it is a glitch. It also means that we need to be careful with margins, so nothing important gets covered by the Status bar.

Note

Stable layout only exists from the Jelly Bean version onwards (API level 16 +).

For Ice Cream Sandwich, SYSTEM_UI_FLAG_LAYOUT_STABLE does not work. But there are very few devices with this version and the Status bar is shown on very few occasions, so it is acceptable.

The real fullscreen mode was introduced in KitKat.

Android 4.4 and beyond – immersive mode

On KiKat, a new mode was introduced: the immersive mode.

Immersive mode hides the Status and Navigation bars completely. It is designed, as the name indicates, for fully-immersive experiences, which means games mostly. Even when the Navigation bar appears again, it is semitransparent instead of black and overlaid on top of the game.

Note

The sticky immersive mode has been designed almost specifically for games.

Immersive mode can be used in two ways: normal and sticky. Both of them are fullscreen and the user is shown a tip the first time the app is put in this mode with an explanation of how to get out of it:

The immersive nonsticky mode will keep the Status and Navigation bars visible once they are shown, while the immersive sticky mode will hide them after a couple of seconds have passed, returning to the real fullscreen. The recommended mode for games is to use sticky immersion.

The code to put the app in the fullscreen sticky immersion mode is as follows:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus) {
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
      | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
      | View.SYSTEM_UI_FLAG_FULLSCREEN
      | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
      | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
      | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  }
}

In this case, as in the previous one, we are requesting the use of a stable layout, and we are making it as if it is fullscreen. This time, we include a flag to make the stable layout the one with no Navigation bar (SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION).

We also add the flags to hide the Status bar (fullscreen) and the Navigation bar (hide navigation). Finally, we ask for the immersive sticky mode. The result is a real fullscreen game:

Immersive mode gives us all the screen space on the device

With this configuration, even when the user does a gesture to show the Status and Navigation bars, they are shown in a semitransparent way overlaid on top of our UI:

When the bars are shown while in sticky immersion mode, they are overlaid and semi transparent

Unfortunately, the sticky mode requires us to add the SYSTEM_UI_FLAG_HIDE_NAVIGATION flag to put the Navigation bar in the sticky mode. This has a very bad side-effect in the previous versions of Android, making the Navigation bar appear and disappear continuously as soon as you touch the screen, since this flag without the immersive mode means something different.

In addition to this, the SYSTEM_UI_FLAG_LOW_PROFILE flag does not have any effect on the versions in which the immersive mode is available. This makes sense, since it is considered a replacement and an improvement on it.

Putting fullscreen together

Since we have two different modes for requesting fullscreen, one prior to KitKat (low profile) and one from KitKat (immersive mode), and the flags for hiding the Navigation bar do not play together nicely, we need to make a different configuration based on which version of Android the device is running on:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus) {
    View decorView = getWindow().getDecorView();
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
      decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LOW_PROFILE);
    }
    else {
      decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
  }
}

With this code, we give the expected game experience to each one of the Android versions; a low profile with a dimmed Navigation bar on the versions older than KitKat and the full-immersive mode on the newer devices.

 

Good practices for game developers


In general, you should avoid premature optimization. This means, do not optimize your code unless you have a performance problem.

Nevertheless, in games, we have two methods (onUpdate and onDraw) for which the execution time is critical. So, we will be providing a few tips that should be enough to get performance under a reasonable threshold.

For the rest of the cases, your code will be probably good. If you find a performance problem, you should measure it carefully to find where the bottleneck is and only then optimize it. Most of the time, the problem is not where we think it is. Premature optimization can lead to a less readable code without significant improvement.

Object pools

The creation and destruction of objects is an expensive operation that should be limited. This is one area where a real-time game is a lot more sensitive than an app.

Every time you create an object, the garbage collector has a chance to be run. In the old versions of Android, it meant that everything stopped for 200ms. While it is no longer this bad, it may still be noticeable.

Note

We should avoid object creation as much as we can.

We want to avoid any expensive operation to be performed inside the onUpdate method—which must run as fast as it can—so we are going to take the creation and destruction of objects out of it.

The solution for this is a well-known software pattern called object pool.

Before we start the game, we will precreate the objects we are going to need and put them in a pool. The pool can be something as simple as a stack or list.

Instead of creating an object, we will pick one from the pool and initialize it. If the pool is empty, it means that we underestimated the number of objects. So as a lesser evil, a new instance of the object must be created.

Instead of destroying an object, we will put it back into the pool.

The fact that we have to return objects to the pool forces us to figure out when an object is no longer needed instead of just relying on the garbage collector to do that for us. While it requires a bit of effort, this mental exercise will improve the game performance and structure. If you have ever worked with C++, this should be easy-peasy for you.

We will use object pools for all the game objects in the code; this means enemies and bullets basically.

Avoiding enhanced loop syntax in lists

Related to the object creation, we should avoid the use of an enhanced loop syntax in the lists. While the for-each syntax is easier to read, it creates an iterator on-the-fly, which makes the execution slower and gives the garbage collector a chance to be run.

In the case of the onUpdate method of GameEngine, we could have written it using the for-each syntax like this:

public void onUpdate(long elapsedMillis) {    
  for (GameObject gameObject : mGameObjects) {
    gameObject.onUpdate(elapsedMillis, this);
  }
}

But this is significantly slower than using the standard for loop syntax. This is why it looks like this instead:

public void onUpdate(long elapsedMillis) {
  int numGameObjects = mGameObjects.size();
  for (int i=0; i<numGameObjects; i++) {
    mGameObjects.get(i).onUpdate(elapsedMillis, this);
  }
}

In the particular case of arrays, the enhanced syntax is as fast as the traditional one on the devices with the JIT (just-in-time) compiler—which should be the case for all devices nowadays—so there is no drawback in always using the default loop syntax instead of the enhanced one.

It is also important to use a variable for the size instead of requesting it for every iteration, which leads us to the next tip.

Precreating objects

Related to the inefficiency of creating objects inside the onUpdate loop, we should always precreate the objects we are going to use.

A good example of this practice is the Runnable objects that are created inside the GameObject to run onRemovedFromGameUiThread and onAddedToGameUiThread.

We could create them on-demand inside the game engine as a part of addGameObject and removeGameObject, but it will be much less efficient.

Accessing variables directly

As often as we can, we will use a direct variable access instead of using getters and setters. This is a good practice in general, since accessors are expensive and the compiler does not inline them.

In the case of games, it makes sense to extend this practice to variables of other classes. As we mentioned several times before, the execution time of onUpdate and onDraw is critical; a difference of just milliseconds counts. This is why, when variables from the game objects are accessed by other game objects, we make them public and work with them directly.

This is a bit counter-intuitive for Java developers, since we are used to encapsulating everything through getters and setters. In this case, efficiency is more important than encapsulation.

Being careful with floating points

In the case of doing calculations, integer operations are about twice as fast as float operations.

When integers are not enough, there is no real difference in speed between float and double. The only difference is in space, where doubles are twice as large.

Also, even for integers, some processors have hardware multiply, but lack hardware divide. In such cases, integer division and modulus operations are performed in the software. All in all, this is a case where premature optimization can harm you.

Performance myths – avoid interfaces

On the older versions of Android, before the JIT compiler was introduced, accessing methods via an interface instead of the exact type was slightly more efficient. In these versions, it made sense to declare a variable of ArrayList instead of the generic List interface to access the class directly.

In the modern versions of Android, however, there is no difference between accessing a variable via an interface and doing it directly. So, for the sake of generality, we will be using the generic interface instead of the class, as seen inside the GameEngine:

private List<GameObject> mGameObjects = new ArrayList<GameObject>();
 

Summary


After a quick introduction to the question of which tools are best to make which types of games, we have described the pros and cons of using the bare Android SDK for making games.

We have set up a project and defined the main activity and its orientation. We have created a basic game engine, included it in the project, and checked whether it works as expected.

Later, we have extended the project with a second fragment and a pause dialog, managed the lifecycle of the game properly, and defined a way to get a proper fullscreen for different Android versions.

Finally, we have covered a few tips on optimizing the code inside the critical sections of the game.

We are ready to start handling the user input.

About the Author
  • Raul Portales

    Raul Portales is a software engineer who has had a love for computers, electronics, and gadgets in general for as long as he remembers. He jumped into Android as soon as it was released. Raul has worked on social networks, education, healthcare, and even founded a gaming studio and a consultancy company. Specializing in mobile and UX, he speaks frequently at conferences and meetups. Raul's love for electronics reignited when Google announced Android Things. He started tinkering with it with the first Developer Preview, which lead to adding the IoT category on his Google Developer expert profile.

    Browse publications by this author
Latest Reviews (7 reviews total)
Very nice book with detail examples to follow.
A few things that i consider important are missing
Another outdated badly edited book by packtpub
Mastering Android Game Development
Unlock this book and the full library FREE for 7 days
Start now