Game Development
Reviewed & published by Brayan K
By the end of this lesson you'll understand the heartbeat of every game — the input/update/render loop — and why movement must be scaled by delta time. You'll write a fixed-timestep loop, a small 2D vector struct, and a tiny entity-component world, all as plain console simulations you can run right here.
Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Run the game loop: input → update → render, every frame
- Scale all movement by delta time so it is frame-rate independent
- Implement a fixed timestep with an accumulator for stable physics
- Write a small 2D vector (Vec2) struct for positions and velocities
- Model entities with the entity-component pattern and simple systems
- Pick a starting engine or library: Unreal, SDL2, SFML, or raylib
💡 Real-World Analogy
Think of a game like an animated flip-book. Each page is a frame. To draw the next page you do three things in order: glance at what the player pressed (input), nudge every character a little (update), then ink the page (render). Flip the pages fast enough and still images become motion. The catch: if one artist flips pages twice as fast as another, the characters would race ahead — unless each nudge is sized by how much time the page took. That time-per-page is delta time, and multiplying movement by it is what keeps the game running the same on a slow laptop and a fast desktop.
1. The Game Loop & Delta Time
A game loop is a loop that runs over and over while the game is alive. Each pass — a frame — does three jobs in the same order: input (what did the player press?), update (move everything forward a little), and render (draw it). Because there is no graphics window here, "render" just prints to the console — but the structure is exactly what Unreal and every other engine uses. Read this worked example and run it.
#include <iostream>
using namespace std;
int main() {
// A game loop does the SAME 3 jobs every frame:
// 1) input 2) update (move things) 3) render (draw)
// We "simulate" it on the console — no graphics library needed.
double playerX = 0.0; // start at the left edge
double speed = 50.0; // 50 PIXELS PER SECOND (not per frame!)
// dt = "delta time" = seconds since the last frame.
// Pretend each frame takes 1/10 of a second (10 frames per second).
double dt = 0.1;
cout << "Frame X-position" << endl;
for (int frame = 1; frame <= 5; frame++) {
// --- INPUT --- (here: always "hold right", so we just move)
// --- UPDATE --- distance = speed * time. dt makes it frame-rate proof.
playerX += speed * dt; // 50 * 0.1 = 5 pixels each frame
// --- RENDER --- (here: print instead of drawing pixels)
cout << " " << frame << " " << playerX << endl;
}
cout << "Moved at 50 px/s for 0.5s total -> 25 px" << endl;
return 0;
}
// Output:
// Frame X-position
// 1 5
// 2 10
// 3 15
// 4 20
// 5 25
// Moved at 50 px/s for 0.5s total -> 25 px
// ⚠️ No expected-output panel for this one, on purpose.
// These numbers are stopwatch readings. They depend on the machine, the
// compiler and what else it is doing, so only the direction of the
// comparison is the lesson — never the figures.
//
// One real run on the machine that builds this site printed:
// Frame X-position
// 1 5
// 2 10
// 3 15
// 4 20
// 5 25
// Moved at 50 px/s for 0.5s total -> 25 pxThe single most important habit in game code: multiply movement by delta time. delta time (dt) is the number of seconds the last frame took. If you instead move by a flat amount per frame, a faster computer renders more frames per second and everything sprints ahead. position += speed * dt turns "pixels per frame" into "pixels per second" so the game behaves identically everywhere. Run this and watch two very different frame rates land on the same answer.
#include <iostream>
using namespace std;
// Two players move at the SAME real speed (50 px/s) but on
// machines with different frame rates. With dt, they end up together.
void run(const string& label, double dt, int frames) {
double x = 0.0;
double speed = 50.0; // pixels PER SECOND
for (int f = 0; f < frames; f++) {
x += speed * dt; // multiply by dt -> time-based, not frame-based
}
cout << label << ": " << frames << " frames, dt=" << dt
<< " -> x=" << x << endl;
}
int main() {
// Both simulate exactly 1 second of game time:
run("Fast PC (60 FPS)", 1.0 / 60.0, 60); // 60 frames of 1/60s
run("Slow PC (10 FPS)", 1.0 / 10.0, 10); // 10 frames of 1/10s
cout << "Both reach x=50 because speed * dt is frame-rate independent." << endl;
return 0;
}
// ✅ Expected output:
// Fast PC (60 FPS): 60 frames, dt=0.0166667 -> x=50
// Slow PC (10 FPS): 10 frames, dt=0.1 -> x=50
// Both reach x=50 because speed * dt is frame-rate independent.Your turn. The program below makes a falling object accelerate under gravity. Fill in the two blanks marked ___ using the // 👉 hints, then run it and check your output against the expected lines.
#include <iostream>
using namespace std;
int main() {
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
double y = 100.0; // start height
double gravity = 200.0; // pixels per second, downward
// 1) Each frame lasts a quarter of a second
double dt = ___; // 👉 0.25
cout << "Frame Y" << endl;
for (int frame = 1; frame <= 4; frame++) {
// 2) Move down by gravity, scaled by dt (frame-rate proof!)
y = y + gravity * ___; // 👉 dt
cout << " " << frame << " " << y << endl;
}
// ✅ Expected output:
// Frame Y
// 1 150
// 2 200
// 3 250
// 4 300
return 0;
}2. Fixed Timestep for Stable Physics
Delta time keeps movement consistent, but raw dt wobbles frame to frame — and wobbly physics means jumps and collisions that behave slightly differently every time. The fix is a fixed timestep: always advance the physics by the same step (commonly 1.0 / 60.0 seconds). An accumulator banks each frame's real time and you run as many fixed steps as fit. A long, stuttery frame simply runs more steps to catch up, so the simulation never drifts.
#include <iostream>
using namespace std;
int main() {
// FIXED TIMESTEP: physics always advances by the SAME step (1/60 s),
// no matter how long a frame really took. An "accumulator" banks the
// leftover time so nothing is lost.
const double STEP = 1.0 / 60.0; // 0.016666... seconds per physics step
double accumulator = 0.0;
int totalSteps = 0;
// Pretend 3 frames arrive with uneven lengths (a stutter mid-game):
double frameTimes[3] = { 0.020, 0.050, 0.010 }; // seconds each
for (int frame = 0; frame < 3; frame++) {
accumulator += frameTimes[frame]; // add this frame's real time
int stepsThisFrame = 0;
// Run as many FIXED steps as fit in the banked time.
while (accumulator >= STEP) {
accumulator -= STEP; // spend one fixed step
totalSteps++;
stepsThisFrame++;
}
cout << "Frame " << frame
<< ": frameTime=" << frameTimes[frame]
<< " steps=" << stepsThisFrame
<< " leftover=" << accumulator << endl;
}
cout << "Total fixed steps run: " << totalSteps << endl;
// A long frame (0.050s) runs MORE steps to catch up -> stable physics.
return 0;
}
// ✅ Expected output:
// Frame 0: frameTime=0.02 steps=1 leftover=0.00333333
// Frame 1: frameTime=0.05 steps=3 leftover=0.00333333
// Frame 2: frameTime=0.01 steps=0 leftover=0.0133333
// Total fixed steps run: 43. A Tiny 2D Vector Struct
Almost everything in a 2D game is an (x, y) pair: a position, a velocity, a direction. Bundling those two numbers into a small Vec2 struct — with + to add and * to scale — makes movement read like the maths you'd write on paper: pos = pos + velocity * dt. The length() (magnitude) of a velocity vector is the object's speed.
#include <iostream>
#include <cmath>
using namespace std;
// A 2D vector = an (x, y) pair. It is the workhorse of game math:
// positions, velocities, and directions are all Vec2 values.
struct Vec2 {
double x = 0, y = 0;
Vec2 operator+(const Vec2& o) const { return { x + o.x, y + o.y }; } // add
Vec2 operator*(double s) const { return { x * s, y * s }; } // scale
double length() const { return sqrt(x * x + y * y); } // magnitude
};
int main() {
Vec2 pos = { 0, 0 };
Vec2 velocity = { 3, 4 }; // moving right + down
double dt = 1.0;
pos = pos + velocity * dt; // move one step: new = old + vel * dt
cout << "Position: (" << pos.x << ", " << pos.y << ")" << endl; // (3, 4)
// length of (3,4) is 5 — the classic 3-4-5 triangle.
cout << "Speed (length of velocity): " << velocity.length() << endl; // 5
return 0;
}
// ✅ Expected output:
// Position: (3, 4)
// Speed (length of velocity): 54. Entities & the Entity-Component Pattern
An entity is "a thing in the world" — a hero, a slime, a coin. Rather than one enormous class that does everything, the entity-component pattern gives each entity a bag of small components (Position, Velocity, Health), and writes systems — plain functions — that loop over every entity and act on the data they care about. One movement system moves all entities; one render system draws them all. It keeps data together and lets you build behaviour by mixing components instead of deep inheritance.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// Entity-component pattern (the simple version):
// an Entity is a bundle of small DATA components. A "system" is just a
// function that loops over every entity and acts on the data it needs.
struct Entity {
string name;
double x = 0, y = 0; // Position component
double vx = 0, vy = 0; // Velocity component
int hp = 100; // Health component
};
// MOVEMENT SYSTEM: update every entity's position from its velocity.
void movementSystem(vector<Entity>& world, double dt) {
for (Entity& e : world) {
e.x += e.vx * dt;
e.y += e.vy * dt;
}
}
// RENDER SYSTEM: "draw" every entity (here, print it).
void renderSystem(const vector<Entity>& world) {
for (const Entity& e : world) {
cout << " " << e.name << " at (" << (int)e.x << ", " << (int)e.y
<< ") HP:" << e.hp << endl;
}
}
int main() {
vector<Entity> world = {
{ "Hero", 0, 0, 60, 0, 100 }, // moves right at 60 px/s
{ "Slime", 100, 50, -20, 0, 30 }, // moves left at 20 px/s
};
double dt = 0.5;
for (int frame = 1; frame <= 2; frame++) {
cout << "Frame " << frame << ":" << endl;
movementSystem(world, dt); // one system updates ALL entities
renderSystem(world); // another system draws them
}
return 0;
}
// ✅ Expected output:
// Frame 1:
// Hero at (30, 0) HP:100
// Slime at (90, 50) HP:30
// Frame 2:
// Hero at (60, 0) HP:100
// Slime at (80, 50) HP:30Now you try. Finish the update step of a side-scrolling ship's game loop — the only blank is the delta-time scaling you learned in Section 1:
#include <iostream>
using namespace std;
int main() {
// 🎯 YOUR TURN — build one game-loop tick for a side-scrolling ship.
double shipX = 0.0;
double speed = 100.0; // pixels per second
double dt = 0.2; // each frame is 0.2 seconds
cout << "Frame ShipX" << endl;
for (int frame = 1; frame <= 3; frame++) {
// --- UPDATE --- move the ship right, scaled by dt
shipX = shipX + speed * ___; // 👉 dt
// --- RENDER --- print the frame and position
cout << " " << frame << " " << shipX << endl;
}
// ✅ Expected output:
// Frame ShipX
// 1 20
// 2 40
// 3 60
return 0;
}🔎 Deep Dive: update rate vs render rate
Real engines separate how often physics updates from how often the screen draws. Physics runs on the fixed timestep (say 60 updates a second); rendering runs as fast as the monitor refreshes. When the two don't line up, you interpolate: draw the object a fraction alpha of the way between its last and next physics positions, so motion looks buttery even if physics ticked slightly before the frame was drawn.
while (running) {
processInput();
accumulator += frameTime;
while (accumulator >= STEP) { // fixed-step physics
update(STEP);
accumulator -= STEP;
}
double alpha = accumulator / STEP; // 0..1 between steps
render(alpha); // interpolate for smoothness
}Pro Tips
- 💡 Always scale by dt: every speed, every acceleration. If a value moves something, it is "per second" and must be multiplied by dt.
- 💡 Clamp huge dt: after a pause or a breakpoint, one frame may report a giant dt. Cap it (e.g. if (dt > 0.1) dt = 0.1;) so objects don't teleport.
- 💡 Keep components tiny: one piece of data each — Position, not PositionVelocityHealth. Small components compose better.
- 💡 Learn the loop before the engine: once the plain-C++ loop here makes sense, raylib or SFML is mostly "the same loop, but draw a sprite instead of cout".
Common Errors (and the fix)
- Frame-rate-dependent movement (no delta time): writing x += speed; moves the object once per frame, so it races on fast PCs and crawls on slow ones. Always scale by time: x += speed * dt;.
- Treating speed as "per frame": a value like 50 is meaningless without a unit. Decide it's pixels per second, then * dt converts it to this frame's movement.
- Integer division in dt: double dt = 1 / 60; gives 0 (integer division), so nothing moves. Write 1.0 / 60.0 so the maths is done in double.
- Spiral of death: if each fixed step takes longer than STEP real seconds, the while (accumulator >= STEP) loop never drains and the game freezes. Cap the steps per frame, or clamp the incoming frame time.
- Forgetting to subtract from the accumulator: leaving out accumulator -= STEP; inside the loop makes it spin forever. Each fixed step must spend one STEP of banked time.
📋 Quick Reference: Engines & Libraries
| Tool | Level | Best for |
|---|---|---|
| raylib | Library (high-level) | Absolute beginners — tiny API, a window and shapes in minutes |
| SFML | Library (mid-level) | Clean C++ classes for 2D graphics, audio, input, networking |
| SDL2 | Library (low-level) | Full control over windows, input, and the render loop |
| Unreal Engine | Full engine (AAA) | 3D/AAA titles — editor, renderer, physics, and tooling built in |
All four are C++ at heart. The loop you wrote above is the same one they run — they just replace cout with drawing a sprite to a window.
Mini-Challenge: Bouncing Ball
No blanks this time — just a brief and an outline to keep you on track. Build a game loop where a ball climbs, hits a ceiling, and falls back down by flipping its velocity. Run it and check your output against the example in the comments.
#include <iostream>
using namespace std;
int main() {
// 🎯 MINI-CHALLENGE: a bouncing ball loop
// 1. Make doubles: y = 0, velocity = 10 (px per second), dt = 1.0.
// 2. Loop 6 frames. Each frame:
// - UPDATE: y = y + velocity * dt;
// - if y reaches 30 or more, flip direction: velocity = -velocity;
// - if y drops to 0 or below, flip direction: velocity = -velocity;
// - RENDER: print "Frame N: y=..."
// 3. Watch the ball climb to ~30, then fall back toward 0.
//
// ✅ Example output (first lines):
// Frame 1: y=10
// Frame 2: y=20
// Frame 3: y=30
// Frame 4: y=20 (velocity flipped to -10)
// your code here
return 0;
}🎉 Lesson Complete
- ✅ The game loop runs input → update → render once per frame, forever
- ✅ Delta time: position += speed * dt makes movement frame-rate independent
- ✅ A fixed timestep + accumulator keeps physics stable on uneven frames
- ✅ A small Vec2 struct powers positions, velocities, and directions
- ✅ The entity-component pattern: entities hold components; systems act on them
- ✅ Start with raylib or SFML, grow into SDL2, then a full engine like Unreal
Practice quiz
What three jobs does a game loop do every frame, in order?
- render, update, input
- update, render, sleep
- input, update, render
- load, save, draw
Answer: input, update, render. Each frame reads input, updates the world a little, then renders (draws) it.
What is delta time (dt)?
- The number of seconds that passed since the last frame
- The total time the game has run
- The frame rate in frames per second
- A fixed constant of 1/60
Answer: The number of seconds that passed since the last frame. dt is the seconds elapsed since the previous frame; multiplying movement by it makes it frame-rate independent.
Why do you write position += speed * dt instead of position += speed?
- It uses less memory
- dt makes the object move faster
- speed alone causes a compile error
- It turns 'pixels per frame' into 'pixels per second' so movement is identical on any frame rate
Answer: It turns 'pixels per frame' into 'pixels per second' so movement is identical on any frame rate. Scaling by dt makes movement time-based, so a fast PC and a slow PC reach the same position.
What is a fixed timestep used for?
- Drawing the screen as fast as possible
- Advancing the physics by the same step every time for consistent, repeatable physics
- Limiting the game to 30 FPS
- Skipping input on slow frames
Answer: Advancing the physics by the same step every time for consistent, repeatable physics. A fixed timestep updates physics with a constant dt (e.g. 1/60 s), so jumps and collisions behave the same on any hardware.
What role does the accumulator play in a fixed-timestep loop?
- It banks each frame's real time so you can run as many fixed steps as fit
- It counts the total score
- It stores the player's position
- It measures the frame rate
Answer: It banks each frame's real time so you can run as many fixed steps as fit. The accumulator banks leftover time; a long frame simply runs more fixed steps to catch up, so nothing drifts.
What does double dt = 1 / 60; produce, and why?
- 0.016667, the correct value
- A compile error
- 0, because 1 / 60 is integer division
- 60, the frame rate
Answer: 0, because 1 / 60 is integer division. 1 / 60 is integer division yielding 0; write 1.0 / 60.0 so the maths is done in double.
For a Vec2 velocity of (3, 4), what does length() return?
- 7
- 5
- 12
- 3.5
Answer: 5. length() is sqrt(3*3 + 4*4) = sqrt(25) = 5 — the classic 3-4-5 triangle.
In the entity-component pattern, what is a 'system'?
- A single giant Player class
- The operating system
- A type of component
- A function that loops over every entity and acts on the data (components) it needs
Answer: A function that loops over every entity and acts on the data (components) it needs. Systems are plain functions (like a movement or render system) that operate on every entity holding the right components.
What is the 'spiral of death' in a fixed-timestep loop?
- When the player loses all lives
- When each fixed step takes longer than STEP real seconds, so the accumulator never drains and the game freezes
- When dt becomes negative
- When the game runs too fast
Answer: When each fixed step takes longer than STEP real seconds, so the accumulator never drains and the game freezes. If steps can't keep up, the while loop never drains the accumulator; cap the steps per frame or clamp the frame time.
Which library is recommended as the gentlest starting point for a beginner?
- Unreal Engine
- SDL2 only
- raylib or SFML
- Vulkan
Answer: raylib or SFML. raylib and SFML have small APIs that get a window and shapes on screen fast; learn the loop first, then grow into bigger tools.
Continue this course
- Previous: Memory Pools, Arenas & Custom Memory Management Techniques
- Next: Creating & Using C++ Libraries (Static, Shared, Cross-Platform) — Build, link, and distribute static and shared C++ libraries cross-platform
- Quick reference: C++ cheat sheet
- From the blog: C++ vs Java: Which Should You Learn First?
Frequently asked questions
What is the game loop and why does every game need one?
A game loop is a loop that never stops while the game runs. Each pass — called a frame — it does three jobs in order: read input, update the world a tiny bit, then draw. Without it, the screen would freeze the instant your code stopped running. Every engine from Unreal to a homemade Pong has one beating at its core.
What is delta time and why must I multiply movement by it?
Delta time (dt) is how many seconds passed since the last frame. If you move an object by a flat amount each frame, it goes faster on a fast computer and slower on a slow one. Multiplying speed by dt — position += speed * dt — turns 'pixels per frame' into 'pixels per second', so movement looks identical on every machine.
What is a fixed timestep and when do I need one?
A fixed timestep updates the physics with the same dt every time (for example exactly 1/60 of a second), using an accumulator to bank leftover time. You need it whenever consistent, repeatable physics matters — jumps, collisions, networked play — because variable dt can make the same input produce different results on different hardware.
What is the entity-component pattern in one sentence?
Instead of one giant Player class that does everything, you give an entity a bag of small data pieces — a Position component, a Velocity component, a Health component — and write systems that act on every entity that has the right pieces. It keeps related data together and lets you mix and match behaviour by composition rather than deep inheritance.
Which engine or library should a beginner start with?
For learning the fundamentals from scratch, raylib or SFML are the gentlest — small APIs, instant window-and-shape on screen. SDL2 is lower-level and great once you want full control. Unreal Engine is a full AAA toolkit: powerful, but a lot to swallow on day one. Learn the loop in plain C++ first (as in this lesson), then a small library, then a big engine.