Skip to content
Lesson 4.1Game State and Bounded Decisions

Game Engines: Input, Simulation, and Rendering

Follow input, movement, collisions, timers, and drawing through an engine; distinguish live state from the code and files that produce it.

Lesson 4.1
Read or listenPrint chapter or book
About 35 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 4.1Key ideas, reference and questions

A one-page summary of “Game Engines: Input, Simulation, and Rendering” you can keep beside your notes. Plain Markdown.

Lesson 4.1 — Game Engines: Input, Simulation, and Rendering: cheatsheet

  • Remember: Changing one live unit affects that instance. Changing its type definition affects units created from it after the edited definition is loaded.
  • Also useful: A library is a toolbox you reach into when you need it. An engine is more like a stage manager who calls your cue when it is your turn.

Key terms

  • game engine: A game engine is that reusable machinery: the software that runs the loop, draws the frames, loads the files, and manages the objects, on which a particular game's content and behavior—such as what a hit does or what a unit costs—are built.
  • vertices: A renderer's basic shape is a triangle joined from three corner points called vertices.
  • framework: Outside games, software that works this way is called a framework.
  • Game time: Game time is the clock used for simulated movement and cooldowns.
  • diagnostic: A diagnostic is a named measurement with a short history, such as frame time over the last 120 frames, that can be switched on and off while the game runs.
  • spawned: A scene usually lives in a file (a data format, Chapter 10) and is spawned at run time: the engine reads the file, creates the entities, and fills in the components.

Formulas and patterns

  • move = 150 × (1/60) = 2.5 pixels; new x = 794 + 2.5 = 796.5; ball size = 16 pixels wide, so its right edge is 796.5 + 8 = 804.5; the wall is at x = 800, and 804.5 is past it, so the ball hits the wall; reply = velocity x becomes −150, and x is moved back to 800 − 8 = 792

Review

  • Q: In Wesnoth you change one unit's hit points in memory. You then recruit another unit of the same type. What hit points does the new unit have, and why? A: The WML file's value, because each new unit is built from the definition on disk
  • Q: Which description best fits a game engine? A: Reusable machinery that runs the loop, draws frames, loads files, and manages objects
Download cheatsheet (.md)

The previous chapters followed values into instructions and turned remote bytes into typed objects and collections. The next question is larger: which system moves them, resolves collisions, applies damage, and advances timers? A health update might be native code, a cooldown calculation a script, and a unit’s starting health loaded from a data file. Finding the relevant function or definition starts with separating game-specific behavior from the engine services that execute it.

The game loop reads input, checks whether actions are allowed, updates positions and health, and draws the result. Games need other machinery too: something loads the level, stores objects, and calls the renderer. A game engine supplies much of this reusable machinery. Knowing where it stops and the game begins tells you which technique in this book will work on a given problem.

Many games need the same kinds of machinery, even when their weapons, maps, and art are different:

  • a game loop that reads input, updates the world, and draws a frame, over and over (Lesson 1.3);
  • a renderer that turns the world into pixels;
  • a way to load art, sound, levels, and settings from disk;
  • a model of the world: what objects exist, where they are, what they hold;
  • collision, when objects can meet walls or one another;
  • sound, input, menus, and sometimes networking and scripting.

Not every game needs every item: a text adventure may have no renderer or collision system. For games that do need these pieces, reusing them can save substantial work. A game about tanks and a game about farming can share the window, input, loading, and rendering machinery while supplying different behavior: armor damage for tanks, crop growth for farming.

A game engine is that reusable machinery: the software that runs the loop, draws the frames, loads the files, and manages the objects, on which a particular game’s content and behavior—such as what a hit does or what a unit costs—are built.

A renderer’s basic shape is a triangle joined from three corner points called vertices. Rasterization finds the pixels the triangle covers, and a pixel stage then gives each of those pixels its colour:

Which pixels does one triangle cover?Vertices A (0, 0) and B (8, 0) sit on the top edge of an 8 × 5 pixel screen. Move vertex C; a pixel is a candidate when its centre lies inside triangle ABC.

Vertices, then the pixels they cover

Three vertices joined into one triangle

Vertices
A (0, 0)
B (8, 0)
C (0, 5)

Pixel centres: filled = inside the triangle

x 0x 7

20 of 40 pixel centres lie inside triangle ABC. Those 20 pixels are the candidates; the pixel stage gives each a colour, and the other 20 keep the background.

A spreadsheet program is a useful comparison, because it holds up when you push on it. The program supplies the grid, the formulas, and the recalculation; each spreadsheet file supplies its own numbers and formulas. A bug in how the program recalculates breaks every spreadsheet you open. A wrong formula in one file breaks only that file. And two files made in the same program share the same structure, so once you understand one, the next is easier to read. All three statements hold for engines and games too, which is exactly what makes the split worth learning.

Games also use libraries. A useful first distinction is who controls the main loop and calls whose code.

Your code calls a library. SDL, for example, is a library that Wesnoth’s engine uses to open a window, read the keyboard, and play sound. It has no idea what a unit or a map is; it waits to be asked.

An engine often calls your code. It may own the loop and call the game’s update and draw functions at the moments it chooses. The game is a guest in a loop the engine runs. Those update and draw functions are callbacks: the game hands them to the engine, and the engine decides when to run them.

This is a way to reason about control flow, not a rigid test for what a project may call an engine. Engine code can call libraries, and game code can call engine functions too.

Outside games, software that works this way is called a framework. A web framework owns the loop that waits for requests and calls the handler you registered for each address, so the same question — who calls whom — sorts those tools too.

every frame, calls
every frame, calls
calls when needed
calls when needed
Engine owns the loop
Game update code
Game draw code
Libraries: window, input, audio

This matters later in a practical way. A repeated call near frame presentation can be a useful meeting point when you verify that the game actually reaches it. That is why Lesson 7.10 hooks Present: this game’s rendering path calls it to show finished frames, making it a good place to observe them.

Before the first frame, the application starts up. It reads its settings from a file, such as the resolution, the volume, and the key bindings. It creates the window, and it sets up each part of the engine. Engines package those parts as plugins. A plugin is a module that registers what it needs, such as its systems and the event types it uses, with the application when it is added, and a plugin group adds several together, such as the window, the input, the renderer, and the audio. The game itself is then a small plugin of its own, and swapping one plugin, say for a different renderer, changes one part without touching the rest. Changed settings are written back to the file, so they persist the way a save does (Chapter 10).

An engine that owns the loop does the same few jobs on every pass through it, in the same order:

  1. Gather input. Ask the operating system which keys, buttons, and mouse movements happened since the last pass, and turn them into actions (Lesson 1.3).
  2. Update the world. Move objects, resolve contacts, apply damage, count timers down, and choose character actions.
  3. Draw. Build the picture of the world as it is now.
  4. Present. Hand the finished picture to the display, and, with vertical sync on, wait for the display’s next refresh.
input
keys, mouse, pad
update
movement, damage, timers
render
draw the world
present
show the frame

Frame time and frame rate are reciprocals of each other:

tframe=1000fps mst_{\text{frame}} = \frac{1000}{\text{fps}}\ \text{ms}

A display that refreshes 60 times each second takes a new picture every 1 ÷ 60 = 0.01667 seconds, which is 16.67 milliseconds. All four jobs must fit in that frame budget, or the display refreshes before the new picture is ready and shows the old one again. A frame that takes 25 ms gives 1 ÷ 0.025 = 40 frames per second; one that takes 8.33 ms gives 120. This is why tools report frame time, in milliseconds, as well as frames per second. Frame times add up, so a 4 ms update plus a 10 ms draw is a 14 ms frame, but frame rates do not add.

Lesson 1.3 showed that movement needs elapsed time: distance = speed × time. An engine measures the delta time, the seconds since the previous frame, and hands it to every update. Physics prefers steady steps, though. If one frame moves a fast ball 3 pixels and the next moves it 30, a contact test that looks only at positions can jump the ball clean over a wall 10 pixels thick. So many engines run physics in a fixed timestep, such as 1/60 second, and carry the leftover real time into the next frame:

ExampleThree frames of fixed steps

Frame Real time since the last frame (ms) Time waiting to be simulated (ms) Fixed steps run (16.67 ms each) Left over (ms)
1 20.00 0 + 20.00 = 20.00 1 20.00 − 16.67 = 3.33
2 20.00 3.33 + 20.00 = 23.33 1 23.33 − 16.67 = 6.67
3 30.00 6.67 + 30.00 = 36.67 2 36.67 − 33.33 = 3.33

After 70 ms of real time the game has run 4 steps, which is 4 × 16.67 = 66.67 ms of game time, and 3.33 ms is waiting. The two add back to 70 ms, so no time was lost. A slow frame runs several steps to catch up, and a quick one runs none.

A game loop turning frames into fixed steps. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

STEP = 1000 / 60;  waiting = 0  # ms of game time per step
each frame, real_ms has passed:
    waiting = waiting + real_ms
    while waiting >= STEP:
        run_one_step()  # rules advance by STEP
        waiting = waiting - STEP
    draw()  # blend by what is left

After 70.00 ms of real time the game ran 4 steps, which is 66.67 ms of game time, with 3.33 ms waiting. 66.67 + 3.33 = 70.00 ms, so no time was lost.

Final values: STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 4; frame = 3; real_ms = 30.00 ms; game_time = 66.67 ms; blend = 20%.

Read every step of the lesson’s example
  1. Step 1, line 1. One rules step is 1000 / 60 = 16.67 ms of game time. Nothing is waiting to be simulated yet.

    STEP = 16.67 ms; waiting = 0.00 ms; steps_run = 0.

  2. Step 2, line 3. Frame 1 arrives 20.00 ms after the last one. Add it to the time already waiting: 0.00 + 20.00 = 20.00 ms.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 0; frame = 1; real_ms = 20.00 ms.

  3. Step 3, line 4. 20.00 is at least 16.67, so there is a whole step to run.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 0; frame = 1; real_ms = 20.00 ms.

  4. Step 4, line 5. Run the rules for one step. That is 1 step so far, which is 16.67 ms of game time.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 1; frame = 1; real_ms = 20.00 ms; game_time = 16.67 ms.

  5. Step 5, line 6. Take that step's time out of the waiting pile: 20.00 - 16.67 = 3.33 ms.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 1; frame = 1; real_ms = 20.00 ms; game_time = 16.67 ms.

  6. Step 6, line 4. 3.33 is less than 16.67, so no more whole steps fit in this frame.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 1; frame = 1; real_ms = 20.00 ms; game_time = 16.67 ms.

  7. Step 7, line 7. Draw the frame. 3.33 ms is still waiting, which is 20% of the way to the next step, so many engines blend the last two steps by that fraction.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 1; frame = 1; real_ms = 20.00 ms; game_time = 16.67 ms; blend = 20%.

  8. Step 8, line 3. Frame 2 arrives 20.00 ms after the last one. Add it to the time already waiting: 3.33 + 20.00 = 23.33 ms.

    STEP = 16.67 ms; waiting = 23.33 ms; steps_run = 1; frame = 2; real_ms = 20.00 ms; game_time = 16.67 ms; blend = 20%.

  9. Step 9, line 4. 23.33 is at least 16.67, so there is a whole step to run.

    STEP = 16.67 ms; waiting = 23.33 ms; steps_run = 1; frame = 2; real_ms = 20.00 ms; game_time = 16.67 ms; blend = 20%.

  10. Step 10, line 5. Run the rules for one step. That is 2 steps so far, which is 33.33 ms of game time.

    STEP = 16.67 ms; waiting = 23.33 ms; steps_run = 2; frame = 2; real_ms = 20.00 ms; game_time = 33.33 ms; blend = 20%.

  11. Step 11, line 6. Take that step's time out of the waiting pile: 23.33 - 16.67 = 6.67 ms.

    STEP = 16.67 ms; waiting = 6.67 ms; steps_run = 2; frame = 2; real_ms = 20.00 ms; game_time = 33.33 ms; blend = 20%.

  12. Step 12, line 4. 6.67 is less than 16.67, so no more whole steps fit in this frame.

    STEP = 16.67 ms; waiting = 6.67 ms; steps_run = 2; frame = 2; real_ms = 20.00 ms; game_time = 33.33 ms; blend = 20%.

  13. Step 13, line 7. Draw the frame. 6.67 ms is still waiting, which is 40% of the way to the next step, so many engines blend the last two steps by that fraction.

    STEP = 16.67 ms; waiting = 6.67 ms; steps_run = 2; frame = 2; real_ms = 20.00 ms; game_time = 33.33 ms; blend = 40%.

  14. Step 14, line 3. Frame 3 arrives 30.00 ms after the last one. Add it to the time already waiting: 6.67 + 30.00 = 36.67 ms.

    STEP = 16.67 ms; waiting = 36.67 ms; steps_run = 2; frame = 3; real_ms = 30.00 ms; game_time = 33.33 ms; blend = 40%.

  15. Step 15, line 4. 36.67 is at least 16.67, so there is a whole step to run.

    STEP = 16.67 ms; waiting = 36.67 ms; steps_run = 2; frame = 3; real_ms = 30.00 ms; game_time = 33.33 ms; blend = 40%.

  16. Step 16, line 5. Run the rules for one step. That is 3 steps so far, which is 50.00 ms of game time.

    STEP = 16.67 ms; waiting = 36.67 ms; steps_run = 3; frame = 3; real_ms = 30.00 ms; game_time = 50.00 ms; blend = 40%.

  17. Step 17, line 6. Take that step's time out of the waiting pile: 36.67 - 16.67 = 20.00 ms.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 3; frame = 3; real_ms = 30.00 ms; game_time = 50.00 ms; blend = 40%.

  18. Step 18, line 4. 20.00 is at least 16.67, so there is a whole step to run.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 3; frame = 3; real_ms = 30.00 ms; game_time = 50.00 ms; blend = 40%.

  19. Step 19, line 5. Run the rules for one step. That is 4 steps so far, which is 66.67 ms of game time.

    STEP = 16.67 ms; waiting = 20.00 ms; steps_run = 4; frame = 3; real_ms = 30.00 ms; game_time = 66.67 ms; blend = 40%.

  20. Step 20, line 6. Take that step's time out of the waiting pile: 20.00 - 16.67 = 3.33 ms.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 4; frame = 3; real_ms = 30.00 ms; game_time = 66.67 ms; blend = 40%.

  21. Step 21, line 4. 3.33 is less than 16.67, so no more whole steps fit in this frame.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 4; frame = 3; real_ms = 30.00 ms; game_time = 66.67 ms; blend = 40%.

  22. Step 22, line 7. After 70.00 ms of real time the game ran 4 steps, which is 66.67 ms of game time, with 3.33 ms waiting. 66.67 + 3.33 = 70.00 ms, so no time was lost.

    STEP = 16.67 ms; waiting = 3.33 ms; steps_run = 4; frame = 3; real_ms = 30.00 ms; game_time = 66.67 ms; blend = 20%.

What is drawn after a frame is usually partway between two steps. The 3.33 ms left over is 3.33 ÷ 16.67 = 20% of the way to the next step, and many engines blend the last two steps by that fraction, so motion looks smooth even though the simulation advances in fixed steps (Lesson 4.4 shows how that blend is computed).

This tells you something when you scan memory. A value the simulation changes moves once per step, and a value the drawing code keeps (an on-screen animation timer, an interpolated position) can change once per frame. Counting how often a value changes tells you which loop owns it.

A cooldown is a timer built the same way. When an ability is used, the game stores how long remains, say 5.0 seconds, and subtracts the delta time on every step: after 60 steps of 1/60 second the value has fallen by 1.0 to 4.0, and after 300 steps it reaches 0 and the ability is ready. Some games instead store the moment it becomes ready (now + 5.0) and compare it with the clock. Either way the cooldown is a number in memory that you can scan for.

The delta time an update receives is not always the time that really passed. Engines keep two clocks. Real time is the wall clock: it never stops, and a loading spinner or a menu animation uses it. Game time is the clock used for simulated movement and cooldowns. Each frame the engine takes the real time since the last frame, limits it to a maximum, and multiplies it by a time scale:

Δgame=min⁡(Δreal, Δmax⁡)×s\Delta_{\text{game}} = \min(\Delta_{\text{real}},\ \Delta_{\max}) \times s

A scale of 1 is normal speed, 0.5 is slow motion, 2 is fast forward, and 0 is a pause. A pause stops game time but not real time, which is why a paused game still draws its menu and answers the mouse. The scale also stretches every timer that counts game time:

ExampleOne 16.67 ms frame at four time scales

Time scale Game time added by the frame A 5.0 s cooldown ends after
1 (normal) 16.67 ms 5.0 ÷ 1 = 5.0 s of real time
0.5 (slow motion) 16.67 × 0.5 = 8.33 ms 5.0 ÷ 0.5 = 10.0 s
2 (fast forward) 16.67 × 2 = 33.33 ms 5.0 ÷ 2 = 2.5 s
0 (paused) 0 ms never, while paused

The limit bounds the simulation time added after a stalled frame. A laptop that sleeps, or a window held still by the mouse for 3 seconds, would otherwise hand the next update a delta of 3.0 s. A ball moving 150 pixels a second would jump 150 × 3.0 = 450 pixels at once, straight through any wall, and a fixed-step loop would owe 3.0 ÷ (1/60) = 180 steps. Bevy’s default limit is 250 ms, so the longest delta any frame can add is 0.25 s, and at 60 steps a second the loop owes at most 0.25 ÷ (1/60) = 15 steps.

real time since
the last frame
limit to a maximum
(250 ms in Bevy)
multiply by the
time scale (0 = paused)
game time added
this frame
fixed steps owed:
game time ÷ step length

The limit also ends a failure called the spiral of death. Suppose each fixed step takes 20 ms of real time to compute but advances game time by only 16.67 ms. While the engine computes one step, the real clock moves 20 ms, so the next frame owes 20 ms of game time, which is 20 ÷ 16.67 = 1.2 times as much as the step paid off. The frame after that owes 1.2 times as much again. Each step leaves the game 20 − 16.67 = 3.33 ms further behind real time, so after 60 steps it is 60 × 3.33 = 200 ms behind, and running more steps to catch up only adds more debt. The clamp throws the excess away, so the game falls behind real time and runs in slow motion instead of freezing.

Scanning memory is easier with this in mind. A game’s elapsed time may exist twice: one value that always rises, and one that stops during a pause. A cooldown that compares itself with the second pauses with the game, and one that compares itself with the first keeps running behind the pause menu.

Reading a 3 MB file can take 30 ms. If the update waited for it, the game would miss 30 ÷ 16.67 = 1.8, so two, refreshes. Engines give slow jobs, such as loading files, decoding sound, or planning a path across a big map, to worker threads (Lesson 6.6) as tasks. The main loop starts a task and keeps running frames. When the task finishes, it sends its result through a channel, a queue that one thread fills and another empties, and the main loop checks the channel once per frame.

Worker threadMain loopWorker threadMain loopframes 1, 2, and 3 keep runningstart the task "load level"result, sent through a channelframe 4 checks the channel and uses the result

The result arrives on a frame nobody can predict, so the game has to handle “not ready yet” with a loading screen or a stand-in. Lesson 6.11 uses the same channel to move records from a game to a recorder without stalling it.

Engines measure themselves. A diagnostic is a named measurement with a short history, such as frame time over the last 120 frames, that can be switched on and off while the game runs. An FPS overlay draws such numbers on the screen. A log records events as lines of text, each with a severity such as error, warning, info, or debug, to a console or a file.

An average can hide a stutter. Four frames of 16, 16, 16, and 66 ms average (16 + 16 + 16 + 66) ÷ 4 = 28.5 ms, or 1000 ÷ 28.5 = 35 frames per second, even though three frames in four were fine. The 66 ms frame is the one a player feels, so a good diagnostic also reports the slowest frame.

The build changes the measurement as much as the code does. A debug build leaves out most optimizations so that mistakes are easier to find, and engine code that loops over thousands of entities can run many times slower in it than in a release build. Suppose a system takes 0.4 ms in a release build and 12 ms in a debug build. That is 12 ÷ 0.4 = 30 times slower, and 12 ms alone is 12 ÷ 16.67 = 72% of the frame budget. A frame time means little until you know which kind of build produced it.

Lesson 1.3 gave things identities, and Lesson 3.10 showed an engine keeping each kind of field in its own collection. Many engines make that the whole design. In an entity component system (ECS):

  • an entity is only an identity, a number (with a generation, so a reused number is told apart from the old one, as Lesson 3.10 explained);
  • a component is a plain bundle of data attached to an entity, such as Position, Velocity, or Health;
  • a system is a function that runs on every update for all the entities that have a particular set of components.

A movement system says: for every entity with both Position and Velocity, add velocity × delta time to position. A wall has a Position but no Velocity, so the movement system never touches it, and nobody wrote an “if wall” test. What an entity does comes from which components it has, not from what kind of object it is.

Entity Components Movement system Collision system Draw system
tank Position, Velocity, Size, Sprite runs runs runs
wall Position, Size, Sprite skips runs runs
coin Position, Size, Sprite, Pickup skips runs runs
hidden trigger Position, Size skips runs skips

In memory, each kind of component sits in its own run. Ten thousand Position components of two 4-byte floats each take 10,000 × 8 = 80,000 bytes in one stretch, which is what Lesson 3.10 called a component pool. A system walks that stretch from start to end, and a scan finds the whole run at once.

Entity updates also need coordination:

  • A system should not add or remove entities while it is looping over them, or the loop could skip some and visit others twice. Instead it queues commands (spawn this, despawn that, add this component), and the engine applies them together at a safe moment between systems. A delayed command is one the engine holds back until a timer ends, such as removing an effect after 2 seconds.
  • Disabling an entity hides it from the systems without deleting it. A marker component such as Disabled makes the queries skip it. Its identity and data stay in memory, so it can come back unchanged. Despawning is different: the identity is freed and may be reused.
  • An observer is code that runs when something happens, such as an entity being created, a component being removed, or “coin 42 was collected” (the event from Lesson 1.3), instead of checking every frame whether it happened.

Lesson 6.8 follows how systems share data through resources and events, how the engine decides which system runs first and which can run at the same time, and how it notices only what changed.

Once the world is made of entities, the engine needs a way to create many of them at once. A scene is a group of entities with their components that can be created together: a level, a menu screen, or one character with all its parts. A scene usually lives in a file (a data format, Chapter 10) and is spawned at run time: the engine reads the file, creates the entities, and fills in the components.

Entities in a scene can form a hierarchy. A tank can have a child for its turret and a grandchild for the gun barrel. A child’s position is stored relative to its parent, so moving the tank moves the turret and the barrel with it, and despawning the tank despawns them too. Lesson 7.1 works out how a child’s position becomes a world position.

Games also organize scenes into states: a menu, a loading screen, the match, the game-over screen. Entering a state spawns its scene, and leaving it despawns it. A loading screen waits for every file the next scene needs. It counts the files still loading, and when the count reaches 0 it switches to the match. This matters when you hunt for addresses: the entities of one scene are freed when the scene is left, so an address found in one level can mean nothing in the next.

The games in this book cover most of the ways a game can relate to its engine:

Game Engine Typical places to inspect behavior and configuration
Wesnoth 1.14.9 its own engine, written for this game C++ engine code, WML data files, Lua scripts
AssaultCube 1.2.0.2 Cube, an open-source engine C++ engine code
Urban Terror 4.3.4 ioquake3, the continuation of the open-sourced Quake III engine C++ engine code
Flare 1.12 Flare, an engine built to run content packages text data files
Wyrmsun 5.0.1 Wyrmgus, a fork of the Stratagus strategy engine Lua scripts and data

Flare shows the split most clearly. Flare is the engine; a campaign such as the Empyrean Campaign from Lesson 10.2 is a content package the engine loads. Swap the content package and the same executable runs a different game.

A spending calculation is implemented in code; a price or starting health value may come from a data file or script. Identify that source before changing a live value. It tells you which technique applies and when the game might replace your edit with a newly calculated or loaded value.

Wesnoth shows two of them side by side:

Gold during a match

A number in memory, updated by native engine code. Change it with the scan from Lesson 1.9 and it changes for this match. Start a new match and it begins from the scenario’s starting gold again.

A unit's hit points

They come from a unit definition in a WML data file. Change one unit’s hit points in memory and that single unit changes. Recruit another of the same type and it arrives with the file’s value, because every new unit is built from the definition on disk. Change the definition file instead, and units created after the game reloads that edited definition use the new value.

affects units created
after definition reload
each recruit is
built from it
changes only this unit
your file edit
unit type in a WML file:
defines its hit points
unit in memory
unit in memory
the next recruit
your memory edit

Choose according to whether you want to change one live unit or the definition that future units will load. That distinction explains why a memory edit can be overwritten while a reloaded definition produces the change again.

Source layer What you change Where this book covers it
a data file the file, before the game loads it Chapter 10
a script the script, or what the host lets it do Chapter 11
native engine code values in memory, or the instructions themselves Chapters 2, 3, 5, and 8
managed code, such as C# in Unity often readable by decompiling it Lesson 6.2

The data-file row deserves special attention. When a game exposes a file for customization, editing it may be the simplest route. Keep a copy of the original: a game update can still replace a file or change its format. Chapter 10 opens with this decision.

Engines are built by developers who need to debug them, and many ship with those debugging tools still inside.

The rendering lessons will use some of these tools. Lesson 7.2 uses Urban Terror’s console variables — named settings the engine exposes — to switch entity drawing on and off. Lessons 7.5 and 7.6 open AssaultCube’s console with ~ and use commands such as idlebots 1, dbgpos 1, and showstats 1 to freeze the bots and print positions on screen.

Those tools are gifts. A console variable that toggles a feature gives you a clean experiment: flip it, and whatever changes in memory is connected to that feature. A debug command that prints your position tells you what value to scan for. Check what the engine already offers before reaching for a debugger.

Overlays, gizmos, editors, and stress tests

Section titled “Overlays, gizmos, editors, and stress tests”

Other developer tools are drawn by the engine itself:

  • A debug overlay prints live numbers, such as frame time, on the screen.
  • Debug drawing, called gizmos in some engines, draws shapes for a single frame without creating any game object: a line, an arrow, a box around a collision shape, the three coloured axes of an object’s own directions, a circle for a light’s reach, a text label. The code calls something like draw_line(a, b, colour) on every frame, and when it stops calling, the shape is gone. An editor’s transform gizmo is the interactive version: three arrows you drag to move, turn, or scale a selected object. Lesson 7.9 draws the same kind of overlay over someone else’s game.
  • An editor lets designers place entities and save scenes, and draws a grid on the ground so the scale and the position of things can be read. A viewer shows one kind of data on its own, such as a 3D model or the buttons of a game controller, without running the whole game.
  • A stress test deliberately runs a heavy load, such as ten thousand sprites, and watches the frame time to find where the game breaks down. If each sprite costs 0.002 ms, 10,000 of them cost 20 ms, which is already over the 16.67 ms budget. Lesson 7.11 shows where such costs come from.

The parts fit together in a game small enough to hold in your head. In Breakout, a paddle at the bottom of the screen bounces a ball upward into rows of bricks.

  • Entities and components. The paddle has Position, Size, and PlayerControlled. The ball has Position, Velocity, and Size. Each of the 40 bricks has Position, Size, and Brick.
  • Systems, in order, every fixed step. Read input into the paddle’s velocity. Move everything that has a Velocity. Test the ball against the walls, the paddle, and the bricks. A hit brick is despawned by a command and adds 1 to the score. If the ball falls below the paddle, switch the game’s state to game over. After the steps, draw every entity that has a Position and a sprite.
  • State. The score, the bricks left, and the lives are numbers in memory. So is the mode: menu, playing, or game over.

ExampleOne step of Breakout

One step, with real numbers. The ball is at x = 794 with a velocity of 150 pixels per second to the right, and a step is 1/60 second:

move = 150 × (1/60) = 2.5 pixels
new x = 794 + 2.5 = 796.5
ball size = 16 pixels wide, so its right edge is 796.5 + 8 = 804.5
the wall is at x = 800, and 804.5 is past it, so the ball hits the wall
reply = velocity x becomes −150, and x is moved back to 800 − 8 = 792

Nothing in that step names Breakout. It is the position update and wall-collision response from Lesson 1.3 run by systems over entities, in the order the engine’s loop calls them.

This lesson names the parts. The lessons below explain how each one works, and the small runnable examples of an open-source engine such as Bevy show nearly every one in a few dozen lines.

Engine part What it does Explained in
Application, window starts the program, opens a window, owns the loop this lesson; Windows Input
Entities, components, scenes organize the world and load it this lesson; Collections
Resources, events, schedules, change detection how systems share data, in what order they run, and what they skip Ordering and Sharing Work
Input turns key, stick, and mouse events into actions How a Game Reads Input; Windows Input
Math, transforms positions, directions, turning, blending Coordinates and Vectors; Camera Frames
Camera, picking what is seen, and what is under the cursor Camera Frames; Camera Rays
2D and 3D rendering, shaders turn the world into pixels Rendering Pipeline; Draw Calls
Animation change a pose over time Aim Geometry
Assets, glTF, audio files the game loads and plays Game Files
UI, text menus and on-screen controls Menus and Text
Gizmos, diagnostics, tools developer views of the running game this lesson; Overlays

Most commercial games are built on a small number of large engines. Three you will meet often:

  • Unity — game logic is usually written in C#, which changes how you find data, as Lesson 6.2 explains;
  • Unreal — C++ engine code, with a visual scripting system called Blueprints;
  • Godot — an open-source engine whose games are scripted in its own GDScript language or in C#.

Games built on the same engine may share code, naming patterns, and file formats. Object layouts also depend on the game’s code and engine version, so use a familiar pattern as a clue and verify it in the next game.

Lesson 6.2 shows how to tell which engine a game uses from the files beside its executable. Do that first. It takes a minute, and it tells you which of the rows in the table above you are dealing with.

An engine supplies reusable machinery; the game adds its particular weapon values, damage routines, missions, and content. A library responds when your code calls it, while an engine also calls game code at the points it controls. The course games use different engines, so a health update or unit definition may live in data, a script, native code, or managed code. A memory edit lasts for a running process; a data-file edit can persist when the file is loaded again. Engine consoles and debug commands expose named state that can help identify the right layer before changing it.

To describe a match, a tool needs more than one verified field: it needs the players and values that belong together at one moment. From One Player to a Game-State Snapshot builds that view while keeping the engine’s changing state separate from the tool’s local copy.

Knowledge check

Where does the rule live?

In Wesnoth you change one unit's hit points in memory. You then recruit another unit of the same type. What hit points does the new unit have, and why?

Finished Lesson 4.1?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 4: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.