The Perfect Gameloop?

Published July 31, 2008
Advertisement
If anything reminds me how far I have to go as a game developer is my recent considerations for the game loop. I want to have absolutely smooth scrolling and moving sprites, without resorting to OpenGL or Direct3D. My initial testing of both SDL and Allegro page flipping modes indicate this can be done, depending on how I implement the game loop.

I initially went with the timed game loop, where I update the movement in the game based on the measured time between frames. I thought that this would make sprite movement as easy as following a physics text book, and run smooth at any refresh rate. This was the technique used in David Brackeen's Java game programming book.

while ( game_is_running ) {  elapsed_time = get_time() - last_time; last_time += elapsed_time;  get_user_input()  update_game( elapsed_time ) render_game( screen ) wait_for_retrace();}


What I got was intermittent "blibs" between smooth movements. Apparently, even the underlying performance timers for windows are not 100% reliable. At first I blamed the page-flipping implementation in SDL, because an Allegro demo showed smoother animation in page flipping mode.

I almost abandoned SDL until I realized that the SDL.DLL pre-built for Windows will not use DirectX unless the %SDL_VideoDriver% variable wasn't set to "directx". I figured this out when I noticed that SDL_VideoInfo()->hw_available returned false.

After modifying the gameloop to sync with the refresh rate made the SDL version move just as smooth in double buffered mode as with Allegro in page-flipping mode, as long as I both updated and rendered the game at retrace.

I've read that using a time-interval like above would cause physics calculations to compound on rounding errors and otherwise break gameplay. The elapsed_time could be clamped to a reasonable max/min before being passed to the update function. In any case, I could not get the smooth uninterrupted movement that I wanted.

I still didn't want the game-speed to be dependent on the frame-rate. The next thing I tried was fixing the update rate at 60Hz, while calling the rendering function at retrace, or as soon as possible.

#define FRAME_INTERVAL 1000/60frame_time = game_time();while ( game_is_running ){  get_user_input();  /* Update only at 60Hz. Skip if we're running under */  while( game_time() > frame_time )  {    update_game(); /* Update one tick */    frame_time += FRAME_INTERVAL;   }  render_game( screen );}


I could have also put a condition to allow the loop to skip a few updates if the loop is running slow (under 60 updates per second), but I wanted the game to go into slow-mo and not skip a frame.

This wasn't good enough because while it looked great on a display that could support 60Hz, the 50Hz LCD on my thinkpad (and primary development machine) made it look all jumpy. Thats because the game was updating faster than my LCD. If I slowed the updates to 50Hz, it looked fine on my laptop, but it appeard "laggy" on the CRT monitor because it was refreshing faster than the updates.

Actually, I'm exagerting the lag and jumpyness a bit but I didn't want to use this loop.

BTW: I forget where I actually learned about these game loops, If I remember the link I'll post it.

The last method I implemented was rendering partial frames or "tweening". This is achieved by locking the internal frame rate (updates) at 50Hz, measuring the time between rendered frames and passing it to the render function as a fraction out ofa 50Hz frame period. The 50Hz was chosen so that it work well under a 50Hz monitors (LCD or PAL TV).

#define FRAME_INTERVAL 1000/50frame_time = game_time();while ( game_is_running ){  get_user_input();  /* Update only at 60Hz. Skip if we're running under */  while( game_time() > frame_time )  {    update_game(); /* Update one tick */    frame_time += FRAME_INTERVAL;   }  current_frame_time = ((game_time() + FRAME_INTERVAL) - frame_time);  tweening  = current_frame_time / FRAME_INTERVAL;  render_game( screen, tweening );}


Then when it comes time to render my sprite, I predict where the sprite will be based on its current velocity (dx,dy) and draw to that location on the screnn:

void render_sprite( sprite, screen, tweening ){  x = sprite->x + sprite->dx * tweening;  y = sprite->y + sprite->dy * tweening;  blit( sprite->frames[ sprite->frame ], screen, x, y, sprite->w, sprite->h );}


This approach is very good. Even if I set the the internal frame rate to say 30Hz or even 24Hz (feature movie display rate) movement looks quite smooth even on both my 60Hz CRT or 50Hz LCD and at the same speed. Again, the loop will not throw out frames if running behind and will opt to slow-mo.

For my requirements, I need precise collision detection because the player will be dodging many bullets as well as enemies. So I may need to run collision detection frequently as once per rendered frame.

So despite my reservations of locking the frame rate, I'm going with the simple text-book approach of waiting for the next frame period at the end of the loop. I'll separate the update() function into compute_AI() and animate(), so that I can do the AI at a lower frame-rate (maybe @20Hz?) , then animate and render the game at 60Hz. This way I can rely on the refresh rate for smooth scrolling and animation, while relying on the system clock for logic.

This also means that my game would slow by 16% on my 50Hz LCD, but it will still look smooth. I think I can live with that.

I might then go with using Allegro, since it lets me request and query the display's refresh rate and supports both page and triple buffering. None of these features as far as I know are supported in SDL 1.2 (perhaps purposely so).

I'm open to comments, because I know locking the game speed to a 60Hz display refresh rate isn't ideal for most players.
Next Entry New Job.
0 likes 1 comments

Comments

venzon
I just randomly saw your entry and thought I could offer a helpful comment, so I apologize if I've misread your entry and am off the mark as to what you're trying to do.

Have you read this?
http://www.gaffer.org/game-physics/fix-your-timestep

It's oriented toward 3D games with complicated physics engines, but it would probably apply to your project as well. You're already doing the "final touch" that he mentions in his article (tweening), but he also shows how to allow the game update rate to be totally independent of render rate but at the same time stay in sync with wall-clock time.
August 02, 2008 05:37 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement