Understanding Frame Rates and Smooth Entity Movement - Devlog
Patrick O'Dacre
Posted on September 29, 2022
My name is Patrick, and I have a YouTube channel called Handmade Games where I share updates as I learn how to program games from scratch using the Odin programming language.
At this point in my game's development, I decided it was time to dive deeper into understanding frame rates and how they impact how we program.
I've spent many days and hours sorting through everything I could find so I could move on with my game and continue to make progress.
I've learned that this is a very broad topic with many related concepts -- delta time, delta motion, capping frame rate, time steps, physics, etc.
It's very hard to know where to start. And it's very easy to get lost in all the material.
At this point I'm concerned with the following:
- How to move my entities around the game smoothly
- How to control entity speed
- How to reliably determine collisions
- How to ensure my game runs well on a variety of systems and monitors
If you'd like some insight into these areas, continue reading.
My goal is to save you a lot of time and headache so you can continue building your game and learning.
What is Frame Rate and Why Does it Matter?
Your game loop speed, or frame time, is different from your monitor refresh rate, or frame rate.
Your monitor refreshes at a regular interval; whereas, your game loop does not.
There are two aspects to the problem:
- Your game loop's frame time is variable.
- Your game loop's frame time is out of sync with your monitor.
Janky entity movement and skipped sound are just two of the symptoms you'll notice.
I highly recommend you watch this episode of Handmade Hero for a more visual explanation of the problem.
Where to start?
The prevailing advice I encountered was that it's best to design around a target frame rate.
you should ask "what is the lowest frame rate I think is acceptable for this game" and design to that
~ Casey Muratori
Since I'm still very much in the exploratory stage, I really have no idea what frame rate is acceptable for my game, so I am starting with 60FPS to match my own monitor. If my game begins to grow so much that fitting everything into 16ms per frame isn't feasable, I can slow things down to 30FPS (33ms / frame) to give myself more time.
Targetting a Frame Rate
As you might expect, the different approaches recommended online range from simple to complex.
The most complex strategies I found were ones that attempted to decouple physics from rendering. Physics are processed at a fixed rate which makes them predictable, and rendering is allowed to happen as quickly as possible.
A popular article detailing this is Fix Your Timestep.
The sales pitch is that you get the best of both worlds -- predictable physics with the fastest possible visuals.
But with these strategies there is an assumption being made -- it is assumed that updates should be completed before rendering.
Why might that not be the best thing?
Let's imagine you have an array of entities. If you were to decouple updates and rendering you would have to iterate through all the entities twice -- once to update positions, and a second time to render them. For this decoupling to be worth it you would have to be certain that the gains won by rendering more often would outweigh losses from the extra work, to say nothing of the added complexity.
I do not like decoupled methods because I find they lead to (sometimes much) slower code. Being able to update and render in one step can make a substantial difference in terms of cache utilization and memory bandwidth, especially when you are dealing with lots of objects.
~ Casey Muratori - Handmade Network
Watch this clip from Handmade Hero to hear more.
It is with this in mind that I decided to go with the more simple solution -- enforcing a frame time for the main loop.
Capping the Frame Rate for the Main Loop
Enforcing, or capping, a frame rate means programming your loop to iterate at a consistent rate.
This approach met almost all my requirements:
- Smooth predictable movement for my entities
- Fine control over entity speed
- Reliable collision detection
I'm not quite sure how to handle different monitor refresh rates, yet. Be sure to subscribe to Handmade Games on YouTube to get that update.
Capping your frame rate boils down to measuring how long it took you to complete your current frame and waiting out the difference until you hit your target time.
// set the target to your current monitor's refresh rate if you have vsync turned on;
// otherwise your game will not run properly
target_milliseconds_per_frame : u64 = 1000 / 60 // 60 FPS
start : u64
end : u64
game_loop: for
{
now = SDL.GetTicks()
// inputs
// update and render
end = SDL.GetTicks()
for (end - start) < target_milliseconds_per_frame
{
end = SDL.GetTicks()
}
SDL.RenderPresent(ctx.renderer)
SDL.RenderClear(ctx.renderer)
}
We could use a sleep-type function like SDL.Delay(remaining_time)
, but we'd have to set the scheduler's granularity to make that work properly, and this approach is good enough while we continue to learn.
Resources
While researching this topic I found many great articles, and videos. I also got some help from the great people in the Odin Lang discord.
Posted on September 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024