Three-Worlds Theory: Art or Gameplay? Pick Two.
I grew up obsessed with turn-based games like Civilization, Advance Wars and Heroes of Might and Magic. “Just one more turn!” has kept me up more nights than I’d like to admit. Recently, I’ve been really excited about design innovations in strategic card games on the PC with Slay the Spire and Hearthstone, but something in the space has bothered me for a long time.
I’ll take Civilization as our prime example. Combat sequences look great, but they are long and prevent players from performing other actions while the animations are playing. As a sort of peace treaty for experienced players, Firaxis offers settings to skip movement and combat animations. Players find themselves choosing between two different games: one that is visually clear but unresponsive at times, and one that is responsive but with worse visual communication.
As a player, I can’t see a gameplay reason that the game should stop me from issuing commands to other troops while a battle plays out. I chalk it down to a technical limitation, but Civilization is not the only game in the space with similar limitations that can be frustrating to more experienced players.
With this post, I hope to show how fundamental architectural changes can open up for new trade-offs for developers of turn-based games. A Three World design decouples gameplay rules from visuals, and enables games to play back animation sequences in parallel, or even reorder them entirely.
View and Input
We start by introducing a “View World” for the player’s viewing pleasure and an “Input World” that races ahead. A “World” in this context is a container for all data required to run gameplay rules as a result of player input.
To maintain these worlds in parallel, there are some key requirements. We need a consistent way to identify entities across each world, and we need to be able to record changes to variables and replay them on another world.
For us to identify entities across all worlds, each entity must have a uniform identification no matter which world we’re talking about. We cannot refer to anything with a physical identity (such as a pointer to an entity in a specific world), and instead we must refer to their logical ID when fetching data. Entity-component-system implementations usually have this as a core concept with an “entity ID”.
Keeping the Worlds in Sync
A World Update is a single change to a variable in the game, whether that’s a change to a character’s health or a movement in that character’s position. When a series of world updates happen together as a package which we want to communicate to the player, we group that together and define it as a gameplay event. In the example below, the “Cast Lightning Bolt” gameplay event involves a series of world updates. In our system, variable updates are recorded automatically within a gameplay event scope.
When a gameplay event like an attack is played out to the player, we want variables like HP to be updated at the moment of impact rather than immediately when animations start playing. We let designers control the instant when the world is updated for the relevant gameplay event in their sequencing tools to make sure the variable updates line up with the visual representation.
Events as Graph
We can model events as a directed acyclic graph (DAG) by creating nodes for each visual entity and treat gameplay events as edges between nodes. With the capability of reasoning about our events in terms of a DAG, we can apply game-specific rules to determine which events would be acceptable to reorder or run in parallel. This probably warrants its own post - please follow us on Twitter for updates.
Solving for Large Games
So far, we’ve talked about only two worlds: Input World and View World. For games without a lot of heavy calculations like AI decisions and pathfinding, two worlds ought to be enough for anybody. Large-scale turn-based games (4X games like Civilization for example) can have rather large maps though, and it’s challenging to calculate everything while staying within frame budget. For a 60 FPS game, we only have about 16 ms per frame to run calculations. For these larger games, we conjure up World #3: The Logic World.
Worlds are entirely separate copies of the gameplay state that can be synchronized by applying state updates, and we can leverage this to create a third copy of the world for running gameplay logic in parallel with the rest of the game. This only marginally increases complexity: we have to send inputs to the Logic World and receive gameplay events and state updates in the regular game loop. The received events and state updates are then applied to the other worlds. With this change, we move part of the responsibilities of the Input World to the Logic World.
Join our Team!
Hopefully this post gives you a glimpse into some of the exciting technical work that we are tackling at Box Dragon. We are hiring and would love to hear from you. Please have a look at our job postings or send us an open application.