Specialization Project • The Game Assembly
City Simulation
A city simulation project built in TGE, focused on how NPCs react to needs, schedules, scarcity, and a changing world instead of following scripted behaviour.
Systems Programmer
TGE - Custom C++ Engine
Simulation Systems, AI, World Interaction
C++, TGE, ImGui
Overview
I've always enjoyed colony and city simulation games because I like seeing stories unfold over time. You give your colonists direction, but they still have some autonomy, which makes the world feel alive.
Going into this project, I knew I wanted to work with AI. My initial idea was to build something similar to a Radiant AI system inspired by Oblivion, since I’ve always been fascinated by NPCs that seem to live their own lives independently of the player.
As I started building the project, it shifted more towards a colony sim instead. Partly because of scope, but also because I became more interested in building a system where behaviour comes from simple rules and interactions rather than something more scripted.
That ended up shaping the goal of the project.
The goal of this project was to make NPCs feel alive by letting them react to the world and their own needs instead of following a script.
Instead of focusing on one mechanic, I wanted to build a small world where NPCs go through daily routines, react to scarcity, and change their behaviour based on what is actually happening around them.
Simulation & NPC Behaviour
A typical NPC moves through a simple daily loop. They work, get hungry, eat, get tired, relax, and eventually go to sleep. This is not a fixed sequence. It comes from their needs and from what is currently available in the world.
The behaviour is also driven by a priority order. Hunger is checked first, then tiredness, then shared world tasks like building. If none of those take priority, the NPC falls back to its current schedule. Carriers also have special rules, since urgent food transport can override their normal work behaviour.
Because hunger has such a high priority, NPCs will try to eat as soon as possible.
This can lead to situations where multiple NPCs decide to go to the same restaurant at the same time, even if it will run out of food before they arrive. When that happens, they cancel their action and switch to something else. If food suddenly becomes available again, they may all go back at once, which can look a bit messy.
The reason for this is that there is no reservation system, which is something I introduced later to fix this behaviour.
Multiple NPCs commit to the same restaurant before the food situation has updated, which creates weird behaviour.
AI Structure – Brain & Actions
I built the AI around a simple Brain + Action structure.
The Brain decides what an NPC should do based on needs, schedule, and the current world state, while Actions handle the actual behaviour.
Each frame, the NPC either continues its current action or switches to a new one if the action has finished or should be interrupted. Some actions, like eating, sleeping, building, or carrying resources, are not allowed to be interrupted once they have started.
Some actions also work like small state machines internally. For example, a build action can move through steps like going to a warehouse, going to the build site, and then building. That made it easier to keep each action self-contained instead of pushing all that logic into the Brain.
The system is very simple, which made it much easier to work with. It was easy to debug, easy to understand, and easy to build on.
One important detail was making sure certain actions were marked as non-interruptible. If I forgot to do that, newly added actions could instantly cancel themselves.
At the core of the AI is a very simple update loop:
View Brain::Update pseudocode
if there is no current action
or the current action is finished
or the current action should be interrupted
{
decide next action
create that action
if a new action exists
{
start the action
}
}
if there is a current action
{
update the action
}
Carriers carrying
Fishers fishing
Hunters hunting
Woodcutters… woodcuttering
From Static to Living Systems
One of the biggest challenges in this project was making the simulation actually feel alive.
Early on, everything technically worked, but it didn’t feel alive. NPCs repeated the same patterns over and over again, there were no real consequences to their decisions, and too little changed in the world.
The biggest improvement came from introducing finite resources.
Once resources could run out, NPCs were forced to react to each other and to the world in a different way. Behaviour became less predictable because NPCs could no longer rely on things always being available. That made their decisions feel more believable.
I also introduced food decay, partly because I thought it would be fun to implement, but also because it added another layer of scarcity. Food is no longer just available or unavailable. It changes over time. This created more variation in the simulation and gave the resource and transport systems more to react to.
Together, these changes made the world feel less perfect and a lot more believable.
Notice how carriers prioritize food based on freshness instead of just going for the nearest one.
Reservation & Task Ownership
Reservation became important once multiple NPCs started competing for the same things. It wasn’t something I planned for early on, which made it harder to introduce later.
Some tasks need to be claimed before the NPC actually starts doing them. In my current project, this matters most for carrying resources and for build sites, where a maximum of two NPCs can work on the same construction at once.
Without this, too many NPCs could commit to the same task, which created messy and inconsistent behaviour. Adding reservation helped reduce that problem and made task distribution feel a lot more controlled.
The build site only allows a limited number of NPCs to reserve and work on it at the same time.
Balancing & Iteration
Balancing the simulation turned out to be one of the hardest parts of the project.
Small changes to values could completely change how the NPCs behaved. At different points, NPCs would eat too often, go to sleep too early, or behave in ways that didn’t feel natural in the context of the world.
What made this challenging was how connected everything is. Needs, schedules, available resources, and action rules all influence each other, which made it difficult to predict the outcome of even small adjustments.
This led to a lot of iteration and constant tweaking of thresholds and values. It was sometimes frustrating, but also one of the most valuable parts of the project.
It also made me realise how sensitive simulation systems are, and how important it is to make them easy to observe, debug, and tweak.
It also showed me how important planning is. I didn’t think about reservation early on, which made it much harder to add later once other systems were already built around it.
What I’d Improve Next
The next step would be to make NPCs affect each other more directly, improve reservation around restaurants, and create even more variation in the world over time.
I would also like to improve the pathfinding and keep expanding the debug tools, since they became a huge part of understanding what was actually going on.