Figure 6.20 defines a subclass of Thread
which updates the game. The thread maintains a reference to the SurfaceView
’s SurfaceHolder
(line 538) and a boolean
indicating whether the thread is running. The class’s run
method (lines 556–587) drives the frame-by-frame animations—this is known as the game loop. Each update of the game elements on the screen is performed based on the number of milliseconds that have passed since the last update. Line 559 gets the system’s current time in milliseconds when the thread begins running. Lines 561–586 loop until threadIsRunning
is false
.
535 // Thread subclass to control the game loop
536 private class CannonThread extends Thread
537 {
538 private SurfaceHolder surfaceHolder; // for manipulating canvas
539 private boolean threadIsRunning = true; // running by default
540
541 // initializes the surface holder
542 public CannonThread(SurfaceHolder holder)
543 {
544 surfaceHolder = holder;
545 setName("CannonThread");
546 }
547
548 // changes running state
549 public void setRunning(boolean running)
550 {
551 threadIsRunning = running;
552 }
553
554 // controls the game loop
555 @Override
556 public void run()
557 {
558 Canvas canvas = null; // used for drawing
559 long previousFrameTime = System.currentTimeMillis();
560
561 while (threadIsRunning)
562 {
563 try
564 {
565 // get Canvas for exclusive drawing from this thread
566 canvas = surfaceHolder.lockCanvas(null);
567
568 // lock the surfaceHolder for drawing
569 synchronized(surfaceHolder)
570 {
571 long currentTime = System.currentTimeMillis();
572 double elapsedTimeMS = currentTime - previousFrameTime;
573 totalElapsedTime += elapsedTimeMS / 1000.0 ;
574 updatePositions(elapsedTimeMS); // update game state
575 drawGameElements(canvas); // draw using the canvas
576 previousFrameTime = currentTime; // update previous time
577 }
578 }
579 finally
580 {
581 // display canvas's contents on the CannonView
582 // and enable other threads to use the Canvas
583 if (canvas != null)
584 surfaceHolder.unlockCanvasAndPost(canvas);
585 }
586 } // end while
587 } // end method run
588 } // end nested class CannonThread
589 } // end class CannonView
First we obtain the Canvas
for drawing on the SurfaceView
by calling SurfaceHolder
method lockCanvas (line 566). Only one thread at a time can draw to a SurfaceView
. To ensure this, you must first lock the SurfaceHolder
by specifying it as the expression in the parentheses of a synchronized
block (line 569). Next, we get the current time in milliseconds, then calculate the elapsed time and add that to the total time so far—this will be used to help display the amount of time left in the game. Line 574 calls method updatePositions
to move all the game elements, passing the elapsed time in milliseconds as an argument. This ensures that the game operates at the same speed regardless of how fast the device is. If the time between frames is larger (i.e, the device is slower), the game elements will move further when each frame of the animation is displayed. If the time between frames is smaller (i.e, the device is faster), the game elements will move less when each frame of the animation is displayed. Finally, line 575 draws the game elements using the SurfaceView
’s Canvas
and line 576 stores the currentTime
as the previousFrameTime
to prepare to calculate the elapsed time between this animation frame and the next.