Coding the PongGame class

We will be returning to this class constantly over the course of this project. What we will do in this chapter is get the fundamentals set up ready to add the game objects (bat and ball) as well as collision detection and sound effects over the next two chapters.

To achieve this, first we will add a bunch of member variables, then we will add some code inside the constructor to set the class up when it is instantiated/created by PongActivity.

After this, we will code a startNewGame method that we can call every time we need to start a new game including the first time we start a game after the app is started by the user.

Following on, we get to code the draw method which will reveal the new steps that we need to take to draw to the screen 60 times per second and we will also see some familiar code that uses our old friends Canvas, Paint and drawText.

At this point, we will need to discuss some more theory. Things like how we will time the animations of the bat and ball, how do we lock these timings without interfering with the smooth running of Android. These last two topics, the game loop, and threads will then allow us to add the final code of the chapter and witness our game engine in action- albeit with just a bit of text.

Adding the member variables

Add the variables as shown below after the PongGame declaration but before the constructor. When prompted, click OK to import the necessary extra classes.

// Are we debugging?
private final boolean DEBUGGING = true;

// These objects are needed to do the drawing
private SurfaceHolder mOurHolder;
private Canvas mCanvas;
private Paint mPaint;

// How many frames per second did we get?
private long mFPS;
// The number of milliseconds in a second
private final int MILLIS_IN_SECOND = 1000;

// Holds the resolution of the screen
private int mScreenX;
private int mScreenY;
// How big will the text be?
private int mFontSize;
private int mFontMargin;

// The game objects
private Bat mBat;
private Ball mBall;

// The current score and lives remaining
private int mScore;
private int mLives;

Be sure to study the code and then we can talk about it.

The first thing to notice is that we are using the naming convention of adding m before the member variable names. As we add local variables in the methods this will help distinguish them from each other.

Also, notice that all the variables are declared private. You could happily delete all the private access specifiers and the code will still work but as we have no need to access any of these variables from outside of this class it is sensible to guarantee it can never happen by declaring them private.

The first member variable is DEBUGGING. We have declared this as final because we don't want to change its value during the game. Note that declaring it final does not preclude us from switching its value manually when we wish to switch between debugging and not debugging.

The next three classes we declare instances of will handle the drawing to the screen. Notice the new one we have not seen before.

// These objects are needed to do the drawing
private SurfaceHolder mOurHolder;
private Canvas mCanvas;
private Paint mPaint;

The SurfaceHolder class is required to enable drawing to take place. It literally is the object that holds the drawing surface. We will see the methods it allows us to use to draw to the screen when we code the draw method in a minute.

The next two variables give us a bit of insight into what we will need to achieve our smooth and consistent animation. Here they are again.

// How many frames per second did we get?
private long mFPS;
// The number of milliseconds in a second
private final int MILLIS_IN_SECOND = 1000;

Both are of type long because they will be holding a huge number. Computers measure time in milliseconds since 1970. More on that when we talk about the game loop but for now we need to know that by monitoring and measuring the speed of each frame of animation is how we will make sure that the bat and ball move exactly as they should.

The first mFPS will be reinitialized every frame of animation around 60 times per second. It will be passed into each of the game objects (every frame of animation) so that they calculate how much time has elapsed and can then derive how far to move.

The MILLIS_IN_SECOND variable is initialized to 1000. There are indeed 1000 milliseconds in a second. We will use this variable in calculations as it will make our code clearer than if we used the literal value 1000. It is declared final because the number of milliseconds in a second will obviously never change.

The next piece of the code we just added is shown again next for convenience.

// Holds the resolution of the screen
private int mScreenX;
private int mScreenY;
// How big will the text be?
private int mFontSize;
private int mFontMargin;

The variables mScreenX and mScreenY will hold the horizontal and vertical resolution of the screen. Remember that they are being passed in from PongActivity into the constructor.

The next two, mFontSize and mMarginSize will be initialized based on the screen size in pixels, to hold a value in pixels to make formatting of our text neat and more concise than constantly doing calculations for each bit of text.

Notice we have also declared an instance of Bat and Ball (mBat and mBall) but we won't do anything with them just yet.

Note

It is OK to declare an object but if you try and use it before initialization you will get a NULL POINTER EXCEPTION and the game will crash. I have added these here now because it is harmless in this situation and it saves revisiting different parts of the code so many times

The final two lines of code declare two member variables mScore and mLives which will hold the player's score and how many chances they have left.

Just to be clear before we move on, these are the import statements you should currently have at the top of the PongGame.java code file.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

Now we can begin to initialize some of these variables in the constructor.

Coding the PongGame constructor

Add the highlighted code to the constructor. Be sure to study the code as well and then we can discuss it.

public PongGame(Context context, int x, int y) {
   // Super... calls the parent class
   // constructor of SurfaceView
   // provided by Android
   super(context);

   // Initialize these two members/fields
   // With the values passed in as parameters
   mScreenX = x;
   mScreenY = y;

   // Font is 5% (1/20th) of screen width
   mFontSize = mScreenX / 20;
   // Margin is 1.5% (1/75th) of screen width
   mFontMargin = mScreenX / 75;

   // Initialize the objects
   // ready for drawing with
   // getHolder is a method of SurfaceView
   mOurHolder = getHolder();
   mPaint = new Paint();

   // Initialize the bat and ball
   
   // Everything is ready so start the game
   startNewGame();
}

The code we just added to the constructor begins by using the values passed in as parameters (x and y) to initialize mScreenX and mScreenY. Our entire PongGame class now has access to the screen resolution whenever it needs it. Here are the two lines again:

// Initialize these two members/fields
// With the values passed in as parameters
mScreenX = x;
mScreenY = y;

Next, we initialize mFontSize and mFontMargin as a fraction of the screen width in pixels. These values are a bit arbitrary, but they work, and we will use various multiples of these variables to align text on the screen neatly. Here are the two lines of code I am referring to:

   // Font is 5% (1/20th) of screen width
   mFontSize = mScreenX / 20;
   // Margin is 1.5% (1/75th) of screen width
   mFontMargin = mScreenX / 75;

Moving on, we initialize our Paint and SurfaceHolder objects. Paint uses the default constructor as we have done previously but mHolder uses the getHolder method which is a method of the SurfaceHolder class. The getHolder method returns a reference which is initialized to mHolder so mHolder is now that reference. In short, mHolder is now ready to be used. We have access to this handy method because, PongGame is a SurfaceView.

   // Initialize the objects
   // ready for drawing with
   // getHolder is a method of SurfaceView
   mOurHolder = getHolder();
   mPaint = new Paint();

We will need to do more preparation in the draw method before we can use our Paint and Canvas classes as we have done before. We will see exactly what very soon.

Finally, we call startNewGame.

   // Initialize the bat and ball
   
   // Everything is ready to start the game
   startNewGame();

Notice the startNewGame method call is underlined in red as an error because we haven't written it yet. Let's do that next. Also, notice the comment indicating where we will eventually get around to initialize the bat and the ball objects.

Coding the startNewGame method

Add the method's code immediately after the constructor's closing curly brace but before the PongGame class closing curly brace.

// The player has just lost
// or is starting their first game
private void startNewGame(){

   // Put the ball back to the starting position
   

   // Reset the score and the player's chances
   mScore = 0;
   mLives = 3;
}

This simple method sets the score back to zero and the player's lives back to three. Just what we need to start a new game.

Note the comment stating // Put the ball back to starting position. This identifies that once we have a ball we will reset its position at the start of each game from this method.

Let's get ready to draw.

Coding the draw method

Add the draw method shown next immediately after the startNewGame method. There will be a couple of errors in the code. We will deal with them, then we will go into detail about how the draw method will work in relation to SurfaceView because there are some completely alien-looking lines of code in there as well as some familiar ones.

// Draw the game objects and the HUD
private void draw() {

   // Draw the game objects and the HUD
    void draw() {
        if (mOurHolder.getSurface().isValid()) {
            // Lock the canvas (graphics memory) ready to draw
            mCanvas = mOurHolder.lockCanvas();

            // Fill the screen with a solid color
            mCanvas.drawColor(Color.argb
                    (255, 26, 128, 182));

            // Choose a color to paint with
            mPaint.setColor(Color.argb
                    (255, 255, 255, 255));

            // Draw the bat and ball

            // Choose the font size
            mPaint.setTextSize(mFontSize);

            // Draw the HUD
            mCanvas.drawText("Score: " + mScore +
                            "   Lives: " + mLives,
                    mFontMargin , mFontSize, mPaint);

            if(DEBUGGING){
                printDebuggingText();
            }
            // Display the drawing on screen
            // unlockCanvasAndPost is a method of SurfaceView
            mOurHolder.unlockCanvasAndPost(mCanvas);
        }

    }

We have two errors. One is that the Color class needs importing. You can fix this in the usual way as described in the previous tip or add the next line of code manually.

Whichever method you choose, the following extra line needs to be added to the code at the top of the file:

import android.graphics.Color;

Let's deal with the other error.

Adding the printDebuggingText method

The second error is the call to printDebuggingText. The method doesn't exist yet. Let's add that now.

Add the code after the draw method…

private void printDebuggingText(){
   int debugSize = mFontSize / 2;
   int debugStart = 150;
   mPaint.setTextSize(debugSize);
   mCanvas.drawText("FPS: " + mFPS ,
      10, debugStart + debugSize, mPaint);

}

The previous code uses the local variable debugSize to hold a value that is half that of the member variable mFontSize. This means that as mFontSize (which is used for the HUD) is initialized dynamically based on the screen resolution, debugSize will always be half that. The debugSize variable is then used to set the size of the font before we start drawing the text. The debugStart variable is just a guess at a good position vertically to start printing the debugging text.

These two values are then used to position a line of text on the screen that shows the current frames per second. As this method is called from draw, which in turn will be called from the main game loop, this line of text will be constantly refreshed up to sixty times per second.

Note

It is possible that on very high or very low-resolution screens you might need to change this value to something more appropriate for your screen. When we learn about the concept of viewports in the final project we will solve this ambiguity once and for all. This game is focussed on our first practical use of classes.

Let's explore those new lines of code in the draw method and exactly how we can use SurfaceView from which our PongGame class is derived, to handle all our drawing requirements.

Understanding the draw method and the SurfaceView class

Starting in the middle of the method and working outwards for a change, we have a few familiar things like the calls to drawColor, setTextSize, and drawText. We can also see the comment which indicates where we will eventually add code to draw the bat and the ball. These familiar method calls do the same thing they did in the previous project.

  • The drawColor code clears the screen with a solid color
  • The setTextSize method sets the size of the text for drawing the HUD
  • And drawText draws the text that will show the score and the number of lives the player has remaining

What is totally new however, is the code at the very start of the draw method. Here it is again.

if (mOurHolder.getSurface().isValid()) {
   // Lock the canvas (graphics memory) ready to draw
   mCanvas = mOurHolder.lockCanvas(); 
…
…

The if statement contains a call to getSurface and chains it with a call to isValid. If this line returns true it confirms that the area of memory which we want to manipulate to represent our frame of drawing is available, the code continues inside the if statement.

What goes on inside those methods (especially the first) is quite complex. They are necessary because all of our drawing and other processing (like moving the objects) will take place asynchronously with the code that detects the player input and listens for the operating system for messages. This wasn't an issue in the previous project because our code just sat there waiting for input, drew a single frame and then sat there waiting again.

Now we want to execute the code 60 times a second, we are going to need to confirm that we have access to the memory- before we access it.

This raises more questions about how does this code run asynchronously? That will be answered when we discuss threads shortly. For now, just know that the line of code checks if some other part of our code or Android itself is currently using the required portion of memory. If it is free, then the code inside the if statement executes.

Furthermore, the first line of code to execute inside the if statement calls lockCanvas so that if another part of the code tries to access the memory while our code is accesing it, it won't be able to.

Then we do all our drawing.

Finally, in the draw method, there is this next line (plus comments) right at the end.

// Display the drawing on screen
// unlockCanvasAndPost is a method of SurfaceHolder
mOurHolder.unlockCanvasAndPost(mCanvas);

The unlockCanvasAndPost method sends our newly decorated Canvas object (mCanvas) for drawing to the screen and releases the lock so that other areas of code can use it again- albeit very briefly before the whole process starts again. This process happens every single frame of animation.

We now understand the code in the draw method, however, we still don't have the mechanism which calls the draw method over and over. In fact, we don't even call the draw method once. We need to talk about the game loop and threads.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.1.195