Implementing the game loop with a thread

Now that we have learned about the game loop and threads, we can put it all together to implement our game loop in the Living Drawing project.

We will add the entire code for the game loop, including writing two methods in the LiveDrawingActivity class, to start and stop the thread that will control the loop.

Tip

Can you guess how the Activity-based class will start and stop the thread in the LiveDrawingView class?

Implementing Runnable and providing the run method

Update the class declaration by implementing Runnable, just like we discussed previously, and as shown in the following highlighted code:

class LiveDrawingView extends SurfaceView implements Runnable{

Notice that we have a new error in the code. Hover the mouse pointer over the word Runnable and you will see a message informing you that we need to implement the run method, again, just as we discussed during the discussion on interfaces and threads in the previous section. Add the empty run method, including the @override label, as demonstrated shortly.

It doesn't matter where you add it, provided it is within the LiveDrawingView class's curly braces and not inside another method. I added mine right after the constructor method because it is near the top and easy to get to. We will be editing this quite a bit in this chapter. Add the empty run method, as shown here:

// When we start the thread with:
// mThread.start();
// the run method is continuously called by Android
// because we implemented the Runnable interface
// Calling mThread.join();
// will stop the thread
@Override
public void run() {
}

The error is gone and now we can declare and initialize a Thread object.

Coding the thread

Declare some variables and instances, shown as follows, underneath all our other members in the LiveDrawingView class:

// Here is the Thread and two control variables
private Thread mThread = null;
// This volatile variable can be accessed
// from inside and outside the thread
private volatile boolean mDrawing;
private boolean mPaused = true;

Now, we can start and stop the thread. Have a think about where we might do this. Remember that the app needs to respond to the operating system starting and stopping the app.

Starting and stopping the thread

Now, we need to start and stop the thread. We have seen the code we need, but when and where should we do it? Let's write two methods, one to start and one to stop, and then we can consider further when and where to call these methods from. Add these two methods inside the LiveDrawingView class. If their names sound familiar, it is not by chance:

// This method is called by LiveDrawingActivity
// when the user quits the app
public void pause() {

   // Set mDrawing to false
   // Stopping the thread isn't
   // always instant
   mDrawing = false;
   try {
         // Stop the thread
         mThread.join();
   } catch (InterruptedException e) {
         Log.e("Error:", "joining thread");
   }

}


// This method is called by LiveDrawingActivity
// when the player starts the app
public void resume() {
   mDrawing = true;
   // Initialize the instance of Thread
   mThread = new Thread(this);

   // Start the thread
   mThread.start();
}

What is happening is slightly given away by the comments. You did read the comments, right? We now have a pause and resume method that stop and start the Thread object using the same code we discussed previously.

Notice that the new methods are public and therefore accessible from outside the class to any other class that has an instance of LiveDrawingView.

Remember that LiveDrawingActivity has the fully declared and initialized instance of LiveDrawingView?

Let's use the Android Activity life cycle to call these two new methods.

Using the Activity lifecycle to start and stop the thread

Update the overridden onResume and onPause methods in LiveDrawingActivity, as shown in the highlighted lines of code:

@Override
protected void onResume() {
   super.onResume();

   // More code here later in the chapter
   mLiveDrawingView.resume();
}

@Override
protected void onPause() {
   super.onPause();

   // More code here later in the chapter
   mLiveDrawingView.pause();
}

Now, our thread will be started and stopped when the operating system is resuming and pausing our app. Remember that onResume is called after onCreate the first time an app is created, not just after resuming from a pause. The code inside onResume and onPause uses the mLiveDrawingView object to call its resume and pause methods, which, in turn, has the code to start and stop the thread. This code then triggers the thread's run method to execute. It is in this run method (in LiveDrawingView) that we will code our game loop. Let's do that now.

Coding the run method

Although our thread is set up and ready to go, nothing happens because the run method is empty. Code the run method, as shown in the following code:

@Override
public void run() {
   // mDrawing gives us finer control
   // rather than just relying on the calls to run
   // mDrawing must be true AND
   // the thread running for the main 
   // loop to execute
   while (mDrawing) {

         // What time is it now at the start of the loop?
         long frameStartTime = System.currentTimeMillis();

         // Provided the app isn't paused
         // call the update method
         if(!mPaused){
                update();
                // Now the particles are in 
                // their new positions
                
         }

         // The movement has been handled and now 
         // we can draw the scene.
         draw();

         // How long did this frame/loop take?
         // Store the answer in timeThisFrame
         long timeThisFrame = 
                System.currentTimeMillis() - frameStartTime;

         // Make sure timeThisFrame is at least 1 millisecond
         // because accidentally dividing 
         // by zero crashes the app
         if (timeThisFrame > 0) {
                // Store the current frame rate in mFPS
                // ready to pass to the update methods of
                // of our particles in the next frame/loop
                mFPS = MILLIS_IN_SECOND / timeThisFrame;
         }
   }
}

Notice that there are two errors in Android Studio. This is because we have not written the update method yet. Let's quickly add an empty method (with a comment) for it. I added mine after the run method:

private void update() {
   // Update the particles

}

Now, let's discuss in detail how the code in the run method achieves the aims of our game loop by looking at the whole thing a bit at a time.

This first part initiates a while loop with the mDrawing condition and wraps the rest of the code inside run so that the thread will need to be started (for run to be called) and mDrawing will need to be true for the while loop to execute:

@Override
public void run() {
   // mPlaying gives us finer control
   // rather than just relying on the calls to run
   // mPlaying must be true AND
   // the thread running for the main 
   // loop to execute
   while (mPlaying) {

The first line of code inside the while loop declares and initializes a local variable, frameStartTime, with whatever the current time is. The static method, currentTimeMillis, of the System class returns this value. If we want to measure how long a frame has taken later on, then we need to know what time it started:

         // What time is it now at the start of the loop?
         long frameStartTime = System.currentTimeMillis();

Next, still inside the while loop, we need to check whether the app is paused, and only if the app is not paused does the following code get executed. If the logic allows execution inside this block, then update is called:

         // Provided the app isn't paused
         // call the update method
         if(!mPaused){
                update();
                // Now the particles are in 
                // their new positions

         }

Outside of the previous if statement, the draw method is called to draw all the objects in the just-updated positions. At this point, another local variable is declared and initialized with the length of time it took to complete the entire frame (updating and drawing). This value is calculated by getting the current time, once again with currentTimeMillis, and subtracting frameStartTime from it, as follows:

         // The movement has been handled and collisions
         // detected now we can draw the scene.
         draw();

         // How long did this frame/loop take?
         // Store the answer in timeThisFrame
         long timeThisFrame = 
                System.currentTimeMillis() - frameStartTime;

The next if statement detects whether timeThisFrame is greater than zero. It is possible for the value to be zero if the thread runs before objects are initialized. If you look at the code inside the if statement, it calculates the frame rate by dividing the elapsed time by MILLIS_IN_SECOND. If you divide by zero, the app will crash, which is why we do the check.

Once mFPS gets the value assigned to it, we can use it in the next frame to pass to the update method all the particles that we will code in the next chapter. They will use the value to make sure that they move by precisely the correct amount based on their target speed and the length of time the frame has taken:

         // Make sure timeThisFrame is at least 1 millisecond
         // because accidentally dividing 
         // by zero crashes the app
         if (timeThisFrame > 0) {
                // Store the current frame rate in mFPS
                // ready to pass to the update methods of
                // the particles in the next frame/loop
                mFPS = MILLIS_IN_SECOND / timeThisFrame;
         }
   }
}

The result of the calculation that initializes mFPS each frame is that mFPS will hold a fraction of 1. As the frame rate fluctuates, mFPS will hold a different value and supply the game objects with the appropriate number to calculate each move.

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

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