If you are lucky and NOT old enough to remember Pong then you should take a look at it and find out some more before continuing. We will be programming the further simplified, single-player, Squash-style version of the game. The player must bounce the ball off the “back” wall and he gets a point each time he manages to hit it. He will lose a “life” each time he misses it. To get started, create a new project called Pong, use the “Empty Activity” template and leave all the other settings as they are.

patreon

 

 

The image above shows what we are aiming for in this project. Everything you need can be copied & pasted from the pages that make up this tutorial.

Programming the Ball class

Create a new class called Ball. You can do this by right-clicking on the folder that contains the MainActivity class and selecting New Class… As the name suggests, this class will handle everything to do with the ball. We will code this class in a few short segments and explain things as we go.

The Ball member variables

First, add some variables. An RectF object is an object which holds four <float values that define the four coordinates of a rectangle. This is perfect for a ball that looks like a square. We also have four float variables to represent the speed of the ball in the horizontal(x) and vertical(y) axes as well as the width and height of the ball. Note that in this project I am using the convention of prefixing a m to the front of all the member variables of a class so they will be more distinct from all the local variables of the methods of the class.

Add the member variables to the Ball class.

public class Ball {

    private RectF mRect;
    private float mXVelocity;
    private float mYVelocity;
    private float mBallWidth;
    private float mBallHeight;

}

The Ball constructor

In the Ball constructor method, the code initializes the width and height relative to the width of the current device’s screen which was passed in as a parameter. Then we initialize the horizontal and vertical speed relative to the height in pixels of the current device’s screen. The ball will be one-hundredth of the screen width and will travel at one-quarter of the screen height every second.

Note that this is a little bit different from what we did in the Snake project. We will see how we control this shortly.

Next, we call new on our RectF object but note that we don’t actually initialize any of its four member variables just yet.
Add the Ball constructor method is shown below.

public Ball(int screenX, int screenY){

	// Make the mBall size relative to the screen resolution
	mBallWidth = screenX / 100;
	mBallHeight = mBallWidth;

	/*
		Start the ball travelling straight up
		at a quarter of the screen height per second
	*/
	mYVelocity = screenY / 4;
	mXVelocity = mYVelocity;

	// Initialize the Rect that represents the mBall
	mRect = new RectF();

}

More Ball methods

Next, we will code a public method so we can get hold of the RectF (which is the graphical representation of the ball) from outside the Ball class. Go ahead and add the getRect method to the Ball class.

// Give access to the Rect
public RectF getRect(){
	return mRect;
}

Next, we will code the update method. Note that the update method in the Ball class is distinct from the update method that we will write in our thread in the PongView class. Although the latter will call the former. This update method will be called once every frame of the game. It updates the top and left values of the ball based on the velocity member variables ( mXVelocity and mYVelocity) divided by the number of frames per second ( fps) that the device is managing to run the game.

We have yet to see how the frames per second are calculated but by moving the ball relative to the frames per second, it will move at a consistent speed regardless of how powerful(or puny) the device’s processor is.

After this the other points of mRect are updated relative to the top-left and the size of the ball.

Add the code for the update method to the Ball class.

// Change the position each frame
public void update(long fps){
	mRect.left = mRect.left + (mXVelocity / fps);
	mRect.top = mRect.top + (mYVelocity / fps);
	mRect.right = mRect.left + mBallWidth;
	mRect.bottom = mRect.top - mBallHeight;
}

Next, we need a few more methods that will enable us to easily deal with various events that will occur throughout the game. At various times in the game, we need to be able to do the following:

  • Reverse the vertical direction
  • Reverse the horizontal direction
  • Set a new random x velocity
  • Speed up the ball

The reverseYVelocity, reverseXVelocity, setRandomXVelocity and increaseVelocity methods achieve these things by applying simple mathematics.

Add the four methods we have just discussed to the Ball class.

// Reverse the vertical heading
public void reverseYVelocity(){
	mYVelocity = -mYVelocity;
}

// Reverse the horizontal heading
public void reverseXVelocity(){
	mXVelocity = -mXVelocity;
}

public void setRandomXVelocity(){

	// Generate a random number either 0 or 1
	Random generator = new Random();
	int answer = generator.nextInt(2);

	if(answer == 0){
		reverseXVelocity();
	}
}

// Speed up by 10%
// A score of over 20 is quite difficult
// Reduce or increase 10 to make this easier or harder
public void increaseVelocity(){
	mXVelocity = mXVelocity + mXVelocity / 10;
	mYVelocity = mYVelocity + mYVelocity / 10;
}

Next, we will add some more methods that do the following things.

One which clears an obstacle on the vertical axis ( clearObstacleY)
Another which clears an obstacle on the horizontal axis ( clearObstacleX)
And one which resets the position of the ball in the bottom center of the screen ( reset).

Each of these methods adjusts/repositions the ball. Their usefulness will become apparent when we see them in action when we code the PongView class in a minute.

public void clearObstacleY(float y){
	mRect.bottom = y;
	mRect.top = y - mBallHeight;
}

public void clearObstacleX(float x){
	mRect.left = x;
	mRect.right = x + mBallWidth;
}

public void reset(int x, int y){
	mRect.left = x / 2;
	mRect.top = y - 20;
	mRect.right = x / 2 + mBallWidth;
	mRect.bottom = y - 20 - mBallHeight;
}

Now our ball can start bouncing. Let’s get the bat coded and then we can code the game engine.

Programming the Bat class

The class has a RectF for holding the bat’s four coordinates. We also have separate mXcoord and mYCoord floating point variables that hold the left and top positions.

We have a float variable for the speed ( mBatSpeed). Next, we have three final int members ( STOPPED, LEFT and RIGHT) which are public. We will be able to refer to these values from outside of the class to manipulate the bat’s direction.

We also have a private variable ( mBatMoving) which will always be assigned one of those three public final values. We begin by setting it to STOPPED. In the Bat class, we want to keep a permanent copy of the screen resolution (size in pixels) so we declare mScreenX and mScreenY which we will initialize soon.

Add the Bat member variables that we have just discussed.

public class Bat {

    // RectF is an object that holds four coordinates - just what we need
    private RectF mRect;

    // How long and high our mBat will be
    private float mLength;
    private float mHeight;

    // X is the far left of the rectangle which forms our mBat
    private float mXCoord;

    // Y is the top coordinate
    private float mYCoord;

    // This will hold the pixels per second speed that
	 // the mBat will move
    private float mBatSpeed;

    // Which ways can the mBat move
    public final int STOPPED = 0;
    public final int LEFT = 1;
    public final int RIGHT = 2;

    // Is the mBat moving and in which direction
    private int mBatMoving = STOPPED;

    // The screen length and width in pixels
    private int mScreenX;
    private int mScreenY;

}

Coding the Bat constructor

Now, we will add the constructor method. In the constructor method, we initialize mScreenX and mScreenY with the passed-in x and y values. We initialize the length of the bat to one-eighth of the screen width and the height to one-twenty-fifth.

Next, the code initializes mXCoord and mYCoord to roughly the bottom center of the screen. We set mBatSpeed it to the same value as mScreenX, which has the effect of setting the bat’s movement to be able to cover the entire screen in one second. This is not as fast as it might first seem. Of course, you can always come back here and make it slower (or even faster) if you want to.

Add the code for the Bat constructor.

// This is the constructor method
// When we create an object from this class we will pass
// in the screen width and mHeight
public Bat(int x, int y){

	mScreenX = x;
	mScreenY = y;

	// 1/8 screen width wide
	mLength = mScreenX / 8;

	// 1/25 screen mHeight high
	mHeight = mScreenY / 25;

	// Start mBat in roughly the sceen centre
	mXCoord = mScreenX / 2;
	mYCoord = mScreenY - 20;

	mRect = new RectF(mXCoord, mYCoord, mXCoord + mLength, mYCoord + mHeight);

	// How fast is the mBat in pixels per second
	mBatSpeed = mScreenX;
	// Cover entire screen in 1 second
}

More Bat methods

Add the public method to return the RectF instance that represents the bat’s location.

// This is a getter method to make the rectangle that
// defines our bat available in PongView class
public RectF getRect(){
	return mRect;
}

The setMovementState method receives a int value as a parameter. We will call this method using one of the three public final int members, LEFT, RIGHT or STOPPED. This method will simply set that state to the mBatMoving member.

// This method will be used to change/set if the mBat is going
// left, right or nowhere

public void setMovementState(int state){
	mBatMoving = state;
}

The final method for the Bat class is its update method. First, it uses a couple of if statements to see if it is moving left or right. If it is, it moves the mXCoord by mBatSpeed divided by the current number frames per second ( fps), just like the Ball class did.

Next, it does two checks to see if the bat might be moving off the screen. If the bat is about to disappear off the left it prevents it by setting mXCoord to . If it is about to disappear off to the right it sets mXCoord to mScreenX, less the width of the bat.

Finally, based on the results of all those if statements the code updates the values held by the RectF ready for the game engine to make use of them when it calls getRect.

// This update method will be called from update in PongView
// It determines if the Bat needs to move and changes the coordinates
// contained in mRect if necessary
public void update(long fps){

	if(BatMoving == LEFT){
		mXCoord = mXCoord - mBatSpeed / fps;
	}

	if(BatMoving == RIGHT){
		mXCoord = mXCoord + mBatSpeed / fps;
	}

	// Make sure it's not leaving screen
	if(mRect.left < 0){ mXCoord = 0; } if(mRect.right > mScreenX){
		mXCoord = mScreenX -
				// The width of the Bat
				(mRect.right - mRect.left);
	}

	// Update the Bat graphics
	mRect.left = mXCoord;
	mRect.right = mXCoord + mLength;
}

Programming the MainActivity class

Most of the action will take place in the next class that we create. We will call that class PongView. So the job of MainActivity is to communicate with the events of the OS and pass on any relevant information to PongView. It also needs to create the PongView object for us. Notice in the next code there is indeed an object of the type PongView declared as a member. Obviously, this will show as an error because we haven’t coded it yet.

In the onCreate method, we use an Display object and the getWindowManager().getDefaultDisplay() chained methods to initialize it. Then we declare an object of the type Point. Using the Display object we can load the screen resolution into point using the getSize method.

We can now call the constructor of PongView to initialize pongView. Notice when we do that we pass in x and y, which is the horizontal and vertical screen resolution. It is from this constructor method in PongView that our Bat and Ball objects will eventually get hold of these values.

Finally, in onCreate we pass in our PongView reference as the argument to setContentView. Whatever we draw in PongView will be displayed as the visuals for the app. This is exactly what we need. PongView will extend SurfaceView from the Android API which not only allows us to have a Thread instance, it also implements onTouchListener allows us to attach an Canvas object to draw all the graphics.

Go ahead and add the variables and the new code to the onCreate method.

// pongView will be the view of the game
// It will also hold the logic of the game
// and respond to screen touches as well
PongView pongView;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	// Get a Display object to access screen details
	Display display = getWindowManager().getDefaultDisplay();

	// Load the resolution into a Point object
	Point size = new Point();
	display.getSize(size);

	// Initialize pongView and set it as the view
	pongView = new PongView(this, size.x, size.y);
	setContentView(pongView);

}

We are almost finished with the MainActivity class. In MainActivity we will override the onResume and onPause methods. In these methods, we will call the resume and pause methods on our PongView instance. In those methods, in the PongView class, we will start and stop the thread which controls the game. This is just what we need as after our PongView class is setup, as it’s constructor is called in onCreate, then onResume will run and set the thread going as well. Then when the player quits the app and the OS calls  onPause our pause method will be called and the thread will be stopped. Otherwise, our ball will still be bouncing and around the screen and beeping, perhaps while the player is receiving a call from his girlfriend.
Add the overridden onResume and onPause methods to the MainActivity class.

// This method executes when the player starts the game
@Override
protected void onResume() {
	super.onResume();

	// Tell the pongView resume method to execute
	pongView.resume();
}

// This method executes when the player quits the game
@Override
protected void onPause() {
	super.onPause();

	// Tell the pongView pause method to execute
	pongView.pause();
}

Now we can code the most important class of our game.

Programming the SurfaceView (PongView)

Create a new class called PongView, extend SurfaceView and implement Runnable. I show you this code after we have discussed it a bit more.
We will need quite a few member variables. Let’s discuss them in turn:

  • A Thread called mGameThread that we will start and stop from the pause and resume methods that we will implement soon. These methods are of course called by the onResume and onPause methods of the MainActivity class
  • SurfaceHolder is what we need to allow us to do to our drawing
  • Volatile boolean mPlaying will be true when the thread is running and false otherwise. It will be used to determine whether we enter a while loop that will control the whole game loop. The volatile the keyword is used because mPlaying can be accessed from outside and inside the thread.
  • We have a boolean mPaused which will determine whether the game is currently paused.
  • We have a Paint and an Canvas object which we will use to draw and draw on respectively.
  • Next, we have a long variable mFPS which will hold the current number of frames per second that our game loop is achieving and of course, this is the value we will be passing into the update methods of Bat and Ball to allow them to move by the correct amount.
  • Next, we declare mScreenX and mScreenY to hold the screen resolution which as we saw, is passed into the constructor from MainActivity when we instantiate an PongView object. We will code that constructor very soon.
  • Now we get to the fun bits; a Ball object mBall and a Bat object mBat.
  • Next up we have all the members that will take care of sound FX including a SoundPool and four int identifiers for sound FX.
  • Finally mLives and mScore will keep track of the player’s score and how many lives they have left

Code the PongView class and its members as we have just discussed.

class PongView extends SurfaceView implements Runnable {

    // This is our thread
    Thread mGameThread = null;

    // We need a SurfaceHolder object
    // We will see it in action in the draw method soon.
    SurfaceHolder mOurHolder;

    // A boolean which we will set and unset
    // when the game is running- or not
	// It is volatile because it is accessed from inside and outside the thread
    volatile boolean mPlaying;

    // Game is mPaused at the start
    boolean mPaused = true;

    // A Canvas and a Paint object
    Canvas mCanvas;
    Paint mPaint;

    // This variable tracks the game frame rate
    long mFPS;

    // The size of the screen in pixels
    int mScreenX;
    int mScreenY;

    // The players mBat
    Bat mBat;

    // A mBall
    Ball mBall;

    // For sound FX
    SoundPool sp;
    int beep1ID = -1;
    int beep2ID = -1;
    int beep3ID = -1;
    int loseLifeID = -1;

    // The mScore
    int mScore = 0;

    // Lives
    int mLives = 3;
}

Let’s discuss the constructor method for PongView.

We initialize mScreenX and mScreenY from the passed-in screen resolution. Then we initialize mOurHolder by calling getHolder and mPaint by calling the default Paint constructor. Next, we instantiate our Bat and Ball by calling their constructors and passing in the screen resolution as is required by the method signatures.

Almost all the rest of the code sets up the sound effects ready to be played. I have gone through the trouble in this code to detect the version of Android and use the appropriate code depending upon the version. The final line of code calls the setupAndRestart method to start a new game and we will code that method shortly.

Add the PongView constructor code.

/*
	When the we call new() on pongView
	This custom constructor runs
*/

public PongView(Context context, int x, int y) {

	/*
		The next line of code asks the
		SurfaceView class to set up our object.
	*/
	super(context);

	// Set the screen width and height
	mScreenX = x;
	mScreenY = y;

	// Initialize mOurHolder and mPaint objects
	mOurHolder = getHolder();
	mPaint = new Paint();

	// A new mBat
	mBat = new Bat(mScreenX, mScreenY);

	// Create a mBall
	mBall = new Ball(mScreenX, mScreenY);

	/*
		Instantiate our sound pool
		dependent upon which version
		of Android is present
	*/

	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
		AudioAttributes audioAttributes = new AudioAttributes.Builder()
				.setUsage(AudioAttributes.USAGE_MEDIA)
				.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
				.build();

		sp = new SoundPool.Builder()
				.setMaxStreams(5)
				.setAudioAttributes(audioAttributes)
				.build();

	} else {
		sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
	}

	try{
		// Create objects of the 2 required classes
		AssetManager assetManager = context.getAssets();
		AssetFileDescriptor descriptor;

		// Load our fx in memory ready for use
		descriptor = assetManager.openFd("beep1.ogg");
		beep1ID = sp.load(descriptor, 0);

		descriptor = assetManager.openFd("beep2.ogg");
		beep2ID = sp.load(descriptor, 0);

		descriptor = assetManager.openFd("beep3.ogg");
		beep3ID = sp.load(descriptor, 0);

		descriptor = assetManager.openFd("loseLife.ogg");
		loseLifeID = sp.load(descriptor, 0);

		descriptor = assetManager.openFd("explode.ogg");
		explodeID = sp.load(descriptor, 0);

	}catch(IOException e){
		// Print an error message to the console
		Log.e("error", "failed to load sound files");
	}

	setupAndRestart();

}

Here is the setupAndRestart method that we first call from the constructor. We will also call this method at the end of every game to start a new one. The code calls the reset method on the Ball instance to position it for the start of a game and if necessary, resets the mScore and mLives variables to and 3 respectively.
Add the setupAndRestart method to the PongView class.

public void setupAndRestart(){

	// Put the mBall back to the start
	mBall.reset(mScreenX, mScreenY);

	// if game over reset scores and mLives
	if(mLives == 0) {
		mScore = 0;
		mLives = 3;
	}

}

Here we have the run method which is the code that is running in a thread. We have a while loop controlled by the value of our volatile boolean called mPlaying. This while loop wraps all the rest of the code inside the run method.

Inside the while loop, we get the system time in milliseconds (thousandths of a second) and initialize the startFrameTime variable with the result. Then we check if the game is currently paused if(!mPaused) and if the game isn’t paused we call the update method. Note this is the update method of the PongView class, not the Ball or Bat classes’ update methods. We will code this method soon.

Next, we call the draw method which will contain all our drawing code. Now we calculate the time the frame took to execute by getting the current system time again and subtracting startFrameTime from the result. We then put the result into mFPS which of course will be passed on to the update methods of the Ball and Bat classes when they are called. The reason we wrap the last bit of code in if (timeThisFrame >= 1) is because if timeThisFrame equals zero, trying to divide by zero will crash the game.

Code the overridden run method.
@Override
public void run() {
	while (mPlaying) {

		// Capture the current time in milliseconds in startFrameTime
		long startFrameTime = System.currentTimeMillis();

		// Update the frame
		// Update the frame
		if(!mPaused){
			update();
		}

		// Draw the frame
		draw();

		/*
			Calculate the FPS this frame
			We can then use the result to
			time animations in the update methods.
		*/
		long timeThisFrame = System.currentTimeMillis() - startFrameTime;
		if (timeThisFrame >= 1) {
			mFPS = 1000 / timeThisFrame;
		}

	}

}

Programming the update method

The update method is quite long so we will go through and code it one piece at a time. Add the signature and the body of the update method and we will steadily add all the code to it.

// Everything that needs to be updated goes in here
// Movement, collision detection etc.
public void update(){

}

First, the code calls the update methods on our Ball and our Bat instances to handle any required movement.

// Everything that needs to be updated goes in here
// Movement, collision detection etc.
public void update() {

	// Move the mBat if required
	mBat.update(mFPS);

	mBall.update(mFPS);
}

Now the ball and the bat are in their new positions for this frame, we can run some tests to see if anything we need to respond to has happened. The first test is to see if the ball has hit the bat. Using the getRect methods of both the Ball and Bat we pass the two returned results into the static intersects method of RectF. The intersects method returns true if the ball and the bat intersect/overlap (have collided) each other. If a collision is detected, execution enters the if block and does a number of things:

  • Call setRandomXVelocity on the ball to choose a random horizontal direction for when the ball heads back up the screen
  • Call reverseYVelocity to start to head back up the screen
  • call clearObstacle which jumps the ball a few pixels and avoids the possibility of the ball getting stuck on the bat
  • Increment mScore to increase the player’s score
  • Play a beep sound from the SoundPool

Add the code we have just discussed to the update method.

	// Check for mBall colliding with mBat
	if(RectF.intersects(mBat.getRect(), mBall.getRect())) {
		mBall.setRandomXVelocity();
		mBall.reverseYVelocity();
		mBall.clearObstacleY(mBat.getRect().top - 2);

		mScore++;
		mBall.increaseVelocity();

		sp.play(beep1ID, 1, 1, 0, 0, 1);
	}

Next, we handle what happens if the ball hits the bottom of the screen. The test to see if this has happened works by calculating the position of the underside of the ball ( mBall.getRect.bottom) and comparing it to the height of the screen in pixels ( mScreenY). If the collision has occurred the following steps happen inside the if block.

  • Reverse the velocity of the ball
  • Jump a few pixels in case the ball gets stuck
  • decrement mLives
  • play a losing sound
  • Check if that was the last life and if it is, then pause the game and call setupAndRestart

Add the code we have just discussed to the update method.

	// Bounce the mBall back when it hits the bottom of screen
	if(mBall.getRect().bottom > mScreenY){
		mBall.reverseYVelocity();
		mBall.clearObstacleY(mScreenY - 2);

		// Lose a life
		mLives--;
		sp.play(loseLifeID, 1, 1, 0, 0, 1);

		if(mLives == 0){
			mPaused = true;
			setupAndRestart();
		}
	}

The next block of code uses the top of the ball and compares it to zero to see if it has reached the top of the screen. If it has it just reverses the ball on the vertical axis, clears any potential obstacles, and plays a beep.

Add the new code to the update method.

	// Bounce the mBall back when it hits the top of screen
	if(mBall.getRect().top < 0){
		mBall.reverseYVelocity();
		mBall.clearObstacleY(12);

		sp.play(beep2ID, 1, 1, 0, 0, 1);
	}

The next block of code uses the left of the ball and compares it to zero to see if it has reached the left of the screen. If it has it reverses the ball on the horizontal axis, clears any obstacles and plays a beep.

Add the new code to the update method.

	// If the mBall hits left wall bounce
	if(mBall.getRect().left < 0){
		mBall.reverseXVelocity();
		mBall.clearObstacleX(2);

		sp.play(beep3ID, 1, 1, 0, 0, 1);
	}

The next code block uses the right of the ball and compares it to mScreenX and sees if it has reached the right-hand side of the screen. If it has it just reverses the ball on the horizontal axis, clears any obstacles and as usual, plays a beep.

Add the new code we have just discussed to the update method.

	// If the mBall hits right wall bounce
	if(mBall.getRect().right > mScreenX){
		mBall.reverseXVelocity();
		mBall.clearObstacleX(mScreenX - 22);

		sp.play(beep3ID, 1, 1, 0, 0, 1);
	}

}

Programming the draw method

The first thing we have to do is attempt to get a lock on the surface to draw on and check it is valid. This is achieved with the following line of code. (don’t add it just yet)

// Make sure our drawing surface is valid or we crash
	if (mOurHolder.getSurface().isValid()) {

	// Draw everything here

}

If the test returns true we are almost ready to draw. We just need this code before we start using our canvas (don’t add this yet).

// Lock the mCanvas ready to draw
mCanvas = mOurHolder.lockCanvas();

Now we can get busy and actually draw things with mPaint and mCanvas. This is the order we will do things in the draw method:

  • Make sure the surface is valid and lock the canvas as previously discussed
  • Draw a background color
  • Change the brush color
  • Draw the bat as a rectangle by passing in getRect as the argument
  • Draw the ball as a rectangle by calling getRect as the argument
  • Change the brush color again
  • Change the size of the text
  • Draw the score and number of lives on the screen
  • Call mOurHolder.unlockCanvasAndPost(mCanvas) to finish the drawing process for this frame

Add the draw method we have just discussed to the PongView class.

// Draw the newly updated scene
public void draw() {

	// Make sure our drawing surface is valid or we crash
	if (mOurHolder.getSurface().isValid()) {

		// Draw everything here

		// Lock the mCanvas ready to draw
		mCanvas = mOurHolder.lockCanvas();

		// Clear the screen with my favorite color
		mCanvas.drawColor(Color.argb(255, 120, 197, 87));

		// Choose the brush color for drawing
		mPaint.setColor(Color.argb(255, 255, 255, 255));

		// Draw the mBat
		mCanvas.drawRect(mBat.getRect(), mPaint);

		// Draw the mBall
		mCanvas.drawRect(mBall.getRect(), mPaint);

		// Change the drawing color to white
		mPaint.setColor(Color.argb(255, 255, 255, 255));

		// Draw the mScore
		mPaint.setTextSize(40);
		mCanvas.drawText("Score: " + mScore + "   Lives: " + mLives, 10, 50, mPaint);

		// Draw everything to the screen
		mOurHolder.unlockCanvasAndPost(mCanvas);
	}

}

Now we can code our pause and resume methods that stop and start the thread. As we have already discussed, these methods are called by the MainActivity class in response to the Android OS.

// If the Activity is paused/stopped
// shutdown our thread.
public void pause() {
	mPlaying = false;
	try {
		mGameThread.join();
	} catch (InterruptedException e) {
		Log.e("Error:", "joining thread");
	}

}

// If the Activity starts/restarts
// start our thread.
public void resume() {
	mPlaying = true;
	mGameThread = new Thread(this);
	mGameThread.start();
}

Adding touch controls

One of the last important pieces of our Pong game is handling the user’s touches. To make the controls as easy as possible we will say that holding anywhere on the right will move the Bat right and anywhere on the left will move left.

When the onTouchEvent method is called, we switch based on the type of event. The first case that we handle is MotionEvent.ACTION_DOWN. This occurs when the player touches the screen. We can access the precise location with the motionEvent.getX method. Therefore in the code that follows we use the following if statement:

if(motionEvent.getX() > mScreenX / 2){

}

This detects if the screen has been touched at a position further to the right than the width of the screen divided by two (the right-hand side). If the above is true we simply call Bat.setMovementState(mBat.RIGHT) and the Bat class will take care of moving correctly the next time update is called. If the previous if statement is false then it must have been touched on the left and we call Bat.setMovementState(mBat.LEFT).

We also need to remember to stop the bat if the player removes their finger from the screen. We handle this in the MotionEvent.ACTION_UP case of the switch block.
Add the overridden onTouchEvent method.

// The SurfaceView class implements onTouchListener
// So we can override this method and detect screen touches.
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {

	switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {

		// Player has touched the screen
		case MotionEvent.ACTION_DOWN:

			mPaused = false;

			// Is the touch on the right or left?
			if(motionEvent.getX() > mScreenX / 2){
				mBat.setMovementState(mBat.RIGHT);
			}
			else{
				mBat.setMovementState(mBat.LEFT);
			}

			break;

		// Player has removed finger from screen
		case MotionEvent.ACTION_UP:

			mBat.setMovementState(mBat.STOPPED);
			break;
	}
	return true;
}

If you are wondering why we set mPaused to false in the MotionEvent.ACTION_DOWN case it is because we pause the game when the player runs out of lives. When the player taps the screen this will then have the effect of starting it again.
We are nearly there now. You could actually run the game. 1972 here I come! Before you get you those flared trousers out and start playing there are two more quick things to do.

Adding the sound files to the project

The only assets we need for this project are some sound effects. Using your operating go to the “app/src/main” folder of the project, add a new folder, and name it “assets”. There are four sound files that we need and they are linked below.

beep1
beep2
beep3
loselife

Place these files into the “assets” directory you just made.

Lock the screen orientation and make full-screen

We can achieve this by editing the AndroidManifest.xml file as shown next.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.gamecodeschool.pong" >

    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:screenOrientation="landscape" android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

And we’re ready to play!

patreon