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.

BoxDragon_Logotype_RGBRed.png

About Us

I’m Karl and I helped found Box Dragon, a new game studio in Gothenburg, Sweden. Our small and driven team has an ambitious vision of creating unyielding experiences. We are creating a new strategy card game as our pilot game that remains unannounced.

Our team is composed of veterans from Riot Games and Stunlock Studios, the studio behind Battlerite. An exciting part of working in an entirely new studio is that our team gets to lay the foundations for a new generation of games.

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. 

TwoWorlds.png

The View World stores the state of the gameplay variables as they resolve in front of the player’s eyes, whereas the “Input World” stores the state of the gameplay variables with all consequences of the player’s inputs being instantly resolved.

In essence, we run gameplay logic on the Input World and we drive UI and other visuals with the View World.

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”.

Events.png

The Input World is our “source of truth", and we need to keep the View World in sync by applying variable updates incrementally. Since variable updates by themselves carry no information about timeliness, the View World might be forced to play out changes arbitrarily. Instead, we want to make variable updates line up with the visuals. We do this by grouping related variable updates together as Gameplay Events.

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.

CodeExample.JPG

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.

EffectHierarchy.png

Gameplay events can contain other gameplay events in a nested fashion. This is necessary because effects can trigger other effects that need to resolve before continuing with a prior animation. A common example of this is playing a card in a card game: The card is played, the cost is paid, the card’s effect happens which can trigger other gameplay events, then finally the card is moved to the discard pile.

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.

ThreeWorlds.png

With the Logic World, gameplay logic runs asynchronously and in parallel with the frame loop. The rest of the game will need to handle the “thinking in progress” case, where inputs have been sent to the Logic World and the game loop is waiting for completion. In return for this added complexity, we get to entirely avoid blocking the rendering loop while running gameplay logic with very little added system-wide complexity.

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.