Making a Simulation Game - Part 2: The Engine Doesn't Know What Beer is
In part 1 I said everything is agents moving resources between buildings.
This post is what happens when you take that literally: the engine that runs my game does not know what beer is. It routes agents and balances books. Every noun lives somewhere else.
The Layers
The game consists of three layers: View, Engine, and Content.
- The player only interacts with the view.
- The engine knows nothing about the content. All it knows is agents moving resources between buildings.
- Content scripts decide what kind of game this is. It just happens to be a medieval brewery game.
sequenceDiagram
participant Player
participant View as View<br/>(scenes/, UI)
participant Engine as Engine<br/>(EngineRuntimeManager)
participant Content as Content<br/>(buildings, orders, policies)
Player->>View: input (click, hotkey)
View->>Engine: submit_command(cmd)
Note right of View: view never mutates sim state:<br/>commands are the only door
rect rgb(128, 128, 128, 0.1)
Note over Engine,Content: _tick_once(): same loop every tick, 20 Hz
Engine->>Engine: apply queued commands
Engine->>Content: on_receive(): agent arrivals deposit resources
Engine->>Content: on_physical_agent_arrived(): workers land
Engine->>Content: on_tick(): every enabled building
Engine->>Content: tick policies (Workforce, Orders, Quests, …)
Content-->>Engine: engine verbs only<br/>(spawn_agent, dispatch_*, consume_buffer)
Content-->>Engine: queue_event(name, args)
Engine->>Engine: tick_number++
end
Engine-->>View: flush event queue (signals)
View-->>Player: render game state
The Simulation Tick
The engine runs at 20 Hz, framerate-independent.
Each tick, it runs one deterministic iteration.
flowchart TB
c1["1 · apply queued commands"] --> c2["2 · deliver Agent arrivals"]
c2 --> c3["3 · advance every PhysicalAgent"]
c3 --> c4["4 · pre-building policies (stats)"]
c4 --> c5["5 · building.on_tick (all enabled)"]
c5 --> c6["6 · post-building policies<br/>Workforce · Factions · Quests · Orders · Relics"]
c6 --> c7["7 · game hook: _post_policy_tick"]
c7 --> c8["8 · queue sim_tick events, tick++"]
c8 --> c9["9 · flush event queue"]
c3 -. "a PA landing IDLE is dispatchable this same tick" .-> c5
Verbs in, Events out
The player never interacts with the engine directly. They always send commands through the view to the engine.
The view has one API for mutating the engine: submit_command. Every building placement, road, and demolition is a command.
The mutation results come back as events from the engine.
Intentionally non-DRY
Besides some common helpers, every content-side scene is an isolated scene tree in its own directory.
A registry automatically discovers content from directory structure conventions.
Therefore, when I'm wearing the content designer hat, I can focus on the building, the resource, or the event I want to tweak without caring about the overall system.
Brews & Kings
This engine powers Brews & Kings, a roguelike medieval city builder where your whole city feeds one sprawling brewing operation, and kings rise or fall on the strength of your beer. Wishlist it on Steam to follow along.