6.1 The endless animation loop

In a game program like Pop, the images keep changing even when you're not giving it input. Things move. The program animates itself. Where is the point in the program that we can repeatedly loop back to for new updates?

Another characteristic thing about game programs is that the user can give input at any time, using the mouse or the keys to move the player icon. How do we synchronize these inputs with the game updates?

In any Windows program, the internal update processes and the user input processes are concurrent or parallel flows, that is, the updating is computed by the machine and the inputs are 'computed' by the player, with the two systems acting independently.

We'll draw a new kind of UML diagram to show how this fits together. Recall that UML has quite a range of diagrams. The use case diagrams are good for requirements gathering. Component diagrams are useful for mapping out the interdependencies of a project's source-code files. Class diagrams show how the classes inherit and associate. Sequence diagrams show the order in which program events happen. We're going to talk about more sequence diagrams in this chapter and also about one more kind of UML diagram: an activity diagram. Sequence and activity diagrams are for showing how a program runs. When you design a program you need to think not only about its classes but also about its run-cycle or work flow.

A UML activity diagram is similar to a traditional flow-chart. We draw rounded rectangles around activities in the program and draw little diamonds to indicate test points. Arrows show the flow of the program control. What makes an activity diagram a bit more than a flow-chart is that it allows you to show concurrent processes. We use horizontal lines to indicate the 'forks' and 'joins' where parallel processes either split apart or join back together. Figure 6.1 is an activity diagram for the Pop program as a whole.

Figure 6.1. Activity diagram of the Windows program flow


A Windows program maintains an internal structure called the message queue, which is basically an array of special MSG structures. A message is placed on the queue, or 'enqueued,' each time that the user does something “ press a key, move the mouse, make a menu selection. And some Windows methods place messages on the queue themselves . A message can be placed on the queue at any time.

Rather than responding to each message immediately, a Windows program like Pop lets the messages wait in the queue until it is ready to deal with them. Pop works its way through the message queue, processing the messages in the order in which they arrived.

As we mentioned in Chapter 5: Software Design Patterns, this is an example of the Command pattern. Rather than executing a Windows message right away, we encapsulate the idea of the message into a command that we place into our message queue, to be executed when we have time.

When there are no more messages to process, Pop begins calling an internal method named OnIdle . When OnIdle returns, Pop checks if there are any new messages to process, and then it calls OnIdle again. When there are no messages at all, Pop's behavior is simply to call OnIdle over and over again. If you want to read more about the Windows execution flow see Chapter 23: Programming Windows with MFC.

Given that OnIdle gets called over and over, this is the spot to stick in the code to run your animation. The CPopApp class defined in the pop.h and pop.cpp files is a child of the MFC CWinApp class that owns the OnIdle method. So what we'll do to animate our program is to override and extend the code for CPopApp::OnIdle inside the pop.cpp file.

If you write an 'eternal-loop' program in the wrong way, you can find it impossible to terminate the program (short of using Ctrl+Alt+Del to get to the Task Manager). That's why it's a good idea to use the approach described here. By locating the eternal loop inside the OnIdle function, we're sure that all user messages to the program get properly processed . If a user message tells the program to terminate, then it never does get back to OnIdle and it exits smoothly.

Inside CPopApp::OnIdle we do two things: we compute an appropriate Real dt timestep, and we pass this dt to the documents with a CPopApp:animateAllDocs(dt) call. Before discussing these points, let's say a bit more about how we override OnIdle .

Using the OnIdle method to call animateAllDocs

We animate by overriding the CWinApp::OnIdle function. We want it to make calls to the CDocument objects that will cascade down to the cGame objects and the CView objects.

An application executes the CWinApp::OnIdle function at least once each time that it finishes processing its current messages. Normally the first two calls to OnIdle are used for maintaining the appearance of the user interface, that is, things like the toolbar buttons and the menu selections. Thus the first call to OnIdle will generate a call to, for instance, OnUpdateGameSpacewar to tell the menu whether or not the Game Spacewar selection should have a checkmark next to it.

The return type of OnIdle is BOOL . If you want your application to keep calling OnIdle over and over again even if no messages are found, you have OnIdle keep returning TRUE . This is safe because OnIdle will continue checking for messages after each return in any case.

If you only want to call OnIdle once each time that you finish processing messages, then return FALSE . Here, a way to keep a program doing things 'forever' is to have its OnIdle function generate more messages. After the program processes these messages, it goes back to OnIdle , which produces more messages, and so on. We can think of either approach as an 'eternal-loop' program (see Exercise 6.5).

The simplest way to put an animation loop inside OnIdle might be to pick a target timestep of, say, 0.05 second (that is, 50 milliseconds , or 20 updates a second), and do something like this.

 BOOL CPopApp::OnIdle(LONG lCount)  {      CWinApp::OnIdle(lCount); //Do the base class WinApp processing.      animateAllDocs(0.05);          //Step through all the docs and feed this timestep.      return TRUE; //Keep doing it over and over.  } 

But we'll improve on this a bit.

  • First of all, we'd like the timestep that we feed into animateAllDocs to reflect the actual time that it really takes the computer to do the update. To do this we need to get information from the computer about the system time. There is a C++ clock method which returns the time in milliseconds, but using the function is a bit messy. So we'll encapsulate our time-getting code within a class we'll call cPerformanceTimer , and give it a tick function which returns the time as a Real number of seconds. More about this in the following section.

  • Secondly, we'd like to have a switch for turning the animation on and off.

  • Thirdly, we'd like to avoid another problem with eternal-loop programs, which is that they can suck up every available machine computation cycle “ a very bad situation if you minimize such a program, forget about it, and then try and run some other programs. If the minimized eternal-loop program is still running, you'll find that your other programs behave very poorly. Our standard practice for avoiding this is to have our eternal loop only be active when our eternal-loop program is the focus or foreground window, that is, only when it's the window whose caption bar is highlighted.

For full details about how to do this, see the CPopApp::OnIdle code in the pop.cpp file of the Pop Framework.

Software Engineering and Computer Games
Software Engineering and Computer Games
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net