|
|
| Game Loop |
|
adamk kromm
Member #5,432
January 2005
|
Hi All I'm hoping someone can clarify the pros and cons of two different game loop styles. The first one, and the one I've only used, is to have a fancy while loop that makes logic run at a predetermined FPS and then have the rendering take up what ever time is left over: (Taken from http://www.koonsolo.com/news/dewitters-gameloop/) 1const int TICKS_PER_SECOND = 25;
2 const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
3 const int MAX_FRAMESKIP = 5;
4
5 DWORD next_game_tick = GetTickCount();
6 int loops;
7 float interpolation;
8
9 bool game_is_running = true;
10 while( game_is_running ) {
11
12 loops = 0;
13 while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
14 update_game();
15
16 next_game_tick += SKIP_TICKS;
17 loops++;
18 }
19
20 interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
21 / float( SKIP_TICKS );
22 display_game( interpolation );
23 }
The other way which I have not tried, but have heard of people doing is an event driven "game loop". The way I understand it is you set up a bunch of timers. One for rendering, one for updating the logic, and one/some for getting input. Can someone explain the pros and cons of each? Also which style is best suited for what style of game? Thanks ---------- |
|
Dizzy Egg
Member #10,824
March 2009
|
The best way is to separate logic from drawing; do your logic every loop, but only draw if it's time to draw. This forum is FULL of examples, I'm sure one of the other guys will post a link for you. But ultimately, ALWAYS separate logic from drawing.
---------------------------------------------------- |
|
Schyfis
Member #9,752
May 2008
|
Dizzy Egg said: But ultimately, ALWAYS separate logic from drawing. I think they are separate in the example code- update_game() would do the logic, and display_game(interpolation) would do the drawing. adamk kromm said: Can someone explain the pros and cons of each? Also which style is best suited for what style of game? Someone should correct me if I'm wrong, but I believe one major pro of an event-driven system would be that it would be easier to adapt to using multiple threads. Another pro is that timing of certain events would be more accurate than if you were to use a typical game loop. For example, in a typical game loop, if update_game takes a particularly long time to spawn objects, load data, or communicate over a network, the call to display_game could potentially be later than you want it to be. Most of the time though, I think the two methods are pretty much equally valid. I don't think either is suited to any particular type of game more than any other type of game. ________________________________________________________________________________________________________ |
|
SiegeLord
Member #7,827
October 2006
|
adamk kromm said: The way I understand it is you set up a bunch of timers. One for rendering, one for updating the logic, and one/some for getting input. This seems insane, I don't know if anybody really does it like that. The event driven loops generally seen for A5 involve a single timer and are fundamentally the same to the game loop you posted. Once you add interpolation, they become practically identical: 1 const int MAX_FRAMESKIP = 5;
2 const float FPS = 25;
3 const float dt = 1.0 / FPS;
4
5 ALLEGRO_TIMER* t = al_create_timer(dt);
6 al_register_event_source(q, al_get_timer_event_source(t));
7 al_start_timer(t);
8
9 float game_time = 0;
10 float offset = al_get_time();
11
12 bool game_is_running = true;
13 while(game_is_running)
14 {
15 int loops = 0;
16 ALLEGRO_EVENT e;
17 while(al_get_next_event(q, &e))
18 {
19 if(e.type == ALLEGRO_EVENT_TIMER)
20 {
21 game_time = e.timer.count * dt;
22
23 update_game();
24
25 loops++;
26 if(loops >= MAX_FRAMESKIP)
27 break;
28 }
29 }
30
31 float interpolation = (al_get_time() - offset - game_time) / dt;
32
33 display_game(interpolation);
34 }
"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
|
Specter Phoenix
Member #1,425
July 2001
|
One thing I've noticed, not sure if it is just me, but beginners seem to have problems with what constitutes game logic. I remember that my first game I coded (back in 2006 if I remember right) I had put some game logic in my drawing routines and I've seen it done a lot being on here, Cplusplus.com, gamedev.net, and cprogramming.com. In my case I just threw it where ever just to get it working and never thought about the placement of it.
|
|
weapon_S
Member #7,859
October 2006
|
Since the OP asked explicitly: there's only one hiatus in that article: it assumes the logic to generally finish in time. When you have complicated logic f.i. 3D collisions, that condition might not hold true on slow hardware. (Kind of depends on what kind of hardware range you want to target.) So that's only important in real-time action games with complicated physics[1] on hardware where you can expect the logic to take more than a frame's time for multiple frames. References
|
|
adamk kromm
Member #5,432
January 2005
|
Hi Thanks for the replies. For some reason I thought the two methods where significantly different in purpose. ---------- |
|
myoung
Member #12,934
June 2011
|
I'm trying to wrap my head around game loop designs in event-based Allegro 5. I adapted the A5 Tutorial "Bouncer" example with the game loop with interpolation algorithm SiegeLord posted (code attached). In the adapted example, a purple box that moves diagonally, bouncing when it hits the edge of the display, I get pretty smooth motion regardless of vertical refresh rate or vsync being enabled or not. Though when the bitmap changes direction abruptly (at the edges of the screen), the motion hiccups, obviously because the interpolation presumes the bitmap's direction will proceed when in fact on the next logic loop the direction changes. Is that just a limitation of using interpolation? It seems to be that "extrapolation" would be more accurate term, or perhaps I'm just thinking about it incorrectly or implemented it incorrectly? |
|
SiegeLord
Member #7,827
October 2006
|
myoung said: implemented it incorrectly This. You coded the algorithm to perform extrapolation instead of interpolation, hence your issues. The general algorithm tends to assume that you are interpolating between the current position and the position one game tick in the past. Your code interpolates between the current position and one game tick into the future (sort of, as it assumes linear motion will persist into the future), thus making it perform extrapolation. Additionally interpolation game loops tend to store the old position etc. explicitly, instead of assuming any sort of relationship between position and velocity. This way your object will be less likely to display in a position it was never actually in. It also obviates the treatment of teleportation. "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
|
myoung
Member #12,934
June 2011
|
Ah! I figured it out. Changed this... flux_capacitor(int gigawatts) ...to this... flux_capacitor(double gigawatts) But really... I stored the previous x and y positions of the object before they are updated in the game logic code block, and draw the object at... previous position + (current position - previous position) * interpolation value. 1while(!game_done) {
2
3 int loops = 0;
4 ALLEGRO_EVENT ev;
5
6 while (al_get_next_event(event_queue, &ev)) {
7
8 if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
9 game_done = true;
10 }
11
12 if (ev.type == ALLEGRO_EVENT_TIMER) {
13 game_time = ev.timer.count * dt;
14
15 //update game
16 if(bouncer_x < 0 || bouncer_x > SCREEN_W - BOUNCER_SIZE) {
17 bouncer_dx = -bouncer_dx;
18 }
19
20 if(bouncer_y < 0 || bouncer_y > SCREEN_H - BOUNCER_SIZE) {
21 bouncer_dy = -bouncer_dy;
22 }
23
24 bouncer_px = bouncer_x;
25 bouncer_py = bouncer_y;
26
27 bouncer_x += bouncer_dx;
28 bouncer_y += bouncer_dy;
29 // done updating game
30
31 loops++;
32 if (loops >= MAX_FRAMESKIP)
33 break;
34 }
35 }
36
37 float interpolation = (al_get_time() - offset - game_time) / dt;
38
39 //display game with interpolation
40 al_clear_to_color(al_map_rgb(0,0,0));
41
42 al_draw_bitmap(bouncer, bouncer_px + (bouncer_x - bouncer_px) * interpolation, bouncer_py + (bouncer_y - bouncer_py) * interpolation, 0);
43
44 al_flip_display();
45 // done displaying game
46}
Still not sure if I'm correct, though that yields the same smooth motion without drawing objects where they perhaps shouldn't be drawn. I suppose adding some user controlled objects will make it clear if I now have it correct or if I'm rendering in the past. |
|
weapon_S
Member #7,859
October 2006
|
Didn't the linked article actually do extrapolation and named it wrong? It's not a bizarre thing to do, given that the thing you display is one frame behind otherwise. It does have inconsistencies then, though. |
|
SiegeLord
Member #7,827
October 2006
|
I guess I'd have to try it on a non-trivial example, but I don't think the artifacts make it worth using extrapolation. The whole point of using interpolation was to remove the artifacts, and if you're removing the jitter ones and replacing them with something that might look even worse... it seems odd to me. Interpolating on one game tick back doesn't seem to be so bad to me... at 60Hz it's 16ms lag... at 120Hz it's 8ms lag. The computers are very fast these days so upping the logic update rate would be my solution to dealing with this lag, rather than doing the questionable extrapolation. That said, the higher your logic rate the less need you have for any sort of interpolation at all... so, meh "For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18 |
|
myoung
Member #12,934
June 2011
|
SiegeLord, thanks for posting that game loop code and for the further elaboration on the interpolation. I re-worked the game loop of my first A5 creation using that interpolation method and with an only rare hiccup (maybe some other even on my system?) I get smooth as silk motion at any refresh rate with a fixed logic rate. I don't really notice input latency with a 60Hz logic rate and faster than 60Hz is indeed no sweat on modern hardware and for what I'll be coding. I would have been otherwise implementing delta time with variable logic rates, which I now see from another posting is a bad idea. Now to trace it and actually understand what it's doing. |
|
|