Game Loop
Summary
The game loop is the continuously repeating cycle that keeps a game running: read input, update the world, render a frame, then repeat — dozens of times per second, for as long as the game is open. Every visible behavior in a game, from a character moving to a menu animating, happens because some code runs inside this loop, once per iteration. Understanding the game loop is understanding what a "running game" actually is underneath the graphics.
Why This Matters
A program that runs once and stops — read some input, compute a result, print it, exit — cannot be a game, because a game has to keep reacting to a player continuously, for an unpredictable and open-ended amount of time. Without a loop structuring that repetition, there would be no consistent place to check "has the player pressed a key," "where should the enemy be now," or "what should be drawn this instant" — and no guarantee those checks happen often enough to feel responsive. The game loop exists to give every one of those responsibilities a predictable, repeating home.
Mental Model
The game loop is a flipbook being redrawn and reflipped, not a single painting. A single static picture can't show motion — animation comes from showing many slightly-different pictures in rapid succession. A game loop does exactly this: each iteration ("frame") recalculates the world's state —a little bit further along than last time — and redraws it, and doing this fast enough (dozens of times per second) creates the illusion of smooth, continuous motion, even though it's actually a rapid sequence of discrete steps.
Core Explanation
- Three broad phases repeat every iteration: process input, update simulation, render. Exact naming and ordering vary by engine, but every game loop reads the current input state, advances game logic based on it (and on time passing), then draws the result.
- The loop runs until the game explicitly stops. Unlike a normal function that returns once its work is done, a game loop's "work" is never finished while the game is running — it's designed to run indefinitely, once per frame, until shutdown.
- Modern engines hide the raw loop and expose lifecycle callbacks
instead. You rarely write
while (true) { ... }yourself in Unity or Unreal — the engine owns the actual loop and calls your code (Update,Tick) at the right point inside it every iteration. - Update and render are conceptually separate steps, even when they
appear to happen "at the same time." Game logic (
Update) computing where things should be, and rendering (drawing the current state to the screen) are distinct responsibilities — some engines even run them on different schedules (see [[CON-0034]] and fixed-timestep discussions).
Syntax / Theory
Engine-neutral (pseudocode)
Initialize()
while (gameIsRunning)
{
ProcessInput()
Update(deltaTime) // Advance simulation state.
Render() // Draw the current state.
}
Shutdown()
C# (Unity)
public class EnemyAI : MonoBehaviour
{
void Update() // Called once per frame by Unity's own game loop.
{
MoveTowardPlayer();
}
}
C++ (Unreal)
void AEnemyCharacter::Tick(float DeltaTime) // Called once per frame by Unreal's loop.
{
Super::Tick(DeltaTime);
MoveTowardPlayer(DeltaTime);
}
Both Update() and Tick() are the engine handing control to your code at
a fixed point inside its own internally-managed game loop — you never see
the while loop itself, but every frame, it calls back into your code
exactly once.
Practical Example
A simple countdown timer displayed on screen has to be recalculated and redrawn every frame, not computed once.
public class CountdownTimer : MonoBehaviour
{
public float secondsRemaining = 60f;
void Update()
{
secondsRemaining -= Time.deltaTime; // Advance state, once per loop iteration.
timerText.text = Mathf.CeilToInt(secondsRemaining).ToString();
if (secondsRemaining <= 0)
OnTimeUp();
}
}
The timer only counts down because Update runs repeatedly — remove the
loop, and secondsRemaining would never change after the first read.
Common Mistakes
- Doing one-time setup work inside a per-frame method. Loading an
asset or instantiating an object inside
Update()/Tick()re-runs that work every single frame instead of once — one-time work belongs in an initialization callback (Start()/Awake()/BeginPlay()), not the loop body. - Assuming the loop runs at a fixed, guaranteed rate. Frame timing varies (see [[CON-0033]]) — code that assumes "this runs exactly 60 times per second" breaks on slower hardware or under heavy load unless it explicitly accounts for variable frame timing.
- Blocking the loop with a long-running operation. A frame that performs a slow file load or synchronous network call freezes the entire game for that duration, because nothing else in the loop — input, rendering — can proceed until that frame finishes.
- Forgetting that rendering shows the last completed update, not the
in-progress one. Code that expects visual feedback to reflect a change
made mid-
Update()before that same frame finishes won't see it — rendering happens after update completes for that iteration.
Game Development Example
A day/night cycle system advances gradually every frame rather than jumping instantly between states.
public class DayNightCycle : MonoBehaviour
{
public float dayLengthInSeconds = 300f;
private float timeOfDay; // 0 to 1, representing one full day.
void Update()
{
timeOfDay += Time.deltaTime / dayLengthInSeconds;
timeOfDay %= 1f;
UpdateSunRotation(timeOfDay);
UpdateAmbientLight(timeOfDay);
}
}
Every frame nudges timeOfDay forward slightly and immediately reflects
that change in lighting — this constant re-evaluation, frame after frame,
is the game loop doing its job.
Best Practices
- Put one-time setup in initialization callbacks, and only genuinely
per-frame logic inside
Update/Tick. - Never assume a fixed number of iterations per second — use elapsed time (see [[CON-0034]]) for anything that should behave consistently across different frame rates.
- Keep per-frame work as cheap as possible — anything expensive inside the loop runs every single frame, and its cost compounds directly into frame rate.
- Avoid blocking operations (synchronous file/network access) inside the loop; use asynchronous or background-loaded alternatives instead.
Exercises
- Write a
BlinkMonoBehaviour/Actorthat toggles a sprite's visibility every 0.5 seconds usingUpdate, and explain which part of your code runs once versus every frame. - Take the
CountdownTimerexample and move thetimerText.text = ...assignment out ofUpdate()intoStart()by mistake — run it and describe, in terms of the game loop, why the displayed time never changes. - You're building a loading screen that needs to display a spinning icon while a large asset loads synchronously. Explain, in terms of the game loop, why the spinner will freeze during the load, and propose one way to fix it.
- Research "fixed timestep" vs. "variable timestep" game loops and explain, in your own words, why physics simulation code commonly runs in a separate, fixed-rate loop instead of the same loop as rendering.
Related Concepts
- Frame Rate — the game loop's iteration speed, measured in iterations (frames) per second, and the direct consequence of how much work each loop iteration does.
- Delta Time — the elapsed-time value the loop hands to update logic each iteration, letting behavior stay consistent regardless of how fast the loop is actually running.
- Input Systems — reading input is one of the game loop's core phases, checked once per iteration alongside updating and rendering.
Further Reading
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Unity documentation — Order of Execution for Event Functions: https://docs.unity3d.com/Manual/ExecutionOrder.html
- Unreal Engine documentation — Actor Ticking: https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-ticking-in-unreal-engine