Abstract
Problem: How should game developers implement event systems to create reactive game worlds where NPCs, creatures, and systems respond dynamically to player actions?
Approach: Tim Cain draws on decades of experience shipping games (including Arcanum) to explain the architecture, design considerations, and optimization strategies behind event queues.
Findings: A well-designed event system decouples game systems from each other, enables delayed and conditional reactions, and scales from simple single-purpose triggers to complex multi-listener architectures. The two biggest wins are designer-friendly delayed/conditional events and programmer-friendly system decoupling.
Key insight: Event systems let any game system post events and any other system listen for them β neither needs to know the other exists. This decoupling is the core architectural benefit.
What Is an Event System?
An event system is a queue of events that the game checks every update cycle. Any system in the game β combat, skills, dialogue, shops β can place events into the queue. Other systems register as listeners and get notified when events matching their criteria fire.
Every event generally includes:
- Type (e.g., "creature died", "lock picked", "item sold")
- Timestamp
- Location
- Initiator (who caused the event)
- Additional data depending on type (e.g., who killed the creature, what weapon was used)
Designing Your Event Granularity
A critical early decision is how specific or general your events should be. Tim gives a practical example:
- If the only death reaction in your game is a companion getting angry about killing cissy pigs, you might only need a "cissy pig died" event with a counter.
- But if multiple systems care about different creatures dying β a gang boss, wolves, cissy pigs β you need a general "creature died" event with data specifying what died.
The lesson: design your event types to match your design scope, not just your current needs.
When Events Fire
Events can be scheduled in several ways:
- Immediately β processed on the next update cycle (still queued for architectural reasons)
- Timed β "fire 5 minutes from now"
- Recurring β "every Tuesday at midnight" (the event reinserts itself after firing)
- Conditional β "fire when the player camps" or "when the player enters this town"
Tim mentions that in Arcanum, the update cycle was a "heartbeat" that could range from every 2 seconds to every tenth of a second. In Unity, it would be every frame.
Listeners and Registration
When a system starts up, it registers itself as a listener with the event system, specifying what it cares about:
- A specific event type ("notify me when anyone dies")
- A specific entity ("notify me when a cissy pig dies")
- A specific initiator ("notify me when the player does something")
- A specific responsibility ("notify me when the player is responsible for something")
One event can have multiple listeners. When a creature dies, the companion system, the quest system, and the AI system might all get notified from a single event.
The listener then decides whether to actually respond β maybe the NPC it was going to inform is now dead, or the conditions no longer apply. If it does respond, it can act immediately or post its own event back into the queue, creating chains of reactions.
Public vs. Private Event Queues
Tim describes two architectural approaches:
Public (General) Queue
A single centralized queue that acts as a clearing house for all events. Can optionally be subdivided into specialized queues (location queue checked on area transitions, camp queue checked when player rests) for performance.
Private Queues
Queues attached to specific NPCs, companions, bosses, or locations. These only update when that entity is loaded and active. More efficient when many events are tied to specific entities.
You don't need private queues β a general queue can handle everything. Private queues are an optimization that depends on your design's scale and complexity.
The Two Biggest Benefits
For Designers: Delayed and Conditional Reactions
Event queues let you design reactive moments that don't happen immediately:
- "Zombies spawn in the cemetery at midnight"
- "While the player wears this amulet, assassins appear as random encounters trying to reclaim it"
This gives designers powerful tools to make the world feel alive and responsive over time.
For Programmers: System Decoupling (Scope)
This is the architectural crown jewel. Tim's lockpicking example:
- The skill system detects a successful lockpick and posts an event: location, time, lock, player, success.
- Nearby guards have registered as listeners for lockpick events.
- Each guard checks: Can I see the player? Is he in range? Is he using stealth?
- If conditions are met, the guard reacts: "Hey, what are you doing over there?"
The skill system and the AI system know nothing about each other. They both interact only with the event system. This means:
- Adding a new skill (like "disarm trap") just means posting a new event type β no AI code changes needed
- Systems can be developed, tested, and modified independently
- The codebase stays manageable as complexity grows
Multiplayer Considerations
Tim notes that event systems work similarly in single-player and multiplayer games. The key difference is that event conditions typically check whether the relevant player is present. If an NPC reacts to the player who killed something, it only triggers when that player is nearby β other players walking past won't set it off.
Optimization Advice
Tim emphasizes that the choice between general vs. private queues, and how events are filtered to listeners, is an optimization decision that should be made after seeing the full design. He warns designers: be specific in your design docs. If you hand a programmer a half-finished design, they'll build a simple general queue. When you later add complex per-NPC event requirements that tank performance, that's on you β not the programmer.