GameAIProOnlineEdition2021_Chapter07_Managing_Pacing_in_Procedural_Levels_in_Warframe
GameAIProOnlineEdition2021_Chapter07_Managing_Pacing_in_Procedural_Levels_in_Warframe
1 Introduction
Warframe is a cooperative online action game where players take on the role of space ninjas
with machine guns and perform missions in procedurally generated levels. Over time players
gain experience and improve their characters, allowing them to take on more challenging
and numerous foes.
Since the levels are procedural, players will never experience the exact same level
layout. While this keeps the experience fresh, the changing size and layout means that
traditional hand-crafted level-design tricks, such as trigger volumes and spawn scripts,
cannot be relied upon to control the pacing. Instead, Warframe uses an AI Director to
understand the structure of the procedural level, monitor the intensity of the action around
the players, and track their progress (Brewer 2013). With this information, the system can
then intelligently spawn enemies to provide an interesting and appropriate challenge for the
players.
In this article, we will first describe the method used to generate the procedural levels
in Warframe. We will then discuss the data structures used by the AI Director to understand
the structure and flow of the generated level, how we measure the intensity of the action
around the players, and how we can control the pacing at runtime. Finally, we will cover
how designers can work with the AI Director to handle some of the special cases in the
game.
2
Warframe levels are generated by connecting pre-made blocks. Each block is crafted by a
designer with a number of external portals that act as sockets to connect the blocks together.
Only compatible portals of the same size can be connected, as shown in Figure 1. For
example, a 5x3 portal can only connect to another 5x3 portal and cannot be connected to a
wide, 9x3 portal. Level blocks can be rotated and transformed as necessary to line up
compatible portals.
Figure 1 Blocks can only be connected via compatible portals. Left shows a valid
connection between compatible portals of the same size. Right shows an invalid connection
between portals of different sizes.
Each block is categorized by a block-type that specifies its appropriate use. These
block-types include Start, Connector, Intermediate, Objective, and Exit. Levels always begin
with a Start block and end with an Exit block. Connectors are smaller blocks that link the
larger blocks together and space them apart. Finally, Intermediates are large, more complex
combat spaces. A segment is then a series of connected level blocks. Since most levels have
only one objective, we typically have two segments, one between the Start and the
Objective, and one between the Objective and the Exit. Segments can be a random length
and are made up of a combination of Connector and Intermediate blocks. Using the first
letters of each block type, an average level might be represented as the string,
“SCICOCICE”, or a longer one as “SCICICOCCICCE”.
For each mission, designers configure a procedural level template that prescribes
how the level should be generated at runtime. The template lists which blocks are available
and their block-types, as well as specifies the length and composition of each segment, as
well as the maximum branching depth. The layout generator will then use this template to
create a sequence of block-types. For each block-type, the generator shuffles and selects a
block at random, connecting it to the previously selected blocks to create the playable level.
The generator needs to ensure this level is valid and does not overlap itself, as illustrated in
Figure 2. To do so, a depth-first, recursive search is done, and if a block cannot be placed in
a valid position, we unwind the previous step and try a different selection. The algorithm is
described in pseudo code in Listing 1.
To reduce the chance that a procedural level template fails to produce any valid
levels, we use an automated test to attempt to generate thousands of layouts. This allows us
to detect if we fail to generate any complete layouts, or if an excessive number of rollbacks
3
are required to find a valid layout. When necessary to improve the success rate, level
designers either modify the portal locations or shape of existing blocks, or they create new
blocks that increase the variety of blocks available to generate the level. As a general rule,
we recommend a pool size that allows the algorithm to select approximately 15-20% of the
available blocks for any particular generated layout. As you might expect, more complex
blocks with bends or loopbacks can cause layout problems as they further constrain the set
of viable neighbors. Adding more blocks that simply have portals opposite each other can
help to create distance between blocks to avoid overlaps. We also try to keep to only one or
two portal types per procedural template as using too many different size portals can result
in too few valid options when trying to add a block.
Overlapping blocks!
Figure 2 Blocks are categorized into types such as Start, Connector, Intermediate and
Objective. When attempting to place a block in the level layout, no overlap with existing
blocks is permitted.
Listing 1 Pseudo code for the algorithm to generate levels for Warframe.
Generate blockTypeQueue, e.g. SCICOCCE
if PlaceNextBlock(0, blockTypeQueue, placedBlocks):
#we have a successful layout but now we need to cap off
#any remaining open portals and add doors to the portals
#that join placed blocks
for each block in placedBlocks:
for each portal on block:
if portal is open:
push (cap, portal) onto placedBlocks
else:
push (doorFitting, portal) onto placedBlocks
return placedBlocks
return emptylevel
TryPlaceBlock(testBlock, placedBlocks):
if placedBlocks is empty:
push (testBlock, root) onto placedBlocks
return true
5
For the AI Director to make pacing decisions at runtime, it needs a way to reason about the
structure and flow of any generated level. We need to identify which way the players should
go to reach their objective, if they are currently moving towards or away from this objective,
and where enemies should spawn to ensure they have a meaningful impact on gameplay. To
accomplish this, we use a tool called the Tactical Area Map or “TacMap”.
The TacMap is essentially a rough corridor map. It is a graph structure that
represents the structure and flow through the level, as shown in Figure 3, that can be used by
the AI Director. Each node in the graph is referred to as an area. The connections between
areas represent how one may move from area to area through the level. A TacMap is created
offline for each level block. At runtime, after generating the procedural layout, all the
individual TacMaps are then connected together into a single graph that represents the
structure of the entire level. Doors and connections between blocks are marked up in the
TacMap so we can identify chokepoints and make decisions based on whether the doors are
locked. The TacMap is coarser than the navigation mesh used for path finding, but finer than
the level blocks (see Figure 3) and provides a practical level of detail to make tactical
decisions.
6
NavMesh with 44 nodes TacMap with 17 nodes Level Block with 3 portals
Figure 3. Comparing the granularity of the TacMap to the navigation mesh and level block.
We often need to search for entities in the level, such as spawn points, cover
locations, alarm panels, et cetera. To do so, we add references to these entities to the areas in
the TacMap graph. Critically, this allows the searches to follow the structure of the level and
not just straight-line distance.
Various forms of influence maps are used to track the runtime properties that the AI
Director can use to make pacing decisions. Rather than a traditional 2D grid, the influence
maps are setup on the TacMap to allow influence to both accumulate in areas and spread to
adjacent areas through the connections in the graph, which represents the actual flow
through the level. As an added advantage, since the TacMap is in 3D and not restricted to a
2D plane, it can also handle verticality.
Depending on the game, there are a number of potentially useful influence maps you
may want to consider. Below we discuss four of the more important maps, namely the
distance map, player-influence map, active-area map, and the visibility map.
4 Measuring Intensity
In order to control pacing, we first need to measure it. However, pacing is difficult to
quantify: How much action is going on around the players? How engaged are the players?
Do we need to spawn more enemies or should we let off for a bit and allow the players to
collect themselves before continuing? In addition, players in Warframe gain experience by
completing missions and upgrading their characters and equipment over time. Thus, an
encounter that may have challenged them before might be too simple when the players play
the mission again later.
While we are always experimenting with more advanced intensity measurement
calculations, often simplicity is best. To that end, every time the player takes damage, her
intensity is raised. This amount is proportional to the normalized ratio of damage received to
her total health and shields. Every time the player kills an enemy, her intensity value rises by
an amount proportional to the distance from the player to that enemy. If a player is being
targeted by an enemy, we maintain the intensity, otherwise it decays over time. This is a
similar scoring metric to that used in Left 4 Dead (Booth 2009).
While not a perfect score, it reasonably estimates that players who are killing
enemies or taking damage themselves are most likely in the thick of the action compared to
players who are not receiving any damage and not killing any enemies.
5 Controlling Pacing
Now that we have a measure for the intensity of the action around the players, we can
attempt to shape the play experience. If we graph the intensity over time, we want this graph
to have a rhythmic rise and fall pattern similar to a roller-coaster. As the players explore and
encounter enemies, a fire-fight ensues, causing a rise in intensity. After the players dispatch
the enemies, the intensity dies down until the players encounter more enemies and the
intensity starts rising again, as shown in Figure 4.
Peak
No spawns during
Intensity rises
decay after peak
during combat
Figure 4: Intensity will rise and fall during gameplay. If intensity reaches 100, it has peaked
and we hold off spawning more enemies until the intensity dies down, giving the players a
9
Our primary tool for controlling the pacing in Warframe is spawning enemies. While
intensity is low, the AI Director should spawn more enemies ahead of the players, and
should continue to spawn enemies as intensity rises, until it reaches the maximum supported
value or peak. Once intensity peaks, we want to maintain the peak for short time and then
back off and stop spawning until the players have had a chance to dispatch all the enemies
and recover from the fight. The intensity then decays down until it reaches zero and the
spawning process begins again. This should drive the intensity back up again as the players
encounter the next group of enemies.
Varying the number of active enemies around the players allows us to control the
experience. As players are naturally attracted to conflict, we can thus subtly lead the players
towards the objective by having more enemies spawn if players are heading towards the
objective and fewer if they are moving away from the objective. If the players have not yet
been discovered, we use a lower limit on active enemy count. This allows our space ninjas
to perform some stealth gameplay and sneak through the few patrolling guards. Once the
players have been spotted and the alarm sounded, we want increased enemy activity and
therefore the active limit is increased.
Finally, the limit also depends on the type of block. Connector blocks tend to be
smaller and so have lower limits. Intermediate blocks, representing larger combat areas,
have higher limits and the Objective block will have the highest limit. Since our procedural
layout generation typically produces alternating connector and intermediate blocks leading
up to an objective or exit block, this helps modulate the enemy numbers and provides a
satisfying rhythm.
6 Spawning Enemies
After deciding to spawn, the AI Director then needs to decide which enemy and where to
create them. The mission will specify a list of available enemy types, their selection
probability and an optional maximum simultaneous limit. Using a weighted random
selection allows the AI Director to spawn many ordinary grunts and fewer specialist or rare
units. Having a maximum simultaneous limit for certain enemy types ensures players will
not stumble into a room full of extremely tough enemies and instead will encounter them in
more reasonable numbers.
Once we’ve selected the type of enemy to spawn, we want to determine where that
enemy will have the biggest impact on gameplay. There is no use spawning an enemy near
the start of the level if the players have already completed the objective and are heading to
the exit. We also don’t want to spawn enemies in a room recently vacated by the players. To
avoid breaking immersion, enemies should typically not spawn in plain sight of the players.
In general, an enemy should be created ahead of the players and close to them, but out of
sight.
Certain levels and enemies allow for special-case spawning. Examples are the
robotic deployment containers that have a special deployment animation when the container
opens and activates the robotic unit housed inside. In this case it is preferable to use a
10
special spawn point that is visible to the players, so they can witness the arrival event. The
AI Director first tries to find a special spawn points for the enemy, but if none are available,
it will fall back on selecting a normal, hidden spawn point.
The algorithm used by the AI Director to search through the TacMap for spawn
points is a breadth-first search, described in Listing 2. This search starts at the areas
containing the players and spreads out, keeping areas within the given constraints and
discarding unsuitable ones. Next, we collect valid spawn points from each valid area. Since
the search for valid areas expands out from the player, the list of valid spawn points will be
approximately sorted by increasing distance from the players. We select a point with a
Gaussian random number biased towards 0, which will prefer points closer to the players
instead of ones further away.
Listing 2 Algorithm used to search the TacMap for spawn points for an enemy. The
call to GaussianRand will generate a random number in the range from a Gaussian
distribution with the mean at 0 and the standard deviation of 0.5.
Add players’ areas to open list
While open list is not empty:
Pop area off front of open list
If area has not yet been visited:
Mark area as visited
If area is valid to keep:
Push area onto keep list
If area is valid to expand:
For each neighbor of area in the TacMap:
Push neighbor onto back of open list
The previous sections cover most situations handled by the AI Director. Some missions
require some special case handling, however, which we describe below.
7.1 Extermination
Extermination missions require the players to progress through a linear, non-branching
gauntlet and dispatch a specific number of enemies. The AI Director needs to not only
ensure that the required number of enemies spawn, but that they all have spawned before the
players have reached the end of the level, and that they are spread out through the level and
not clumped at the beginning or at the end. Additionally, we do not want the concentration
of enemies to be completely even, as this would be dull. Instead, there should ideally be
11
80
60
40
20
0
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
Progress Through Level
Figure 5: The population graph for exterminate missions is fixed and provides two peaks of
activity to ensure all enemies spawn before reaching the end of the level. For crossfire
missions, the graph is shifted to match the position of the combat front in the level.
7.2 Crossfire
Crossfire missions operate just like Exterminate missions, except the players ally with one
faction to oppose a common enemy. An example is a boarding action between capital ships
of rival factions. The players start on one ship, and fight their way across onto the other. One
of the blocks in the level represents the main combat front where the enemies are clashing. It
would be disappointing if the players were to arrive at these battle lines only to discover a
lull in activity.
Instead, to overcome the vagaries of the procedural layout, we tweak the
predetermined population graph based on the actual layout. First we look up the combat
front in the level’s distance map. We shift the second peak in our graph to this point to
ensure it lines up with the features of the level. We then shift our first peak halfway between
the start and the previously adjusted peak. This modified graph as shown in Figure 5, now
12
matches the level layout better, and ensures the action is where we want it to be.
8 Conclusion
As games embrace procedural generation, we can’t rely on traditional designer scripted set-
pieces. We need systemic and procedural pacing solutions. The AI Director in Warframe is
one approach to this problem. It handles the dynamic pacing requirements of standard
missions on procedural levels by monitoring the intensity of the action around the players,
tracking their progress through the level, and reasoning about the flow and structure of the
level at runtime in order to provide a compelling gameplay experience.
There will always be times when a fully automated system cannot handle specific
design requirements, but in these situations most of the information required is maintained
by the AI Director and the TacMap. This allows designers to script special case encounters
in a systemic fashion, without having to worry about the specifics of the level layout, which
can vary at runtime over repeat play-throughs of a mission and as players level up their
characters.
13
9 References
Brewer, D., Cheng, A, Dumas, R., Laidacker, A. AI Postmortems: Assassin's Creed III,
XCOM: Enemy Unknown, and Warframe. GDC 2013, San Francisco,
https://www.gdcvault.com/play/1018058/AI-Postmortems-Assassin-s-Creed
10 Biography