Lesson 20 • Advanced
Game Development in C++
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.
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.
Worked example: one game-loop tick
Five frames of input → update → render, with movement scaled by dt.
#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).
...The 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.
Worked example: why delta time matters
Same real speed on a 60 FPS and a 10 FPS machine — both reach x=50.
#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
...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.
🎯 Your turn: scale gravity by delta time
Fill in the ___ blanks so the object falls the same on any frame rate.
#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 * ___;
...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.
Worked example: accumulator + fixed steps
Uneven frame times, but physics always advances by a constant 1/60 s step.
#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 }
...3. 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.
Worked example: a Vec2 struct
Add and scale 2D vectors, and measure length with the 3-4-5 triangle.
#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() {
...4. 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.
Worked example: entities, components & systems
A movement system and a render system act on a vector of entities.
#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'
...Now 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:
🎯 Your turn: finish the game-loop tick
Fill in the ___ so the ship moves at a steady 100 px/s.
#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 p
...🔎 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, notPositionVelocityHealth. 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
50is meaningless without a unit. Decide it's pixels per second, then* dtconverts it to this frame's movement. - Integer division in dt:
double dt = 1 / 60;gives0(integer division), so nothing moves. Write1.0 / 60.0so the maths is done indouble. - Spiral of death: if each fixed step takes longer than
STEPreal seconds, thewhile (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 oneSTEPof 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.
Frequently Asked Questions
Q: 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.
Q: 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.
Q: 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.
Q: 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.
Q: 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.
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.
🎯 Mini-Challenge: bouncing ball loop
Write the update logic yourself: move by velocity * dt, then flip at the edges.
#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, t
...🎉 Lesson Complete
- ✅ The game loop runs
input → update → renderonce per frame, forever - ✅ Delta time:
position += speed * dtmakes movement frame-rate independent - ✅ A fixed timestep + accumulator keeps physics stable on uneven frames
- ✅ A small
Vec2struct 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
- ✅ Next lesson: C++ Libraries — pulling in and using external code
Sign up for free to track which lessons you've completed and get learning reminders.