Introduction
Hello! In our last lesson, we put the finishing touches on our Phong lighting model by passing light and camera properties to our shaders using uniforms. This gave us dynamic, realistic lighting on our 3D objects. However, our scene is still static. To bring our world to life, we need things to move.
Today, we'll address a fundamental concept in game and simulation development: implementing a time-step mechanism using delta time. Our goal is to ensure that any movement or change in our game happens consistently, regardless of how fast or slow a user's computer is. This is what we call frame-rate independent logic.
This concept has strong parallels with what you've likely studied in chemical engineering. When simulating a process, you model how state variables (like temperature or concentration) change with respect to time, often using differential equations. In game development, we do something very similar: we model how a game object's position changes over time. Making this change respect real-world time, not just computational cycles (frames), is the core of today's lesson.
1. The Problem: Frame-Rate Dependency
Imagine we want to move an object. A simple approach would be to add a small, fixed amount to its position in every iteration of our main loop.
// In the main game loop
while (!glfwWindowShouldClose(window)) {
// ... input, rendering, etc.
gameObject.position.x += 0.01f; // Move 0.01 units every frame
}
What's the problem here? The game loop runs once per frame. A powerful computer might render at 500 frames per second (FPS), while a less powerful one might run at 60 FPS.
- Fast PC (500 FPS): The object moves
500 * 0.01 = 5.0units per second. - Slower PC (60 FPS): The object moves
60 * 0.01 = 0.6units per second.
The object's speed is now tied directly to the hardware's performance, which leads to an inconsistent and unfair experience. The following video illustrates this problem perfectly.
TIMESTEPS and DELTA TIME | Game Engine series
The Cherno provides an excellent demonstration of why tying game logic directly to frame updates is problematic.
Watch from 00:25 to 01:57. The video shows how camera movement speed changes dramatically when the frame rate is uncapped (VSync off).
This article provides another clear, text-based explanation of the same issue using a car racing analogy.
Understanding Delta (Δ) Time In Games
This article from dev.to explains the core problem of frame-rate dependency in a very accessible way.
Read the sections 'Let's Work Through A Problem' and 'Solution'. It clearly breaks down the difference between 'units per frame' and 'units per second'.
2. The Solution: Delta Time
To solve this, we need to measure the amount of real-world time that has passed since the last frame was rendered. This duration is called delta time, often abbreviated as dt or Δt.
If a game is running at 60 FPS, each frame takes approximately 1 / 60 ≈ 0.0167 seconds. If the frame rate drops to 30 FPS, each frame takes 1 / 30 ≈ 0.0333 seconds. This dt value is precisely the factor we need to make our movement consistent.
Instead of moving a fixed amount per frame, we define a speed in units per second and multiply it by dt to find the correct distance to move in the current frame.
distance_to_move = speed_per_second * delta_time
Let's revisit our example with a desired speed of 2.0 units/second:
- Fast PC (500 FPS):
dtis1/500 = 0.002s. Movement per frame is2.0 * 0.002 = 0.004units. Over one second (500 frames), total movement is500 * 0.004 = 2.0units. - Slower PC (60 FPS):
dtis1/60 ≈ 0.0167s. Movement per frame is2.0 * 0.0167 ≈ 0.0334units. Over one second (60 frames), total movement is60 * 0.0334 ≈ 2.0units.
The result is the same! The object on the slower PC takes bigger steps but less frequently, while the object on the faster PC takes tiny steps much more often. Both cover the same distance in the same amount of real time.
The following video segment explains this concept mathematically.
TIMESTEPS and DELTA TIME | Game Engine series
The Cherno continues by explaining exactly what delta time is and how it works to create frame-rate independent movement.
Watch from 01:57 to 07:58. This part provides a fantastic conceptual and mathematical breakdown of delta time.
3. Implementation: Calculating Delta Time in C
Now, let's implement this. We need a way to measure elapsed time. The GLFW library provides a convenient function for this: glfwGetTime(), which returns the time in seconds since GLFW was initialized.
The logic is simple:
- Keep track of the time when the last frame finished (
lastFrameTime). - At the start of a new frame, get the
currentFrameTime. - Calculate
deltaTime = currentFrameTime - lastFrameTime. - Update
lastFrameTimetocurrentFrameTimefor the next loop iteration.
Here is how you would integrate this into your main C application loop:
// Place these variables outside your main loop
double lastFrameTime = 0.0;
double deltaTime = 0.0;
// Inside your main loop
while (!glfwWindowShouldClose(window)) {
// --- Time logic ---
double currentFrameTime = glfwGetTime();
deltaTime = currentFrameTime - lastFrameTime;
lastFrameTime = currentFrameTime;
// --- Game Logic (using deltaTime) ---
// Example: process input for a moving object
// process_input(window, &camera, deltaTime);
// --- Rendering ---
// ... clear screen, draw objects, etc.
// --- End of Frame ---
glfwSwapBuffers(window);
glfwPollEvents();
}
This short clip shows a similar implementation (in C++, but the logic is identical) within an application's main loop.
TIMESTEPS and DELTA TIME | Game Engine series
This part of the video shows how to calculate delta time in the main loop by tracking the time of the current and previous frames.
Watch from 13:32 to 16:02. Focus on the logic of time - lastFrameTime. The video uses glfwGetTime() just as we will.
4. Applying Delta Time to Game Logic
With deltaTime calculated each frame, you can now update your game logic. Let's imagine you have a camera or player object you want to move based on keyboard input. In your input handling function, you would pass deltaTime and use it to scale the movement.
// Example of an input processing function
void process_input(GLFWwindow* window, Camera* camera, float dt) {
const float moveSpeed = 2.5f; // units per second
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
// Assume camera->Position and camera->Front are vec3s
// We scale the camera's Front vector by the desired speed and delta time
vec3 velocity;
glm_vec3_scale(camera->Front, moveSpeed * dt, velocity);
glm_vec3_add(camera->Position, velocity, camera->Position);
}
// ... handle other keys (S, A, D) similarly
}
// In the main loop:
process_input(window, &myCamera, (float)deltaTime);
This is a form of numerical integration, specifically Euler's method, which you've likely encountered in your engineering courses. If you consider velocity v as the rate of change of position x (v = dx/dt), you can rearrange it to dx = v * dt. Over a small time step Δt, the change in position Δx is approximately v * Δt. Our code, position += velocity * deltaTime, is a direct implementation of this principle.
This final video clip demonstrates the result and confirms that movement speed is now consistent whether VSync is on (capped FPS) or off (uncapped FPS).
TIMESTEPS and DELTA TIME | Game Engine series
Watch this to see the final result of applying delta time to movement speed, demonstrating its effectiveness.
Watch from 18:47 to 25:17. The presenter first shows the fast, inconsistent movement with VSync off, then applies delta time, and shows that the speed is now consistent and controllable regardless of the frame rate.
5. An Important Caveat: When Not to Use Delta Time
Delta time is essential for any process or change that occurs over time, such as movement, rotation, or a regenerating health bar.
However, you should not apply delta time to instantaneous events. These are actions that happen at a single point in time in response to an event.
- A player jumping (the initial impulse is instant, though gravity's effect afterwards is continuous).
- Losing health from a single hit.
- Spawning a projectile when a button is pressed.
Applying dt to these would mean a player with a lower frame rate would jump higher or deal more damage, which is incorrect. This video gives a very clear explanation of this important distinction.
Dear Game Developers, Stop Messing This Up!
Jonas Tyroller provides a memorable rule of thumb for when to use delta time and, just as importantly, when not to.
Watch from 13:54 to 15:45. Pay close attention to the Flappy Snail example, which contrasts continuous movement (which needs dt) with an instantaneous jump (which does not).
Conclusion
You've just implemented one of the most critical components of a game engine's architecture. By making your game logic frame-rate independent, you ensure a consistent and fair experience for all players, regardless of their hardware.
Key Takeaways:
- Relying on a fixed update amount per frame leads to frame-rate dependent logic, where game speed is tied to hardware performance.
- Delta time (
dt) is the duration in seconds between the last frame and the current one. It's the key to frame-rate independence. - You can calculate
dtin your main loop usingglfwGetTime(). - For continuous processes like movement, scale your rates (e.g., speed) by
dt:position += speed * deltaTime. - Do not apply
dtto instantaneous, one-off events.
Preview of the Next Lesson:
The variable time-step we've just implemented is perfect for many types of game logic, but it can introduce instability for physics simulations, where consistency is paramount. Large, sudden spikes in dt (e.g., due to a lag spike) can cause objects to tunnel through walls or cause simulations to "explode".
In our next lesson, we will build upon today's foundation to create a more robust game loop. We will separate a fixed-timestep update loop for physics from a variable-timestep render loop, creating a structure that is both smooth and stable—the standard for modern game engines.