Chapter 10. Timing and the Game Loop

You have learned how to use the Graphics2D class to program graphics using vector shapes and bitmap images, and you have even seen a nearly complete game written from scratch. You have learned how to load and play sound files and MIDI music files, and how to program the keyboard and mouse. By all accounts, you have the tools to create many different games already. But there are some tricks of the trade—secrets of the craft—that will help you to make your games stand out in the crowd and impress. This chapter discusses the game loop and its vital importance to a smooth-running game. You will learn about threads and timing, and you will take the Asteroids-style game created in Chapters 3 and 5 into completely new territory, as it is modified extensively in the following pages.

Here are the specific topics you will learn about:

  • Using timing methods

  • Starting and stopping a thread

  • Using a thread for the game loop

The Potency of a Game Loop

The key to creating a game loop to facilitate the needs of a high-speed game is Java’s multithreading capability. Threads are such an integral part of Java that it makes a special thread available to your program just for this purpose. This special thread is called Runnable, an interface class. However, it’s entirely possible to write a Java game without threads by just using a simple game loop. I’ll show you how to do this first, and then we’ll take a look at threads as an even better form of game loop.

Tip

An interface class is an abstract class with properties and methods that are defined but not implemented. A program that uses an interface class is said to consume it, and must implement all of the public methods in the interface. Typical examples include KeyListener and Runnable.

A Simple Loop

The Runnable interface gives your program its own awareness. I realize this concept sounds a lot like artificial intelligence, but the term awareness is a good description of the Runnable interface. Before Runnable, your Java programs have been somewhat naive, capable of only processing during the screen refresh. Let’s take a look at an example. Figure 10.1 shows the SimpleLoop program. As you can see, this program doesn’t do anything in real time; it waits until you press a key or click the mouse before drawing a rectangle.

The SimpleLoop program.

Figure 10.1. The SimpleLoop program.

// SimpleLoop program
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 import java.util.*;

public class SimpleLoop extends JFrame
        implements KeyListener, MouseListener {
    Random rand = new Random();

public static void main(String[] args) {
    new SimpleLoop();
}
public SimpleLoop() {
    super("SimpleLoop");
    setSize(500,400);
    setVisible(true);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    addKeyListener(this);
    addMouseListener(this);
}

public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;

    //create a random rectangle
    int w = rand.nextInt(100);
    int h = rand.nextInt(100);
    int x = rand.nextInt(getSize().width - w);
    int y = rand.nextInt(getSize().height - h);
    Rectangle rect = new Rectangle(x,y,w,h);

    //generate a random color
    int red = rand.nextInt(256);
    int green = rand.nextInt(256);
    int blue = rand.nextInt(256);
    g2d.setColor(new Color(red,green,blue));

       //draw the rectangle
       g2d.fill(rect);
    }   

    //handle keyboard events
    public void keyReleased(KeyEvent k) { }
    public void keyTyped(KeyEvent k) { }
    public void keyPressed(KeyEvent k) {
        repaint();
    }

    //handle mouse events
    public void mouseEntered(MouseEvent m) { }
    public void mouseExited(MouseEvent m) { }
    public void mouseReleased(MouseEvent m) { }
    public void mouseClicked(MouseEvent m) { }
    public void mousePressed(MouseEvent m) {
        repaint();
    }
}   

Tip

The Random class is located in the java.awt.util class along with many other utility classes that provide commonly needed tools to your program. To use Random in your program, you must include this class with the following import statement:

import java.awt.util.*;

This program has no loop whatsoever, so it cannot process anything in real time—not spaceships, asteroids, jumping Italian plumbers, yellow dot-eaters, or female spelunkers packing dual Berettas. The only thing this program can do is draw a single rectangle at a time.

There are some ways you can make the program a little more interactive. The only problem with the mouse and keyboard listener interfaces is that you have to implement all of them or none of them. That reminds me of Yoda’s famous saying, “Do, or do not. There is no ‘try.’” An interface class is an all-or-nothing proposition that tends to junk up your source code, not to mention that it takes a lot of work to type in all of those interface event methods every time! But there’s no real workaround for the unused event methods.

Note

I’ve been thinking about a way to use all of these interface classes (such as Runnable and the input listeners) by tucking them away into a class outside of the main program. This support class would provide my main program with real events when things happen, such as key presses, mouse movement, and other events. Perhaps this is the birth of an idea that will become some sort of game engine? Let’s wait and see what Part III has in store.

Overriding Some Default Behaviors

There is a serious problem with this program because it was supposed to just add a new rectangle every time the user presses a key or mouse button, not redraw the entire window—with a single rectangle left over. There is definitely something odd going on because this program should have worked as expected.

Well, it turns out that Java has been screwing with the screen without permission—or rather, by default. The JFrame class, which is the basis for the SimpleLoop program, provides many default event methods that do certain things for you. You don’t even need to implement paint() if you don’t want to, and the JFrame base class will provide it for your program. Granted, nothing will be drawn on the window as a result, but the compiler won’t give you an error. This differs from an interface class (such as KeyListener) that mandates that you must implement all of its abstract methods. So it’s pretty obvious by this difference in functionality that JFrame is not an interface class, but a fully functioning class.

What happens, then, when you implement a JFrame class method such as paint()? These methods are essentially empty inside the JFrame class. Oh, they exist and are not abstract, but they don’t do anything. The JFrame class defines these methods in such a way that you can override them in your program. For instance, in the SimpleLoop program, SimpleLoop is actually the name of the class, and it inherits from JFrame. Therefore, SimpleLoop has the opportunity to override any of the methods in JFrame that it wants to, including paint().

Feeling Loopy

Now you finally have an opportunity to add a real loop to this program. But just for kicks, what do you think would happen if you added a while() loop to the constructor? I tried it, so you should try it too. Doing this will cause the window to quickly fill up with rectangles! The only problem with this kind of loop is that none of our program’s other events will come up because we’ve trapped the program inside the while loop. The call to repaint the window is only the last step in a game. First, you move your game objects around on the screen, perform collision testing, and so forth. You can perform all of these steps in the paint() event, but that limits the program to a very low frame rate. The following is a suggestion on what we might try to do (note: this is not actual code that you should add to the SimpleLoop program—it’s just an exploration of an idea).

//the game loop
while (true) {
    //move the game objects
    updateObjects();
    //perform collision testing
    testCollisions();
    //redraw the window
    repaint();
}

This would work, but we’re still locking out processes that communicate with our program through events such as the keyboard and mouse handlers.

Stepping Up to Threads

We use the Runnable interface class to add threading support to a game. This interface has a single event method called run() that represents the thread’s functionality. We can create this run() method in a game, and that will act like the game loop. The thread just calls run() once, so you must add a while loop to this method. The while loop will be running inside a thread that is separate from the main program. By implementing Runnable, a game becomes multithreaded. In that sense, it will support multiple processors or processor cores! For instance, if you have an Intel Core2 Duo or another multi-core processor, you should be able to see the applet running in a thread that is separate from the web browser or applet viewer program.

Starting and Stopping the Thread

To get a thread running in an applet, you have to create an instance of the Thread class, and then start it running using the applet’s start() event. First, let’s create the thread object:

  Thread thread;
  Next, create the thread in the constructor method:

  thread = new Thread(this);
  thread.start();

Then you can write the code for the thread’s loop in the run() event method (part of Runnable). We’ll take a look at what goes inside run() in a moment.

The ThreadedLoop Program

Let’s take a look at the entire ThreadedLoop project, and then I’ll explain the loop for the thread inside the run() method. The key portions of code have been highlighted in bold.

  // ThreadedLoop program
 import java.awt.*;
 import java.lang.*;
 import javax.swing.*;
 import java.util.*;

 public class ThreadedLoop extends JFrame implements Runnable {
     Random rand = new Random();

     //the main thread
     Thread thread;

     //count the number of rectangles drawn
     long count = 0, total = 0;

     public static void main(String[] args) {
         new ThreadedLoop();
}
     public ThreadedLoop() {
     super("ThreadedLoop");
     setSize(500,400);
     setVisible(true);
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    thread = new Thread(this);
    thread.start();
}
//thread run event
public void run() {
    long start = System.currentTimeMillis();

   //acquire the current thread
   Thread current = Thread.currentThread();

   //here's the new game loop
   while (current == thread) {
       try { Thread.sleep(0); }
   catch(InterruptedException e) { e.printStackTrace(); }

    //draw something
    repaint();

   //figure out how fast it's running
   if (start + 1000 < System.currentTimeMillis ()){
       start = System.currentTimeMillis();
       total = count;
       count = 0;

    }

    }
}   

//JFrame paint event
public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;

    //create a random rectangle
    int w = rand.nextInt(100);
    int h = rand.nextInt(100);
    int x = rand.nextInt(getSize().width - w);
    int y = rand.nextInt(getSize().height - h);
    Rectangle rect = new Rectangle(x,y,w,h);

    //generate a random color
    int red = rand.nextInt(256);
    int green = rand.nextInt(256);
    int blue = rand.nextInt(256);
    g2d.setColor(new Color(red,green,blue));

    //draw the rectangle
    g2d.fill(rect);

    //add another to the counter
    count++;

    g2d.setColor(Color.WHITE);
    g2d.fill(new Rectangle(0,360,500,400));
    g2d.setColor(Color.BLACK);
    g2d.drawString("Rectangles per second: " + total, 10, 380);
   }

}   

Now let’s examine this run() event that is called by the Runnable interface. There’s a lot going on in this method. First, a variable called start gets the current time in milliseconds (System.currentTimeMillis()). This value is used to pause once per second to print out the total number of rectangles that have been drawn (see Figure 10.2).

Next, a local variable is set to the current thread, and then a while loop is created.

The ThreadedLoop program displays the number of rectangles drawn per second.

Figure 10.2. The ThreadedLoop program displays the number of rectangles drawn per second.

Thread current = Thread.currentThread();

This local thread makes sure that our loop only processes thread events intended for the game loop because you can use multiple threads in a program.

while (current == thread)

The core of the thread loop includes a call to Thread.sleep(0), which is a placeholder for slowing the game down to a consistent frame rate. Right now it’s running as fast as possible because the sleep() method is being passed a 0. This single method call requires an error handler because it throws an Interrupted Exception if the thread is ever interrupted by another thread. This is an advanced subject that doesn’t concern us. If the thread is interrupted, it’s not a big deal—we’ll just ignore any such error that might crop up.

try {
    Thread.sleep(0);
}
catch(InterruptedException e) {
   e.printStackTrace();
}

After this call to sleep (which will slow the game to a consistent frame rate), then we can call game-related methods to update objects on the screen, perform collision testing, perform game logic to interact with enemies, and so on. In the block of code that follows, you can see some timing code inside an if statement. This code prints out the number of rectangles that have been drawn by the paint() event during the past 1,000 milliseconds (which equals 1 second).

//draw something
repaint();

//figure out how fast it's running
if (start + 1000 < System.currentTimeMillis()) {
    start = System.currentTimeMillis();
    count = 0;
}

The single call to repaint() actually makes this program do something; all of the rest of the code helps this method call to do its job effectively. The run() event contains the new threaded game loop.

Examining Multithreading

Aside from the sample game in Chapter 3, this program might have been your first exposure to multithreaded programming. Java makes the process very easy compared to other languages. I’ve used several threading libraries such as Posix threads, Boost threads, and Windows threads to add thread support in my C++ programs in Linux and Windows, and it’s not very easy to use at all compared to Java’s built-in support for threads. We will continue to use threads in every subsequent chapter, so you will have had a lot of exposure to this subject by the time you complete the book.

Note

Multi-Threaded Game Engine Design (Course Technology, 2010) covers thread libraries extensively with the C++ language while developing a fully featured game engine along the way.

What You Have Learned

This was a heavyweight chapter that covered some very difficult subjects. But the idea is to get up the most difficult part of a hill so you can reach the peak, and then head on down the other side. That is what this chapter represents—the last few steps up to the peak. You have now learned the most difficult and challenging aspects of writing a Java game at this point, and you are ready to start heading down the hill at a more leisurely pace in the upcoming chapters. This chapter explained:

  • How to create a threaded game loop

  • How to override default applet methods

  • How to manipulate a bitmap with transformations

Review Questions

The following questions will help you to determine how well you have learned the subjects discussed in this chapter. The answers are provided in Appendix A, “Chapter Quiz Answers.”

1.

What is the name of the interface class that provides thread support?

2.

What is the name of the thread execution method that you can use to run code inside the separate thread?

3.

What is the name of the class that handles vector-based graphics?

4.

What Thread method causes the thread to pause execution for a specified time?

5.

What System method returns the current time in milliseconds?

6.

What is the name of the method that returns the directory containing the applet (or HTML container) file?

7.

What is the name of the method that returns the entire URL string including the applet (or HTML container) file?

8.

What class do you use to store a bitmap image?

9.

Which Graphics2D method is used to draw a bitmap?

10.

Which class helps to improve gameplay by providing random numbers?

On Your Own

The following exercises will test your comprehension of the topics covered in this chapter by making some important changes to the projects.

Exercise 1

The ThreadedLoop program runs at breakneck speed with no delay. Modify the thread delay value to see whether you can slow down the number of rectangles being drawn to just 1,000 rectangles per second.

Exercise 2

The ThreadedLoop program just draws vector graphics. Modify the program so that it draws animated sprites instead.

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

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