Smooth animation when using fixed time step
Sythical

Hello, I'm trying to implement the game loop where the physics is independent from rendering but my animation isn't as smooth as I would like and seems to be jumpy. Here is my code:

#SelectExpand
1// alpha is used for interpolation 2// counter_old_time is to do with displaying the fps 3double alpha = 0, counter_old_time = 0; 4double accumulator = 0, delta_time = 0, current_time = 0, previous_time = 0; 5unsigned frame_counter = 0, current_fps = 0; // also used for displaying the fps 6const unsigned physics_rate = 40, max_step_count = 5; 7 8// information about the magic ball (position and velocity) 9int old_pos_x = 100, new_pos_x = 100, render_pos_x = 100, velocity_x = 60; 10 11const double 12 step_duration = 1.0 / 40.0, 13 accumulator_max = step_duration * 5; 14 15previous_time = al_get_time(); 16 17while(true) { 18 current_time = al_get_time(); 19 delta_time = current_time - previous_time; 20 previous_time = current_time; 21 accumulator += delta_time; 22 23 if(accumulator > accumulator_max) { 24 accumulator = accumulator_max; 25 } 26 27 while(accumulator >= step_duration) { 28 if(new_pos_x > 1330) velocity_x = -15; 29 else if(new_pos_x < 70) velocity_x = 15; 30 31 old_pos_x = new_pos_x; 32 new_pos_x += velocity_x; 33 accumulator -= step_duration; 34 } 35 36 alpha = accumulator / static_cast<double>(step_duration); 37 render_pos_x = old_pos_x + (new_pos_x - old_pos_x) * alpha; 38 39 al_clear_to_color(al_map_rgb(20, 20, 40)); // clears the screen 40 al_draw_textf(font, al_map_rgb(255, 255, 255), 20, 20, 0, "current_fps: %i", current_fps); // print fps 41 al_draw_filled_circle(render_pos_x, 400, 15, al_map_rgb(255, 255, 255)); // draw circle 42 // I've added this to test how the program will behave when rendering takes 43 // considerably longer than updating the game. 44 al_rest(0.008); 45 al_flip_display(); // swaps the buffers 46 47 frame_counter++; 48 49 if(al_get_time() - counter_old_time >= 1) { 50 current_fps = frame_counter; 51 frame_counter = 0; 52 counter_old_time = al_get_time(); 53 } 54}

On line 44, I've added a rest because I want to see how the code behaves when a lot of rendering is involved. If I remove it, the animation is smooth but I'm not happy with that since that's just hoping for the best. On my computer, the frame rate is around 125 and the animation is still jumpy. I've been trying to fix this for a week and have had no luck so I'd be very grateful if someone can help me. Thank you!

Edit: I added the following code to work out the actual velocity (pixels per second) of the ball each time the ball is rendered and surprisingly it's not constant so I'm guessing that's the issue. I'm not sure why it's not constant.

alpha = accumulator / static_cast<double>(step_duration);
render_pos_x = old_pos_x + (new_pos_x - old_pos_x) * alpha;
cout << (render_pos_x - old_render_pos) / delta_time << endl;
old_render_pos = render_pos_x;

SiegeLord

I don't know the answer to your question... but instead I converted your example to the interpolation algorithm I tend to use. It seems to me that my way looks smoother, but that might be my brain lying to me. I didn't feel like setting up a double blind experiment to test :P.

Also... it looks a lot smoother if you use vsync, at least on my computer. If you're on Linux, uncomment the al_wait_for_vsync line. If you're on Windows, uncomment al_set_new_display_option(ALLEGRO_VSYNC...) line. The fact that's its different is probably an Allegro bug.

Here's your code (with added functions to make it run. I also slowed down the ball a bit.):

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_primitives.h> 3#include <allegro5/allegro_font.h> 4 5int main() 6{ 7 al_init(); 8 al_init_primitives_addon(); 9 al_init_font_addon(); 10 auto font = al_create_builtin_font(); 11 12 //al_set_new_display_option(ALLEGRO_VSYNC, ALLEGRO_REQUIRE, 1); // For Windows 13 auto d = al_create_display(800, 600); 14 15 // alpha is used for interpolation 16 // counter_old_time is to do with displaying the fps 17 double alpha = 0, counter_old_time = 0; 18 double accumulator = 0, delta_time = 0, current_time = 0, previous_time = 0; 19 unsigned frame_counter = 0, current_fps = 0; // also used for displaying the fps 20 const unsigned physics_rate = 40, max_step_count = 5; 21 22 // information about the magic ball (position and velocity) 23 int old_pos_x = 100, new_pos_x = 100, render_pos_x = 100, velocity_x = 10; 24 25 const double 26 step_duration = 1.0 / 40.0, 27 accumulator_max = step_duration * 5; 28 29 previous_time = al_get_time(); 30 31 while(true) { 32 current_time = al_get_time(); 33 delta_time = current_time - previous_time; 34 previous_time = current_time; 35 accumulator += delta_time; 36 37 if(accumulator > accumulator_max) { 38 accumulator = accumulator_max; 39 } 40 41 while(accumulator >= step_duration) { 42 if(new_pos_x > al_get_display_width(d) - 70) velocity_x = -10; 43 else if(new_pos_x < 70) velocity_x = 10; 44 45 old_pos_x = new_pos_x; 46 new_pos_x += velocity_x; 47 accumulator -= step_duration; 48 } 49 50 alpha = accumulator / static_cast<double>(step_duration); 51 render_pos_x = old_pos_x + (new_pos_x - old_pos_x) * alpha; 52 53 al_clear_to_color(al_map_rgb(20, 20, 40)); // clears the screen 54 al_draw_textf(font, al_map_rgb(255, 255, 255), 20, 20, 0, "current_fps: %i", current_fps); // print fps 55 al_draw_filled_circle(render_pos_x, 400, 15, al_map_rgb(255, 255, 255)); // draw circle 56 // I've added this to test how the program will behave when rendering takes 57 // considerably longer than updating the game. 58 //al_wait_for_vsync(); // For Linux 59 al_rest(0.008); 60 al_flip_display(); // swaps the buffers 61 62 frame_counter++; 63 64 if(al_get_time() - counter_old_time >= 1) { 65 current_fps = frame_counter; 66 frame_counter = 0; 67 counter_old_time = al_get_time(); 68 } 69 } 70}

And here's mine:

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_primitives.h> 3#include <allegro5/allegro_font.h> 4 5int main() 6{ 7 al_init(); 8 al_init_primitives_addon(); 9 al_init_font_addon(); 10 auto font = al_create_builtin_font(); 11 12 //al_set_new_display_option(ALLEGRO_VSYNC, ALLEGRO_REQUIRE, 1); // For Windows 13 auto d = al_create_display(800, 600); 14 15 double counter_old_time = 0; 16 unsigned frame_counter = 0, current_fps = 0; // also used for displaying the fps 17 const unsigned physics_rate = 40, max_step_count = 5; 18 auto dt = 1.0 / physics_rate; 19 20 auto q = al_create_event_queue(); 21 auto t = al_create_timer(dt); 22 al_register_event_source(q, al_get_timer_event_source(t)); 23 al_start_timer(t); 24 25 // information about the magic ball (position and velocity) 26 int old_pos_x = 100, new_pos_x = 100, render_pos_x = 100, velocity_x = 10; 27 28 float game_time = 0; 29 float offset = al_get_time(); 30 31 while(true) { 32 int steps = 0; 33 ALLEGRO_EVENT e; 34 35 while(al_get_next_event(q, &e)) { 36 if(e.type == ALLEGRO_EVENT_TIMER) { 37 game_time = e.timer.count * dt; 38 39 if(new_pos_x > al_get_display_width(d) - 70) velocity_x = -10; 40 else if(new_pos_x < 70) velocity_x = 10; 41 42 old_pos_x = new_pos_x; 43 new_pos_x += velocity_x; 44 45 steps++; 46 47 if(steps >= max_step_count) 48 break; 49 } 50 } 51 52 float alpha = (al_get_time() - offset - game_time) / dt; 53 render_pos_x = old_pos_x + (new_pos_x - old_pos_x) * alpha; 54 55 al_clear_to_color(al_map_rgb(20, 20, 40)); // clears the screen 56 al_draw_textf(font, al_map_rgb(255, 255, 255), 20, 20, 0, "current_fps: %i", current_fps); // print fps 57 al_draw_filled_circle(render_pos_x, 400, 15, al_map_rgb(255, 255, 255)); // draw circle 58 // I've added this to test how the program will behave when rendering takes 59 // considerably longer than updating the game. 60 //al_wait_for_vsync(); // For Linux 61 al_rest(0.008); 62 al_flip_display(); // swaps the buffers 63 64 frame_counter++; 65 66 if(al_get_time() - counter_old_time >= 1) { 67 current_fps = frame_counter; 68 frame_counter = 0; 69 counter_old_time = al_get_time(); 70 } 71 } 72}

Sythical

Hey SiegeLord, thank you for response. I tried both programs and they seem to be more or less identical on my computer. Vsync does make the animation a lot smoother so I'll start using that. I've read that vsync doesn't work in windowed mode because updates are managed by the OS and the game itself has no control over when the screen updates. I read this when I was trying to figure out how to enable vsync in SDL.

I've been searching for an article which talks about frame rates and refresh rates with game development in mind. In the games I normally play (mainly WoW), the frame rate usually varies a lot but the gameplay is still butter smooth and if I lock the fps to something that doesn't match my monitor's refresh rate, so locked at 70 or 50, the gameplay is still smooth. In my game however, variable frame rate and not matching the refresh rate makes a huge difference. Can someone please briefly explain some of the things that I need to keep in mind?

Thank you again :)

SiegeLord

I don't see the point in drawing more often than the monitor refresh rate. Interpolation allows you to decouple your logic rate from the monitor refresh rate, while keeping things smooth. I have no trouble in enabling vsync in windowed applications on Linux or Windows.

I don't know what technique WoW uses to handle things (it's almost certainly is not delta timing though, given that it works even when FPS drops to single digits).

Thread #613038. Printed from Allegro.cc