- Android Game Programming by Example
- John Horton
- 1582字
- 2021-07-16 13:50:01
Coding the game loop
We said that we will not be using a UI layout for our game screen, but instead a dynamically drawn view. This is where the view of our pattern comes in. Let's create a new class to represent our view, then we will put in the fundamental building blocks of our Tappy Defender game.
Building the view
We will leave our two activity classes alone for a while so that we can take a look at our class that will represent the view of our game. As we discussed at the start of this chapter, the view and the controller aspects will be part of the same class.
The Android API provides us with an ideal class for our requirements. The android.view.SurfaceView
class not only provides us a view that is designed for drawing pixels, text, lines, and sprites onto, but also enables us to quickly handle player input as well.
As if this wasn't useful enough, we can also spawn a thread by implementing the runnable interface allowing our main game loop to get player input and other system essentials at the same time. We will deal with the general structure of your new SurfaceView
implementation now, so we can fill in the details as we progress with the project.
Creating a new class for the view
Without further delay, we can create a new class which extends SurfaceView
.
- Right-click the folder containing our
.java
files and select New | Java Class then click on OK. - In the Create New Class dialog, name the new class
TDView,
(Tappy Defender view). Now, click on OK to have Android Studio autogenerate the class. - The new class will open in the code editor. Amend the code to have it extend
SurfaceView
and implementRunnable
as discussed in the previous section. Edit the highlighted parts of the code that follows:package com.gamecodeschool.c1tappydefender;
import android.view.SurfaceView;
public class TDView extends SurfaceView implements Runnable{
} - Use the Alt | Enter combination to import the missing classes.
- Note that we still have an error in our code. This is because we must provide a constructor for our
SurfaceView
implementation. Right-click just below theTDView
class declaration and navigate to Generate | Constructor | SurfaceView(Context:context). Or you can just type this in as shown in the next block of code. Now click on OK.
What we did
We now have a new class called TDView
, which extends SurfaceView
for our drawing requirements and implements Runnable
for our threading needs. We have also generated a constructor, which we will use soon to initialize our new class.
The Context
parameter that is passed into our constructor is a reference to the current state of our application within the Android system that is held by our GameActivity
class. This Context
parameter is useful/essential for a number of things that we will be implementing throughout this project.
So far, our TDView
class will look like this:
package com.gamecodeschool.c1tappydefender; import android.content.Context; import android.view.SurfaceView; public class TDView extends SurfaceView implements Runnable{ public TDView(Context context) { super(context); } }
Structuring the class code
Now that we have our TDView
class extended from the SurfaceView
class, we can start coding it. To control the game, we need to be able to update all the game data/objects. This implies an update
method. In addition, we are obviously going to want to draw all our game data once every frame after they have been updated. Let's keep all of our drawing code together in a method called draw
. Furthermore, we need to control the frequency with which this happens. Therefore, a control
method seems like it should be part of the class as well.
We also know that everything needs to happen in your thread; so to achieve this, we should wrap the code in the run
method. Lastly, we need a way to control when the thread should and shouldn't do its work so we need an infinite loop controlled by a Boolean, perhaps, playing
.
Copy the following code into the body of our TDView
class to implement what we just discussed:
@Override public void run() { while (playing) { update(); draw(); control(); } }
This is the bare-bones of our game. The run
method will execute in a thread, but it will only execute the game loop while the Boolean playing
instance is true. Then, it will update all the game data, draw the screen based on that game data, and control how long it is until the run
method is called again.
Now, we can quickly build on this code. First of all, we can implement the three methods that we call from the run
method. Type the following code in the body of our TDView
class before the closing curly brace of the run
method:
private void update(){ } private void draw(){ } private void control(){ }
We now need to declare our playing member variable. We can do this using the volatile
keyword as it will be accessed from outside the thread and from within. Type this code just after the TDView
class declaration:
volatile boolean playing;
Now, we know that we can control the execution of code within the run method with the infinite loop and the playing
variable. We also need to start and stop the actual thread itself. Not just when we decide, but when the player unexpectedly quits the game. What if he gets a phone call or just taps the home button on his device.
To handle these events, we need the TDView
class and GameActivity
to work together. Now, in the TDView
class, we can implement a pause
method and a resume
method. Within them, we put the code to stop and start our thread. Implement these two methods within the body of the TDView
class:
// Clean up our thread if the game is interrupted or the player quits public void pause() { playing = false; try { gameThread.join(); } catch (InterruptedException e) { } } // Make a new thread and start it // Execution moves to our R public void resume() { playing = true; gameThread = new Thread(this); gameThread.start(); }
Now, we need an instance of a Thread
class called gameThread
. We can declare it as a member variable of TDView
just after the class declaration, right after our Boolean playing
parameter. Like this:
volatile boolean playing;
Thread gameThread = null;
Note that the onPause
and onResume
methods are public. We can now add code to our GameActivity
class to call these methods at the appropriate time. Remember that GameActivity
extends Activity
. Therefore, use the overridden Activity
lifecycle methods.
By overriding the onPause
method, whenever the activity is paused, we can shut down the thread. This avoids potentially embarrassing the player and having to explain to his caller, why they can hear sound FX in the background.
By overriding onResume()
, we can have our thread start up in the last phase of the Android lifecycle before the app is actually running.
Note
Note the distinction between the pause
and resume
methods of the TDView
class and the overridden onPause
and onResume
methods of the GameActivity
class.
The game activity
Before you implement/override this method, note that all they will do is call the parent version of their respective methods followed by the public methods in the TDView
class to which they correspond.
You might remember back to the section when we created our new GameActivity
class, we deleted the entire code contents? With that in mind, here is the outline of the code we will need in GameActivity.java
including the implementation of the overridden methods within the body of the GameActivity
class that we discussed in the previous section. Type this code in GameActivity.java
:
package com.gamecodeschool.c1tappydefender; import android.app.Activity; import android.os.Bundle; public class GameActivity extends Activity { // This is where the "Play" button from HomeActivity sends us @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } // If the Activity is paused make sure to pause our thread @Override protected void onPause() { super.onPause(); gameView.pause(); } // If the Activity is resumed make sure to resume our thread @Override protected void onResume() { super.onResume(); gameView.resume(); } }
Finally, let's go ahead and declare an object of the TDView
class. Do this just after the GameActivity
class declaration:
// Our object to handle the View private TDView gameView;
Now, within the onCreate
method, we need to instantiate your object, keeping in mind that your constructor in TDView.java
takes a Context
object as an argument. Then, we use the newly instantiated object in a call to setContentView()
. Remember when we built our home screen, we called setContentView()
and passed in our UI design. This time, we are setting the player's view to be the object of our TDView
class. Copy the following code into the onCreate
method of the GameActivity
class:
// Create an instance of our Tappy Defender View (TDView) // Also passing in "this" which is the Context of our app gameView = new TDView(this); // Make our gameView the view for the Activity setContentView(gameView);
At this point, we can actually run our game and click on the Play button to proceed to the GameView
activity, which will use TDView
as its view and start our thread. Obviously, there is nothing to see yet, so let's work on the model of our design pattern and build the basic outline of our first game object. At the end of the chapter, we will see how to run the game on an Android device.