
The InputController base class
It does not matter how we want to control the game; it can always be abstracted as a two-axis joystick and a button to fire. For other games this could be different, but you should always be able to extract basic actions from the user and create an InputController
that handles them. The InputController
we are building is useful for any game that uses directional control.
Note
Input controllers are quite specific to each type of game.
We are going to consider a normalized horizontal and vertical axis as an input (going from -1 to 1). In the case of a controller that does not have a range, we will just set it to the maximum. This will allow us to handle user input with precision when the type of input allows us to, as is the case with virtual and real joysticks as well as with sensors.

The coordinate system of a phone screen
Just as a reminder, the coordinates on a computer screen have [0,0] in the top-left corner and go positive towards to the right and down. The bottom-right corner has coordinates [width, height]. This is different from the standard coordinate system we are used to, but it is very important that you remember it.
This is why moving left is -1 and moving to the top is -1 as well.
The base class for all the input controllers in our game is going to be as follows:
public class InputController { public double mHorizontalFactor; public double mVerticalFactor; public boolean mIsFiring; public void onStart() { } public void onStop() { } public void onPause() { } public void onResume() { } }
Note that this is a class with public variables. It is done this way to avoid reading the values via a method. We mentioned that in the previous chapter as a performance improvement.
Each implementation of this class will be responsible for having these variables populated with the updated values. The game objects can read them during onUpdate
. By doing this, we separate the action of using the values from the game objects from the reading of the user input.
Note
InputController
isolates the reading of the input from its usage via the game objects.
InputController
is a part of the GameEngine
. We just add a variable of this type to the engine and create a method to set it:
public InputController mInputController; public void setInputController(InputController controller) { mInputController = controller; }
The methods onStart
, onStop
, onPause
, and onResume
are called from the GameEngine
, when the game is started, stopped, paused, or resumed. Some input controllers will need to do special actions in such situations.
Finally, we add the input controller to the GameEngine
during the initialization of the engine inside the GameFragment
:
mGameEngine = new GameEngine(getActivity());
mGameEngine.addGameObject(new ScoreGameObject(view, R.id.txt_score));
view.findViewById(R.id.btn_play_pause).setOnClickListener(this);
mGameEngine.setInputController(new InputController());
mGameEngine.addGameObject(new Player(getView()));
mGameEngine.startGame();
For now, we are adding an input controller that does nothing. We are also adding a Player
game object, which we are going to work on before going into the different input controllers in more detail.
Note that we are no longer using the ScoreGameObject
from the previous example and it should not be added to the game engine.
The Player object
The first version of the Player
game object we are going to build will just initialize its coordinates in the middle of the screen. Then, it will update them based on the information in the input controller and, finally, it will display the value as [x, y] in the TextView
we have on the layout.
After this, we will make it display a spaceship located at the coordinates. But for now, we will focus on how onUpdate
is implemented.
The code for onUpdate
and onDraw
of the first version of the Player
class is as follows:
@Override public void onUpdate(long elapsedMillis, GameEngine gameEngine) { InputController inputController = gameEngine.inputController; mPositionX += mSpeedFactor*inputController.mHorizontalFactor*elapsedMillis; if (mPositionX < 0) { mPositionX = 0; } if (mPositionX > mMaxX) { mPositionX = mMaxX; } mPositionY += mSpeedFactor*inputController.mVerticalFactor*elapsedMillis ; if (mPositionY < 0) { mPositionY = 0; } if (mPositionY > mMaxY) { mPositionY = mMaxY; } } @Override public void onDraw() { mTextView.setText("["+(int) (mPositionX)+","+(int) (mPositionY)+"]"); }
So, in each run of onUpdate
, we will increase the x
and y
position using the corresponding factor (which we read from the input controller), a speed factor, and the elapsed milliseconds. This is nothing more than the classic formula distance = speed * time.
The rest of the code ensures that the x and y positions stay inside the boundaries of the screen.
The onDraw
method is equivalent to the one of ScoreGameObject
, but it just sets text in TextView
.
Now there are a few values in this code that we have not initialized. They are as follows:
mSpeedFactor
: The speed converted into pixels per millisecond.mMaxX
: The maximum value forx
. It will be the width of the view minus the paddings.mMaxY
: The maximum value fory
. It is the height of the view minus the padding.mTextView
: The view in which we set the current coordinates.
All these elements are initialized on the constructor of the Player
object that receives the parent view as a parameter:
public Player(final View view) { // We read the size of the view double pixelFactor = view.getHeight() / 400d; mSpeedFactor = pixelFactor * 100d / 1000d; mMaxX = view.getWidth() - view.getPaddingRight() - view.getPaddingRight(); mMaxY = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom(); mTextView = (TextView) view.findViewById(R.id.txt_score); }
We calculate the pixel factor of our screen, taking a height of 400 units as a reference. This is an arbitrary number and you can use whatever makes sense to you. It will help if you think of working with a 400 px tall screen and then let the code convert it to the real amount of pixels.
This is a concept that is similar to dips, but also different. While dips are meant to have the same physical size among all devices, the units make our game scale. So, all the items of the game will take the same amount of screen space regardless of the resolution or size of the device.
Note
We will define the game space in "units" so all the devices have the same screen height.
We want our ship to move at a speed of 100 units per second, so moving across the screen from its bottom to the top takes 4 seconds. Since we need the speed in pixels per millisecond, we need to multiply the desired speed with the pixel factor (pixels/unit) and pide it by 1,000 (milliseconds/second).
The next step is to read the width and height of the parent view and use them as the maximum width and height after subtracting the padding.
Finally, we get a hook into the TextView
that we are going to use to display the coordinates.
Once we have finished the initialization, we still have the startGame
method. In this one, we will position our player in the middle of the screen.
@Override public void startGame() { mPositionX = mMaxX / 2; mPositionY = mMaxY / 2; }
If you try and run the example now, you will see that the position stays at [0,0], indicating that something is going wrong.
The problem is that we are reading the width and height of a view straight after it is created (inside the onViewCreated
method of the GameFragment
). At this moment, the view has not yet been measured.
Note
You cannot obtain the width and/or height of a view during the constructor, as it has not been measured yet.
The solution for this is to delay the initialization of the GameEngine
until the view has been measured. The best way to do this is to use ViewTreeObserver
. Let's go to the onViewCreated
of the GameFragment
and update it:
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); view.findViewById(R.id.btn_play_pause).setOnClickListener(this); final ViewTreeObserver obs = view.getViewTreeObserver(); obs.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { obs.removeGlobalOnLayoutListener(this); } else { obs.removeOnGlobalLayoutListener(this); } mGameEngine = new GameEngine(getActivity()); mGameEngine.setInputController(new BasicInputController(getView())); mGameEngine.addGameObject(new Player(getView())); mGameEngine.startGame(); } }); }
We get the ViewTreeObserver
of the view that has just been created for the layout and add a new OnGlobalLayoutListener
to it. We create the listener as an anonymous inner class.
This listener will be called every time a global layout is performed. To avoid being called multiple times and, therefore, initializing multiple engines, we need to remove the listener as soon as it is called.
Unfortunately, there was a typo in the name of the method used to remove the listener in Android versions prior to Jelly Bean, so we have to use one method name for versions before Jelly Bean and another one for later versions.
The rest of the code inside the method is the engine initialization, which was previously done directly inside onViewCreated
. We just moved it inside onGlobalLayout
.
Note that, while the views have not been measured yet, they have been created and they exist. So, there is no need to move the code that sets the OnClickListener
for the pause button to the layout observer.
If we go on and run this version, we will see that the coordinates show the value of the center of the screen in pixels.

Displaying a spaceship
All this is not fun if we don't at least show a spaceship, so we can see that something is really happening.
We are going to take the graphics for the game from the OpenGameArt website (http://opengameart.org), which contains multiple free—as in freedom—graphics for games, most of them under a Creative Commons license, which means you have to credit the author.
Note
The OpenGameArt.org website is a great resource for game graphics.
The spaceships we are going to show were created by Eikesteer and we will use them throughout the game.

The spaceship set made by Eikesteer that we picked from Open Game Art
From the set, we will use the third from the right. We can extract it to a new image using a simple editor such as GIMP and place it under the drawable-nodpi
directory.
Note that we are going to scale everything to be consistent with our 400 units of screen height, so it does not make sense to put the image in a drawable directory that has a density qualifier. This is why we are going to use drawable-nodpi
.
The drawable-nodpi
directory is meant to be independent from any density, while drawable
is meant for images that do not have a qualifier. This means that the behavior is different when we try to read the intrinsic size of a drawable image. The intrinsic size will return the real size when placed under nodpi
and will depend on the device when read from drawable
.
Note
We will place our game object images in the drawable-nodpi
folder.
The next step is to create an ImageView
to display our spaceship. We are going to do this inside the constructor of the Player
object:
public Player(final View view) { [...] // We create an image view and add it to the view mShip = new ImageView(view.getContext()); Drawable shipDrawable = view.getContext().getResources() .getDrawable(R.drawable.ship); mShip.setLayoutParams(new ViewGroup.LayoutParams( (int) (shipDrawable.getIntrinsicWidth() * mPixelFactor), (int) (shipDrawable.getIntrinsicHeight() * mPixelFactor))); mShip.setImageDrawable(shipDrawable); mMaxX -= (shipDrawable.getIntrinsicWidth()*mPixelFactor); mMaxY -= (shipDrawable.getIntrinsicHeight()*mPixelFactor); ((FrameLayout) view).addView(mShip); }
The first part of the constructor remains unchanged. We then add the code to create the ImageView
and load the Drawable
into it.
First, we create an ImageView
using the Context
of the parent view and we store it as a class variable.
Then, we load the Drawable
of the ship from the resources and assign it to the shipDrawable
local variable.
We proceed to create a LayoutParams
object for the ImageView
and set it. Since we already have the drawable, we can specify the exact dimensions for it. For this, we read the intrinsic width and height of the shipDrawable
and multiply it by the pixel factor.
This means that the ImageView
of the spaceship will be scaled to the equivalent of a 400-unit screen in pixels. Another way to say this is that the spaceship is the exact same size as it would be if displayed on a 400-pixel-tall screen. The drawable is then set to the ImageView
.
We also have to update the maximum value of x and y by subtracting the size of the ship. With this, it gets placed in the center and it does not go outside the borders.
Finally, the ImageView
is added to the parent view, which is expected to be a FrameLayout
. This new requirement comes from the need to be able to position the image anywhere.
This is something we need to update or we will get a ClassCastException
. We are updating the fragment_game.xml
layout to have a top layout of the FrameLayout
type.
Now that we are touching the layout, we will also align the pause button to the top right, which is where the pause button for most games is:
<FrameLayout 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:paddingTop="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" tools:context="com.plattysoft.yass.counter.GameFragment"> <TextView android:layout_gravity="top|left" android:id="@+id/txt_score" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <Button android:layout_gravity="top|right" android:id="@+id/btn_play_pause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pause" /> </FrameLayout>
Finally, we need to update the onDraw
method to make it display the spaceship in the right position. For this, we just translate the ImageView
to the expected position on the screen using translateX
and translateY
.
This is far from optimal, but we will work on the drawing in the next chapter. For now, it serves the purpose of displaying the image at the right position:
@Override public void onDraw() { mTextView.setText("["+(int) (mPositionX)+","+(int) (mPositionY)+"]"); mShip.setTranslationX((int) mPositionX); mShip.setTranslationY((int) mPositionY); }
If we launch our game, we can see the spaceship in the middle of the screen:

Now that we have a spaceship, it is time to add some bullets to the mix.
Firing bullets
The spaceship will fire bullets that will move upward until they are outside the screen.
As we mentioned in the Good practices for game developers
section of the previous chapter, we will use an object pool for the bullets that we will create inside the Player
class:
List<Bullet> mBullets = new ArrayList<Bullet>(); private void initBulletPool() { for (int i=0; i<INITIAL_BULLET_POOL_AMOUNT; i++) { mBullets.add(new Bullet(mPixelFactor)); } } private Bullet getBullet() { if (mBullets.isEmpty()) { return null; } return mBullets.remove(0); } private void releaseBullet(Bullet b) { mBullets.add(b); }
It initializes the number of bullets we want to have on the screen at a certain point. If you ask for a bullet when the pool has items it will just remove one and return it, but if the list is empty, it will return null. You can make this number a limitation to impact the gameplay or you can do the math and make the pool large enough.
In our case, we cannot fire more than 6 bullets, given the speed of the bullets and the time between shots.
Back to the pool, to release a bullet we will simply put it back into the list.
Now, during the onUpdate
of the player, we check if we should fire a bullet:
@Override public void onUpdate(long elapsedMillis, GameEngine gameEngine) { updatePosition(elapsedMillis, gameEngine.mInputController); checkFiring(elapsedMillis, gameEngine); } private void checkFiring(long elapsedMillis, GameEngine gameEngine) { if (gameEngine.mInputController.mIsFiring && mTimeSinceLastFire > TIME_BETWEEN_BULLETS) { Bullet b = getBullet(); if (b == null) { return; } b.init(mPositionX + mShip.getWidth()/2, mPositionY); gameEngine.addGameObject(b); mTimeSinceLastFire = 0; } else { mTimeSinceLastFire += elapsedMillis; } }
We check whether the input controller has the fire button pressed and whether the cool down time has passed. If we want and can fire a bullet we take one from the pool.
If there is no bullet available (the object b is null), we do nothing else and return.
Once we get a Bullet
from the pool, we initialize it using the current position and place it in the middle of the spaceship. Then, we add it to the engine. To conclude, we reset the time since the last fire.
If we cannot or do not want to fire, we just add the elapsed milliseconds to the time since the last bullet was fired.

In the preceding image, we can see the relative position of the bullet with the spaceship and why passing the x coordinate as the center of the spaceship gives the right information to the bullet. But we still need to add some offsets to it.
From this moment on, all the logic regarding the movement of the bullet is done inside the Bullet
object.
The Bullet game object
The Bullet
extends GameObject
as well. And, as the spaceship does, it also creates an ImageView
and loads the drawable into it as part of the constructor:
public Bullet(View view, double pixelFactor) { Context c = view.getContext(); mSpeedFactor = pixelFactor * -300d / 1000d; mImageView = new ImageView(c); Drawable bulletDrawable = c.getResources().getDrawable(R.drawable.bullet); mImageHeight = bulletDrawable.getIntrinsicHeight() * pixelFactor; mImageWidth = bulletDrawable.getIntrinsicWidth() * pixelFactor; mImageView.setLayoutParams(new ViewGroup.LayoutParams( (int) (mImageWidth), (int) (mImageHeight))); mImageView.setImageDrawable(bulletDrawable); mImageView.setVisibility(View.GONE); ((FrameLayout) view).addView(mImageView); }
The only difference between this constructor and the one for the Player
object is that we set the visibility of ImageView
to GONE
, since the bullets are not supposed to be displayed unless they are being fired. The Bullet
also has an mPositionX
and mPositionY
used for drawing.
These similarities come from the fact that both the game objects are what we call sprites. Sprite is a GameObject
that has an image associated with it and gets rendered on the screen.
Note
Sprite is a game object (generally a 2D image) that is displayed in a game and manipulated as a single entity.
In the next chapter, we will extract the common concepts of sprite and put them in a base class.
In the constructor, we also set the speed of the bullet to 300 units per second. This is 3 times faster than the spaceship. You can play with the values of speed and time between bullets, but remember to test that they do not overlap during continuous fire while the spaceship moves upward.
If you modify the bullet speed, you may also need to check the size of the pool. The worst case is to continuously fire with the spaceship placed at the bottom of the screen.
The next interesting point is initialization. This is done using the init
method that receives the position of the spaceship:
public void init(Player parent, double positionX, double positionY) { mPositionX = positionX - mImageWidth/2; mPositionY = positionY - mImageHeight/2; mParent = parent; }
It is worth mentioning that we want to position the bullet a bit ahead of the spaceship and properly centered. Since the member variables mPositionX
and mPositionY
are pointing to the top-left corner of the image, we have to apply an offset to the initial parameters based on the size of the bullet.
We are positioning the bullet only half way outside the spaceship on the vertical axis (mImageHeight/2) to improve the feeling of it being shot from the spaceship. We are also displaying it centered on the horizontal axis, which is why we also subtract mImageWidth/2.
The image in the previous section will also help you visualize this offset.
Because the Bullets
are added and removed from the GameEngine
, we need to change the visibility of the view when they are added and removed. This needs to be done on the UIThread
. For this purpose, we use the callbacks we created in the previous chapter:
@Override public void onRemovedFromGameUiThread() { mImageView.setVisibility(View.GONE); } @Override public void onAddedToGameUiThread() { mImageView.setVisibility(View.VISIBLE); }
Note
All changes to the view must be done on the UIThread
, otherwise an exception will be thrown.
Since these bullets are also sprites, the onDraw
method is almost identical to the one of the player. We do it again by animating the view and translating it:
@Override public void onDraw() { mImageView.setTranslationX((int) mPositionX); mImageView.setTranslationY((int) mPositionY); }
On the other hand, the onUpdate
method is a bit different and it is interesting to look at it in detail:
@Override public void onUpdate(long elapsedMillis, GameEngine gameEngine) { mPositionY += mSpeedFactor * elapsedMillis; if (mPositionY < -mImageHeight) { gameEngine.removeGameObject(this); // And return it to the pool mParent.releaseBullet(this); } }
Similar to what we did with the player, we use the distance = speed * time formula. But, in this case, there is no influence from the InputController
at all. The bullet has a fixed vertical speed.
We also check whether the bullet is out of the screen. Since we draw the items in the top-left corner, we need it to be completely gone. This is why we compare with mImageHeight
.
If the bullet is out, we remove it from the GameEngine
and we return it to the pool by calling releaseBullet
.
This game object removal is done inside the onUpdate
loop of the GameEngine
. If we modify the list at this moment, we will get an ArrayIndexOutOfBoundsException
while executing onUpdate
in the GameEngine
. This is why the removeGameObject
method puts the objects in a separate list to be removed after onUpdate
is called.
Now, all this is useless unless we can move the spaceship and fire the bullets. Let's build the most basic InputController
.