🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

04.06 - It's About Time...

Started by
26 comments, last by Teej 23 years ago
Timing is Everything Without some facility for keeping track of time in our game, we frankly wouldn't have a game in the first place -- nothing would be able to properly move. Our game engine (GAMEMAIN.CPP) is designed to be executed as often as possible, and we're coding our game engine on the premise that each iteration of Game_Main() equates to one frame of animation. So, dear reader, how many frames a second does Game_Main() run? Don't worry, I don't know either. The fact is, the sooner our game can finish executing Game_Main() , the sooner it can be called again, and even this assumes that Game_Main() takes the same amount of time to run every frame (which it won't), and that there aren't any Windows messages to process (which there will be). In other words, the number of times Game_Main() is called in a second is variable. We're going to want to move objects in our game. Movement is really just drawing an object at a different location (offset) from where it was previously being displayed. If we had such an object, say a missile, and moved it one pixel to the right every frame, what would we see? Our missle wouldn't be moving at a steady pace, as that would require that our frames were being displayed at a steady rate. We wouldn't be able to dictate the speed of our missile either, as the more frames-per-second there are, the farther the missle would travel. What you'd end up with is your missile flying at different speeds on different machines, and sporatically speeding-up and slowing-down frame-by-frame. This will not do. There are two common methods of rectifying this situation. One is to regulate how many times a second Game_Main() gets to run, thereby providing a constant frame rate. The other involves planning all movement in terms of real-world velocity (e.g. 10 pixels per second), and keeping track of when it's time to move an object, regardless of what frame it is. I think that it's worth the time to investigate each of these methods a bit further before we settle upon what we'll be using... (1) Constant Frame Rate This method, quite simply, forces Game_Main() to be executed a constant number of times per second. The general idea is to calculate how long Game_Main() takes to run (on a frame-by-frame basis), and add in a delay (if necessary). For example, if we wanted our game to run at 30 frames per second, Game_Main() would have to take 33 milliseconds (msec.) on each iteration. If during one such iteration we found that we took 25 msec. to execute, we would simply force Game_Main() to sleep an additional 8 msec. before finishing. To be more precise, we'd include the overhead cost of the Windows' message loop as well, and say that "It should take 33 msec. to get from the end of Game_Main() to the end of Game_Main() again". The way I see it (and yes, I'm speaking personally), there's one major problem with this method, which has to do with the quality of the animation overall -- if the user's machine can handle 60 frames per second, it seems odd that we're doing so much 'sleeping' in our main game code. As an illustration, imagine that our little missile would like to move at 60 pixels a second. Now, because our game is locked-down at 30 FPS, our missile has to be drawn two pixels over every frame. But, if our game was running at 60FPS, the missile would only be drawn one pixel over each frame -- wouldn't that look nicer, rather than skipping pixels? (2) Time-Based Movement In this scenario, Game_Main() is left to run as many times as it possibly can, but keep in mind that we don't have a constant frame rate any more -- we can't move x pixels per frame, for instance. What we do have though is a game that will run as fast as the user's machine will let it, allowing them to enjoy a better quality of animation. The trick here is to plan all movement in terms of physical time. If I wanted our missile to move at 60 pixels per second, it needs to travel 60 pixels regardless of how many frames were available to move it in. What this involves, in a nutshell, is the ability to keep track of the last time the missile has moved, and see if enough time has elapsed since then in order to move it again. If the missile is moving faster than the frame rate, we'd be checking to see how many pixels the missile has to move this frame in order to be moving at the proper rate of speed. Think about it -- the better the frame rate, the smoother the animation, and everything moves at the proper speed regardless. Excuse Me, Do You Have the Time? There are plenty of ways to get time information from the operating system, but only one of them really shines out for our purposes. Since we're living in the realm of milliseconds, we need a fairly precise timer -- Windows WM_TIMER messages are definitely out of the question. GetLocalTime() does in fact provide the current time right down to the millisecond, but for reasons that will become clear in a moment, this is a bad choice as well. What we're looking for is not the current time, but a relative time. We want to be able to ask the operating system for some time value twice, and be able to tell how many milliseconds have elapsed between the two calls. As you'll soon see, this is all that we need. It turns out that there are three possiblities: multimedia timers, high-resolution timers, and the system timer. The documentation for high-resolution timers suggests that not all machines have the proper capability, so we'll discount that as an option. Of the other two, timeGetTime() and GetTickCount() , either will suit our purpose (and are available on Win95/98/NT/2K). The issue with time functions such as GetTickCount() and timeGetTime() is their resolutuion, i.e. how quickly you can call them in succession and get accurate numbers. If for instance a function can't be called twice within 50 msec., it isn't going to help us if we need to time intervals less than 50 msec. Of the two possible functions, it turns out that timeGetTime() offers the best resolution and machine independance, so that's what we'll be using. It should be noted that these are DWORD (32-bit) values, which means that they'll roll over (start over at 0) 49.7 days after the user's machine was started, so we'll either have to check for this situation in our code, or ignore it and hope that our game isn't running at that time. This all boils down to a few extra cycles devoted every frame for this check, and I'd be interested to see how many game developers bother with it... So, how do we solve all of our timing problems with timeGetTime() ? Like so:

Game_Main()
{
    static DWORD lastTickCount = timeGetTime();
    DWORD thisTickCount;
 
    // Timing
    thisTickCount = timeGetTime();
    G.diffTickCount = thisTickCount - lastTickCount;
    lastTickCount = thisTickCount;
 
    // ..
    // Game code
    // ...	
}
 
The variable lastTickCount is static, which means that it keeps its value between function calls. The first time Game_Main() is called, lastTickCount is set to the current millisecond count, and every other time Game_Main() is called it will still be holding whatever it was when Game_Main() ended the last time. What the timing code accomplishes is to get not only the current time, but the difference between the current time now and the current time last Game_Main() iteration. What we're left with in our new global variable diffTickCount is the number of milliseconds that have elapsed since our last time processing our game code, which can in turn be used to check against sprite movement, or anything else we're timing. Go through this sample a few times in your head, pretending that the game code takes 10 msec. to run, and convince yourself that G.diffTickCount always makes the proper elapsed time available to the rest of our game. By the way, that's why it's a global variable and not local -- other functions (and there will be more in the future) will need to access this value as well. Let's take a look at how this delta (difference in times between frames) can be actually used. Say that our little missile would like to move one pixel per second (a very slow missile indeed). First, we need a variable to keep track of when the missile last moved: static DWORD missileTickCount = 0; ...and every frame, add-in the number of milliseconds that have elapsed: missileTickCount += G.diffTickCount; As you could imagine, this variable will steadily accumulate elapsed frame times. Next, we'd like to trigger the missile's movement when we have 1000 (or more) milliseconds accumulated (which means that it hasn't moved in that long): if (missileTickCount >= 1000) {...} Don't forget the >= as missileTickCount could have contained values like this from frame to frame: ...925, 943, 961, 988, 1004, 1029... ...and we want to catch it as soon as possible when it reaches the one second mark. Finally, we want to perform our movement and note the fact that we've accounted for our one-second movement: if (missileTickCount >= 1000) { // ...move the sprite missileTickCount -= 1000; } It's important that this last line makes sense to you. If the value of missileTickCount was actually 1015 for instance, we've got 15 msec. too much, and that value should be carried over so that the sprite can move properly one second after it was originally supposed to, not one second after we actually moved it. Now, let's play with the example above to take into account an object that wants to move x pixels per second. It might help to show you some sample data first:

x        = 1 pixel every (msec.)
----     -----------------------
1        1000
10       100
20       50
50       20
100      10
1000     1
 
At any one frame, an object might need to move 0 or more pixels. Since our movement counter variable tracks per-pixel movement, we're going to need a loop in case there's more than one movement 'turn' for this iteration of Game_Main() . You should be able to figure out that 1000 / x = the number of msec. to wait before moving one pixel, and here's some code that would do the trick:

missileTickCount += G.diffTickCount;
while (missileTickCount >= (1000 / missileMovementRate))
{
    // Move your missile one pixel here...
    
    missileTickCount -= (1000 / missileMovementRate);
}
 
Be careful that missileMovementRate is never zero else you'll have a division-by-zero problem on your hands. Hey, now that we've got a timing mechanism in place, it's time for a demo! The following code uses BaseCode2, which is the first article so far to do so -- grab a copy on my webpage. It's essentially the same 'ol template code, but adds a DirectDrawClipper for safe blitting, source color-keying for black-background images, and the G.diffTickCount global variable for timing. Once you've got that ready, throw the following in Game_Main() :
    
// We're using the letter 'A' for our sprite

#define SPRITE_WIDTH    11
#define SPRITE_HEIGHT   13
  
//////////////////////////////////////////////////////////////////////////////

// Game_Main

//

// This function is called once per frame.

//

void Game_Main()
{
    HRESULT      hRet;
    // Timing

    static DWORD lastTickCount   = timeGetTime();
    DWORD        thisTickCount;
    // Sprite

    static int   pos             = -SPRITE_WIDTH;       // Sprite position

    int          spriteRate      = 200;                 // Pixels per second

    static DWORD spriteTickCount = 0;                   // Movement counter

    // Rendering

    RECT         rectSrc,                               // Sprite source

                 rectDest;                              // Sprite destination

 
    // Leave if we're quitting

    if (G.bQuitting) return;
 
    //------------------------------------------------------------------------

    // Timing

    //------------------------------------------------------------------------

 
    thisTickCount = timeGetTime();
    G.diffTickCount = thisTickCount - lastTickCount;
    lastTickCount = thisTickCount;
 
    //------------------------------------------------------------------------

    // Get keyboard input

    //------------------------------------------------------------------------

 
    while (hRet = G.lpDIKeyboard->GetDeviceState(256, G.KeyState)
                  == DIERR_INPUTLOST)
    {
        if (FAILED(hRet = G.lpDIKeyboard->Acquire())) break;
    }
 
    //------------------------------------------------------------------------

    // Sprite movement

    //------------------------------------------------------------------------

 
    spriteTickCount += G.diffTickCount;
    if (KEYDOWN(DIK_SPACE)) return; // Delay

    while (spriteTickCount >= (1000 / spriteRate))
    {
        // Move the sprite one pixel over

        pos++;
        
        // Wraparound the display if off the right edge

        if (pos > SCREEN_WIDTH) pos = -SPRITE_WIDTH;
        
        // Update the sprite's movement counter

        spriteTickCount -= (1000 / spriteRate);
    }
 
    //------------------------------------------------------------------------

    // Rendering

    //------------------------------------------------------------------------

 
    // Clear the back buffer

    EraseBackground();
 
    // Prepare the sprite's source RECT

    rectSrc.left = rectSrc.top = 0;
    rectSrc.right = SPRITE_WIDTH - 1;
    rectSrc.bottom = SPRITE_HEIGHT - 1;
 
    // Prepare the sprite's destination RECT

    rectDest.left = pos;
    rectDest.top = 0;
    rectDest.right = rectDest.left + SPRITE_WIDTH - 1;
    rectDest.bottom = rectDest.top + SPRITE_HEIGHT - 1;

    // Render the sprite to the backbuffer

    G.lpDDSBack->Blt(&rectDest, G.lpDDSRes, &rectSrc, DDBLT_WAIT, NULL);
 
    // Flip the surfaces

    G.lpDDSPrimary->Flip(NULL, 0);
}
  
Since we're using clipping, we can't use BltFast() anymore, but that's fine. What we can do now though is draw sprites partially off of the display, as this code does. You'll see the letter 'A' travelling from the left of the display, across to the right at a constant rate of 200 pixels per second. If you press SPACE, the function will abort causing a short delay before the sprite is rendered again. By pressing and holding the space bar, it is demonstrated that regardless of how often the sprite is rendered, it is in fact moving at a constant velocity of 200 pixels per second. As you'll notice in the SDK documentation for timeGetTime() , you need to initialize the function's resolution using timeBeginPeriod() and timeEndPeriod() , so these lines are added to Game_Init() and Game_Term() :

//
// In Game_Init():
//
// Initialize our timer resolution for 1 millisecond
timeBeginPeriod(1);

//
// In Game_Term();
//
// Reset our timer resolution to the default (use the same value
// (that was used in timeBeginPeriod() to reset)
timeEndPeriod(1);
 
Theoretically there's a chance that the call to timeBeginPeriod() will fail by returning TIMERR_NOCANDO, but in this example I didn't bother checking...the worst that can happen is that we're stuck using the timer's default resolution which probably isn't so bad anyhow (1-5 msec.). Now that we're able to tell when a period of time has elapsed, everything in our game code can immediately benefit from it -- sprite animation, game state counters, in-game timers, AI processing...the list goes on and on. Questions? Comments? Please reply to this topic. Edited by - Teej on June 1, 2001 2:22:05 PM
Advertisement
Hi teej,

I''m looking at http://teejb.webjump.com/ but I don''t see ''BaseCode2.zip''. Am I looking at the right page?

Thanks in advance!

BTW, great tutorials! Keep it up
she-devil with a sword
I just typed in basecode2.zip after the last / in the website and it downloaded for me:

http://teejb.webjump.com/BaseCode2.zip
Well, I just looked at the code from the file I downloaded, and it doesn''t look any different than basecode1. I must not have the right file, either. From the code above, it looks like the changes are mainly just in Globals.h, but I''m not sure if that''s all.
OOPS!

That's not the correct BASECODE2.ZIP. Actually, that's an oldy from long ago... I have to wait until 6pm to upload the new BaseCode2...

Boy, you guys are fast!

Teej

UPDATE: It's there now, and should work...


Edited by - Teej on May 29, 2001 6:42:21 PM
Man..
I don''t understand how you guys can access those files!

I tried over and over again, upper case, lower case...waited for few hrs...

I keep gettng that SITE NOT FOUND error!

Can someone mirror the file somewhere??
Hmm... I try accessing the webpage and click on a link, and it offers to download just fine.

Can someone else tell me if the friggin'' webpage is working or not???

Teej



It works for me teej. Just paste the url into the address field of the browser.

http://teejb.webjump.com/BaseCode2.zip
Actually, there is a link directly off the main page.
Just go to: http://teejb.webjump.com/

And you can select "BaseCode2.zip" from there.

Yes Teej, your page is working .
she-devil with a sword
It''s a cinch. Works just fine.

This topic is closed to new replies.

Advertisement