good morning and welcome to Marvel's
spider-man nai post-mortem my name is
Adam noon Chester and I'm one of the
lead gameplay programmers at insomniac
games over the past 11 years I've been
fortunate to work on eight great games
at insomniac and for the majority of
these I've been in AI programmer on
Marvel's spider-man I led the team of
gameplay programmers that developed
combat let's take a quick look my team
worked on both stealth and melee combat
we helped create spider-man's combat
moves and all of his gadgets we also
worked on the enemies and bosses and I'd
like to take this opportunity to tell
you about some of the biggest changes
and challenges that we encountered on
the AI side developing this game so let
me give you a quick preview of the
topics I'm gonna discuss today I'll
start with some high-level trends
related to how we author behaviors and
build AI characters then I'll move in
how we use sink animations to get a
Marvel feel in a game from there I'll
give an overview of a few core problems
and how we iterated on them with our
combat after that we'll look at some
procedural animation techniques that we
developed specifically for Spider Man
and then finally time permitting I'll
briefly discuss a few of the places
where I feel we fell short so let's
begin with a high-level trend that
occurred over the course of development
that I expect to continue and that is
less use of complex behavior trees and
more use of data-driven hierarchical
finite state machines this happened for
several reasons but the largest was
simply that Marvel spider-man was the
largest game that insomniac games is
made to date spider-man has five major
factions and a few minor ones we've also
got eight Ark types that are common to
all the factions and the majority of
these Ark types have variations
perfection there are also several
faction exclusive types and eleven boss
fights this all meant that we had to
create 64 AI classes and the majority of
these had to have some unique attacks
reactions or other behaviors we also had
to do this with a slightly smaller
gameplay team than our last big game
sunset overdrive in which we only made
19 classes let me begin by briefly
describing how we
organize our AI behavior at insomniac we
have our own proprietary engine and the
vast majority of our code is written in
C++ this is the general architecture
that's been in use since about 2012 from
here on I'm gonna also use the term AI
and BOTS interchangeably at the top
level we have a behavior tree and that
decides on a behavior to run a behave
behaviors can have an arbitrary number
of sub behaviors but eventually a
behavior will instantiate a state these
are our leaf nodes and they control
animation motion and perform other
player facing actions when we first
implemented behavior trees into our
engine a lot of them tended to have a
structure like this this is one corner
of the behavior tree that drove the
behavior of ranged enemies and sunset
overdrive this simple sub tree uses 34
discrete nodes and many of them just
mapped to a simple AI state such as
playing an animation shuffling around
may laying or aiming the parameters for
the state were largely configured by the
behavior tree and passed through a
behavior that only instantiated a single
state in order for our AI to function as
desired things tended to get really
complicated nodes often had to utilize
numerous kinds of callback functions for
example let's look at a simple node that
is trying to use a state to aim at a
target over a railing that node will
need to be able to decide if it has to
run so we have a callback for that the
bot state that we're using doesn't ever
end so the tree needs to decide if that
state should continue to run so we add
another callback the state we're using
also doesn't or this behavior tree nodes
shouldn't change state until they are
entered so since we need to notify
another system when we start aiming over
a railing
we had a callback the state that we're
using doesn't do any weapon management
so we have a callback to do all of that
from the tree and we need to notify that
same system when we're done using the
railing so we add a fifth callback
furthermore in order to get the correct
node to activate at the correct time we
often needed to consider how a nodes
parents selected which of its children
to run we call that the group policy and
there are three types the order of the
children could be
priority ranking or the order could be a
sequence with each node running one
after the other or the the node whose
decider callback returns the highest
value would be the one to run and if
your behavior tree implementation is
anything like ours you have plenty of
decorators and flags and other confusing
options all of this led to trees that
worked but were complicated and
difficult to debug when they broke
furthermore one of the advantages to
behavior trees is that the trees are
supposed to be composable and nodes
should be reusable
we found the opposite to be true when
the tree is complicated we often needed
to think about how both a node siblings
and its ancestors functioned for Marvel
spider-man we recognized that we could
not have unique behavior trees or even
sub trees for each of the 50-plus BOTS
classes we gradually started moving
towards trees that were smaller and more
straightforward with behaviors that
contained a lot of the complexity and
logic that was previously entangled in
the callback functions this meant that
we could share structure and behaviors
nearly all of our standard enemies use
exactly the same behavior tree and set
of behaviors as a quick example we have
a single node in our behavior tree that
handles all of melee combat and while
the previous sub tree that I showed you
still exists the most complicated chunk
of transition logic was moved into a
behavior so what do we do instead as our
behaviors became more robust we began
today to drive more of them I want to
discuss two of these examples the first
is a simple behavior that is used when
designers need explicit control over
bots the second is more complicated and
is used for the majority of our combat
both of these behaviors take a piece of
polymorphic data configured in our
editor and transform it into a state or
sub behavior they do this while
encapsulating a large portion of the
complicated logic inherent to action
game AI so rather than have a bunch of
behavior tree nodes that respond to
designer scripting we made a more
data-driven solution let's take a look
at the high level architecture script
we'll begin by creating what we call a
bot command and these commands go into a
bot command queue when the queue exists
the behavior tree has a node which
instantiates behavior scripted this
behavior pull
commands one at a time out of the queue
and uses them to create sub behaviors or
states let's take a look at the
individual components starting with our
command queue all it really does is
provide storage for designer commands
that a bot is going to execute and a
simple interface for accessing them next
I want to touch on the bot commands
themselves the basic command itself is
really just an interface an actual
command will use this method to start
whatever behavior or state it needs to
and attach it to the provided parent
behavior and here's an implementation of
that method in a play annamund it sets
up some play Adams sets up some data for
a play animist eight and then request
that state to be added to the provided
behave behavior all bought commands
follow this format they're created by
script and added to the bot command
queue finally let's take a quick look at
behavior scripted itself if a bot
command queue exists and has any
commands in it the paver tree will
instantiate this behavior it has an
update function a consume next command
function and a handle to access the
command cue the behavior update is
simple if the current command is done we
pop it from the queue and try to consume
the next one the consume next command
function is also straightforward it gets
the command cue checks to see if it has
a command so there is no command we're
done but if there is one then we tell
that command to start and attach
ourselves as the parent behavior when
the command is done the previous update
function will just call this function
again and will continue executing
commands here's what issuing commands
looks like from script we first request
control of a bot and tell them to go to
a position face their target and play an
animation and here's what things look
like in game you can see the bot command
queue here you can see the bot moved to
a position then face the player and then
finally play the provided action some of
you may be wondering why don't we data
drive more and this is an example of why
there are many times when an AI should
not immediately respond to requests for
script control they could be jumping
through the air they could be in a
cinematic playing a hit react animation
or any number of other things our goal
is to
data drive the interesting player facing
problems but also handled the difficult
squirrely and bug prone parts reliably
in code so let me talk about a place
where things can get squirrely melee
combat and Marvel spider-man is
data-driven and I want to first give you
another overview of the architecture of
this system before we dive into some of
the details let's look at the data first
at the top level each bot has a combo
config that combo config contains a list
of combo entries each combo entry
contains one or more combo move
containers and each of these contains a
single combo move base and this is the
polymorphic data all of this data is
owned by a component called bot combos
on the behavior side we have our
behavior tree which can instantiate for
example behavior melee when it is time
to attack this behavior will pass data
from bot combos into a behavior use
combo which will then use that data to
start a sub behavior or state let's
start looking at the bottom of the data
for the next several slides I will be
showing examples of structs written in a
proprietary c++ like data definition
language all mark usage of it with DDL
all attack actions in combat will
ultimately derive from a bot combo move
base these these strux can represent all
of the static data needed to execute for
example a melee attack this combo move
has an animation to play as well as some
data describing the timing for the
attack it could also represent throwing
a projectile this struct has data for
the projectile asset to throw what
animation to use where on the bot the
shot should spawn and some additional
timing data the point is that each of
these derived types contain all of the
static data needed to start either a
behavior or a state to perform the
desired attack that is to say that these
contain the static data to make these
let's move one level up the data
hierarchy and talk about the move
container here's the definition for bot
combo move container you can see the
combo move here at the bottom but this
struct contains other data that data is
parameters for defining how to prepare
the
to execute the move these are things
like does the bot need line-of-sight
with their target to perform the attack
does the bot need to be on-screen or how
close does the bot need to be in other
words this the combo move container
contains not only the combo move but
also has data that drives the behavior
of this behaviour use combo so let's
briefly look at it it's really just a
state machine it can choose to wait to
perform an attack it can go to its
target it can perform a combo move and
at some point it will end it has its own
functions for determining what state it
should be in based on the data in the
move container and it also does things
for us like setting hit react immunities
while the attack is active or starting
cooldowns at the correct time however
the relevant code happens once behavior
used combo has determined that it is in
position and ready to execute an attack
it calls a transition combo function
this function takes the move container
and then retrieves some metadata in the
container about the type of combo move
that it contains if it's a melee move we
prep some state data and start a melee
attack or if or if it's a throw
projectile move we we prep some
different state data and start it throw
a projectile state in the full version
of this function this if-else Cascades
for the other 37 combo move types that
we have there might be a little bit of
cleanup work for us to do here so we've
discussed the bottom half of this
diagram so next we're going to look at
the actual combo entries themselves
because behavior use combo may want to
perform more than one move in a sequence
the combo entry contains a list of them
to execute one after the other
it also contains all of the data for
determining when a combo is valid for
example a cool-down for how often it can
be used a waiting value to be used when
selecting between valid combos a pair of
distances that define the range at which
a combo is valid as well as a list of
other arbitrary data driven conditions
the final piece of the data is the combo
config and this is just a simple struct
that contains a list of combos every
single one of our AI
as a unique one of these I want to
briefly touch on the owner of all of
this data bought combos it is
responsible for not only owning the
combo config but also for selecting the
best combo to use from that config
versus a provided target it will use all
of the data contained in the combo
entries to make a list of valid combos
and return one based on their weights
finally we'll briefly take a look at an
example user of behavior use combo
behavior melee it's a fairly standard
state machine that has a bunch of states
that it handles transitioning between in
code one of them is using a combo if
it's the bots turn to attack and bot
combos has a valid combo entry this
behavior will just start using this
behavior will start behavior use combo
here's a brief look at combos in action
there's some debug display up here this
character has five combos and active
will be drawn next to each of the combos
that he is currently using each of these
uses different states whose static
parameters are configured entirely in
data so that's a high-level review of
how we dated Rove to large sections of
our AI melee combat and script control
we had two other significant behaviors
that were data-driven one for handling
hit reacts and another for responding to
incoming attacks by blocking dodging
counter attacking etc we felt we got a
lot of mileage out of working in this
paradigm for three main reasons the
first is that new moves are easy to add
since you didn't need to change complex
transition logic the second is that we
were able to loosely couple our states
with data that is a single bot combo
move could potentially map to multiple
states or multiple moves could map to
the same state if it was appropriate and
third this allowed us to pick and choose
what data was presented to the designer
and then handle the messy logic in code
working this way allowed us to make a
large number of classes who only
differed in data that
and there are some definite
disadvantages with our current approach
most of these behaviors organically grew
into this pattern so parts of them can
be a little bit of a mess you saw an
example of this with the giant list of
if else's in our Meili example but the
real big problem we had for us was that
our tools only allowed us to express the
data in a properties panel and let me
briefly show you why this is such a
problem here's the data definition for
the heavy 8 enemies three-hit combo
first we have all of the data for how he
can select to use the move then we have
all of the data for how behavior use
combo will prepare to execute the move
then we have a bunch of data that every
single attack needs so that it can
properly register with spider-sense then
we have the actual data for the melee
attack itself however remember that I
said this was a three-hit combo all of
this data is repeated twice more and it
eventually looks like this this
character also has 4 more combos a shock
wave a grapple a throw and another
multi-hit combo so yeah the amount of
data needed to be configured made the
system intimidating and difficult to
understand
improving on this is one of our next big
goals so let's move on to a section
where I can finally start showing a
bunch of screenshots and videos synched
animations our creative director gave
the team a goal that he wanted Spiderman
to have a comic book movie feel early on
in development we decided that we wanted
a high degree of fidelity in our combat
animations and reactions in order to do
this we determined that we would need to
have a system that allowed us to
playback those animations of spider-man
and his foes interacting our system at
its root has the concept of a host
usually spider-man the attacker and a
guest usually his victim the whole thing
hinges on something we call a sink joint
it's shown as the white debug sphere in
this video the sink joint is a is a
joint on the host which has a position
and orientation that will match that of
the guests when the animations are
played back in synced we use joint on
the attacker because this allows
spider-man to perform a synced attack
versus a character that hasn't yet
played a sink react
in this video you can see both the
current position of the sink joint drawn
in front of spider-man as well as the
sink joints target position drawn on the
enemy as the time of impact draws near
you can see spider-man blend into
position to hit his target spider-man
doesn't know if his victim will play the
corresponding synced reaction or if he
will block dodge or play some non
synched reaction we actually support
several different options for how
spider-man accomplishes this blending
these can be selected independently for
rotation and translation the most common
case for lining up orientation is called
host a guest or center line in this case
both the host and the guests rotate to
face each other along a line drawn
between them we also have support for
only using the hosts orientation or only
using the guests
neither of these modes are actually used
in the shipping game the other kind of
rotation alignment we use is what we
call an external anchor this is one
another piece of code sets the target
orientation for the system in this video
when you see debug draw on screen both
characters are rotating towards the
external actor orientation we use this
here
because the player is able to push in
any direction on the movement stick
while executing this kick throw and the
enemy will be thrown in that direction
so that is the direction is set as the
sync animation system's rotational
alignment target for translation
alignment we have two common options the
first is when the guest moves into
position this is called host as anchor
and it is used in situations like this
where spider-man is unable to move
you'll be able to see the sync joint
here on spider-man and then you can
notice the enemy move to it the other
kind of alignment is when spider-man
performs all the translation himself
this is called guest as anchor and is
the translation alignment technique for
the vast majority of attacks since
spider-man is the one moving into
position there is an external anchor
option for translational alignment
similar to rotation isn't actually used
in the shipping game a problem we
frequent that frequently came up for us
was that the attacker and the victim
could be at
Heights different from how they were
animated or mo kept we have a few
strategies for how we handle this all
synched attacks have an annotated sync
point and a release point for stealth
takedowns and combat finishers that use
animated cameras we can hide floating
characters by pulling the camera in
before we reach the sync point and
waiting until after the release point to
push it back out in other cases we can
make use of webbing to handle the fact
that the characters aren't at matching
Heights in this case the webs are
animated and then we procedurally alter
them joint by joint so that they match
where their final transforms would be if
our sync joint was properly aligned
another thing that helps with certain
situations is that our character
movement system remains active if one
member of the synced animation begins to
collide with something they can pass the
translation they failed to move over to
the other participant in this case
Spiderman begins to hit the step is
unable to fully align with his target
and when this happens the enemy then a
of that and slides backwards and the two
manage to synch up however we can't
always fix things up and in many cases
we just deal with things being off this
is what happens for most regular melee
attacks as well as a few finishers
enemies can use the system to play
synced attack animations versus
spider-man as well however we found that
the synced alignment options usually
felt unfair the more common use of
synched attacks was for an enemy to use
a non synced attack and then if it
connected to immediately start a synced
reaction animation there are two big
drawbacks to our synced animation system
the first is that it only supports two
characters a host and a guest one of the
most requested features towards the end
of development was finishers and allowed
spider-man to take down multiple foes at
the same time I do want to point out a
more general drawback to using synched
animations is that it quickly becomes
very time consuming to add new attacks
or skeletons to your game
you usually have to make an attack
animation and a react animation for
every victim skeleton how we make use of
a sink joint allows us to mitigate this
if we want to but fidelity will suffer
we had a goal of combat for spider-man
that was fast fluid and dynamic on the
combat team we frequently talked about
plate play style flow and improvisation
as being very important things to us
having enemies that felt dangerous but
gave the player opportunities to flow
was going to be the linchpin to our
combat the topics I'm going to discuss
today are probably very familiar to many
of you
however as programmers we often want to
create a system verified that it's
working as designed and then move on and
this is not how you make good games I
want to share with you some examples of
the iterations we went through to get
our for combat fundamentals right we
needed to manage attacks and mitigate
cheap hits or else the game was going to
turn into this we have to combat
managers that control when ranged and
melee enemies are able to attack and
they occasionally coordinate with each
other but they mostly operate
independently you'll hear me say the
word job a lot in this section in this
case the job is to token that allows a
bot to attack spider-man our manager
from melee started out simple we wanted
there to be one attack job unless the
player had recently dodged and then we
wanted to give him a quick break to
fight back we had a simple scheme
hitting which bot received the attack
job we calculated a priority that was
the sum of the time the job that the bot
had spent waiting for a job Plus how
close that bot was to his ideal attack
range plus a bonus value for high
priority mini boss style enemies here's
what that system looks like in action
there's a blue line drawn from the enemy
who has the attack job to spider-man in
the top-left corner you can see an idle
timer when I dodge that timer becomes
nonzero this gives me a momentary chance
to attack this works alright but there
are still a number of issues the first
is that I don't get attacked nearly as
aggressively as the timer says I should
here's why the enemy that has been given
the attacked
is too far away to quickly execute it
now I do want to emphasize the players
weren't actually doing this but due to
spider-man's agility this kind of
situation could arise naturally as they
bounce back and forth between enemies we
implemented two solutions the first was
that if there was a bot that could
immediately start an attack and a bot
who had been chasing their target for
too long we'd allow the job to be stolen
by the bot who is ready to attack the
second thing we added was the ability
for the manager to occasionally create
an attack job if the player stood too
close to a bot for more than a split
second here's what it looks like with
those features enabled you'll see that
as I run past enemies the blue lines
shifts to me shifts to a nearby enemy
and they quickly try to attack me the
two concepts of job stealing and
proximity attacks were critical for
addressing times when our combat would
suddenly feel dead then we noticed that
things started to feel too predictable
and protracted encounters so we added an
intensity meter this meter fills as the
enemies attack and is spent by the
manager to either simultaneously give
out a few attack jobs or rapidly give
them out in sequence here's what that
looks like in action you can see the
current intensity value here and below
that you can see how much we need to
accrue in order to perform the next
special attack you can see up here what
the next special attack type is and then
you can see the jobs as they are given
out down here so let's talk about ranged
combat it also started out simple there
was going to be a cool-down window where
no shooters could receive attack jobs
followed by an attack window where
shooters could receive attack jobs
during the attack window we had a
maximum number of simultaneous attack
jobs and there was a delay that
prevented the manager from giving out
jobs to rapidly here's what that looks
like up at the top you can see the
manager switch back and forth between
cooldown and actives and then you can
see the status of the jobs here for this
case I've restricted the manager from
giving out more than one job
this kept things from being completely
crazy but there are still a lot of
places where things felt unfair and the
biggest was being shot from off-screen
so we started by saying we're going to
prioritize giving jobs only to on-screen
bots as we can you can see that in
action here you'll notice that as
enemies leave the edge of the screen
that they stop being selected as
attackers however there were still times
when all valid shooters were off-screen
so we made enemies take longer to fire
when they are off-screen this gives the
player more time to notice the
spider-sense warning in this video you
can see that when enemies are off-screen
it lasts for 1.5 seconds when they are
on screen the warning only lasts for
0.75 seconds by now things were starting
to feel decent when fighting only
shooter enemies but things could still
be overwhelming when enemy enemies that
used melee were present so we allow
design to specify an entirely new set of
values if melee enemies were present if
you look here you can see that not only
is the active time slightly shorter but
the cooldown period is nearly doubled
this felt pretty good until the player
started using air combat then with the
slower timing values it felt like
enemies became unresponsive for nearly 5
seconds at a time
we tried disabling the second set of
values when the player was in the air
but players then felt like they were
being punished for using air combat
which was not what we wanted so we added
something that we called air aggression
here it is an action once the player
performs enough actions in the air you
can see the air aggression chance change
from zero to 0.5 this means that for
each additional action the player
performs well in the air there is a 50%
chance that the manager will immediately
exit cooldown when that happens you'll
see era aggression active here
even after all of this iteration players
still felt that there were times when
being attacked was cheap so we made a
list performing any action from this
list would cause the managers to cancel
all attack jobs that hadn't actually
started the attack web throwing an
environmental interactable web striking
a new target using an air launcher on a
target dodging an attack perfect dodging
an attack using a finisher jumping off
an enemy jumping landing being hit and
so in all of these cases we'd cancel
incoming attacks unless you abuse the
action repeatedly and then we'd allow
the attack jobs to persist unless that
action was a dodge in which case we'd
still cancel the jobs unless it was a
boss fight and then the jobs wouldn't be
canceled and that still wasn't enough
players still felt that there were times
that enemies would knock spider-man out
of an attack in a way that felt cheap
this is a specific case that was causing
a lot of frustration players would
properly identify an attacking enemy and
then they'd try to attack that enemy to
interrupt them
but because of animation timing they'd
get hit we created a system we call beat
to the punch which guarantees that
spider-man will win these interactions
when the red sphere is visible the
system is active beat to the punch
activates as spider-man's attacks get
close to the impact frame while active
we examine each incoming attack and see
if spider-man's attack would force a
reaction if it would then spider-man has
granted immunity from that enemies
attack until he's past his own impact
frame you can see it in action here and
as you can see the system will ensure
that in cases where spider-man and an
enemy are attacking each other at the
same time spider-man beats the enemy to
the punch this simple system was the
last thing that we needed to clear up
the vast majority of our remaining
complaints about cheap hits so let's
move on to every AI programmers other
favorite problem positioning we've made
a lot of shooters at insomniac and how
our AI decided where they would move to
was based on the needs of that genre
we annotated positions for BOTS to fight
from using fire points and we marked
large pieces of occlusion by wrapping
them in volumes
this allowed our ranged enemies to take
up positions in cover or at places that
are interesting furthermore melee
enemies in the shooter typically just
try to get close to the player and
attack as often as possible and managing
the Horde and the space is part of the
gameplay in spider-man we wanted the
player to fight a large crowd of enemies
but we didn't want them to feel like
they were continuously being mobbed nor
did we limit want to limit our combat
just to large arenas we also wanted the
more interesting AI classes to be in
position to attack more and we needed to
do all of this while looking good
we started by using a fight circle like
this
we divided the area around the player
into wedges and then we'd repeat with
more wedges for additional rings as
needed we do some Navis to see that each
wedge is on nav and then we'd assign
each enemy to the wedge who's closest to
this didn't work for two main reasons
the first is easily visible in this
video simply moving around the space
could easily cause the wedges to become
invalid this led to enemies frequently
repositioning when it wasn't when there
wasn't a clear reason to the player why
they should do so the other reason why
this failed was because it tended to
look very robotic and choreographed we
tried adding in some random variation
but we were never able to get it to feel
good I'm gonna quickly diagram our
solution let's say we have an enemy
standing here who's engaged with
spider-man standing here then suppose
there are some enemies here and let's
pretend like each bot and spider-man
have kind of a little circle around them
that is their personal space we call
these bot reservations finally the edges
of navmesh are here our goal is to find
the best position for our bot to stand
that is outside of all this red we did
this with a simple algorithm for finding
local minima a gradient descent hill
climber this algorithm will iteratively
follow the gradient of steepest descent
in order to minimize an energy function
in our case energy will increase as we
move towards the center of the circle
and as we move off of navmesh it will be
zero everywhere else another way to look
at this is that we will iteratively push
points that lie in one of the circles
radially out of the circles like so
those points that are often Ave will be
pushed back towards nav like so in our
game each of our bots will actually feed
five positions through the hillclimber
his current position and then based on a
line from the bot to his target and an
ideal distance we can compute a position
in front and a position behind the
target then a line segment perpendicular
to the first segment is used to select a
position to the left in a position to
the right here's how the hill climber
would modify these positions for the one
at the bots current position
it will do nothing but already outside
of the circles and inside navmesh for
the two lower points they eat in a
circle so we'll just push them radially
out of the circle for the point on the
far right we'll push it Ward's navmesh
like so the last point lies between two
circles will try to simultaneously push
it out of both by adding these two
vectors together to get this however
because that won't work we'll need to
iterate a couple times and get to our
final position so at the end we wind up
with five potential positions for an
enemy to choose between and here's what
that looks like in game this video is
showing the results of this enemy's
hillclimber
our bot reservations are shown in red
and the results of the hill climber are
shown as trails of little blue spheres
we cap the number of iterations the hill
climber can perform at 30 we did try
using more complicated algorithms like
sim sim you simulated annealing but we
didn't find them to be of any
qualitative benefit once we've generated
those 5 positions the hill climber tape
creates a score for each based on the
distance to our current position and the
distance to our target we then apply
additional penalties at that point is
too close to the target too far from the
target or if moving there from our
current position would likely cause us
to cross too close to our target we then
pick the position with a smallest score
this worked pretty well for small to
medium-sized encounters
however in large encounters we notice
that standard melee enemies could
prevent the more interesting classes
from being able to get close to the
player this was particularly evident if
those classes appeared in a backup wave
so we partially read
directed the concept of a fight circle
however this time instead of giving out
positions we handed out distances these
distances are used to compute the
initial positions that were fed into the
hillclimber
this lets us buy us those more important
enemies without blowing up the algorithm
in our game there's just two rings an
inner high priority ring and what's
about six guys can stand and an outer
low priority ring where everyone else
gets assigned in frantic in frantic
combat though we still had issues with
enemies repositioning when it felt
unnatural so we added a final check at
the behavior level first we check that
we aren't already too close to our new
position if that check succeeded
we'd compute the distance to our ideal
attack position then compute the
distance between the new position and
the attack position subtracting these
values tells us how much repositioning
will incur will improve our current
situation based on if we would be moving
forward or backwards we compare that
value with different thresholds to
determine if we should move those
threshold values change over time so
that a character who has been stationary
for a long time is more likely to move
we also have a similar scheme for
deciding if an AI should update their
weight position while in motion we
compute the distance to our current
position we compute the distance to our
new position we then compute the
distance from our current to our new and
all of these must be larger than a
threshold value in order for us to
update the destination let's move on to
a few uses of procedural animation in
Marvel's spider-man we wanted mark we
wanted spider-man to be able to use his
webs to stick guys surfaces
unfortunately we have a lot of uneven
and weird surfaces and the AI may be in
an unexpected pose because of rag doll
we couldn't just naively spawn a model
or a decal everything starts with the
game generating an ideal world space
orientation and then passing that into
something we call a web blanket the
blanket itself is a model of a web with
a six by six lattice of joints you can
see it here fully extended and locked in
place when spawned the the blanket will
project rays from a position above the
initial position
and through the position of each joint
on the model if the model was naively
placed at the initial position where
that ray hits is where the joint will be
attached in this video the ray casts are
shown in orange if you look closely you
can see that the blanket is compact when
it's first spawned and then each of the
joints interpret into the position found
by its corresponding ray cast if a ray
cast doesn't hit anything we rotate that
ray towards the blankets initial point
this will usually cause the blanket to
wrap around whatever it is attaching to
additionally joints are allowed to
rescan for a period of time before they
lock in place this allows the blanket to
adapt to a settling rag doll or
animating character without bunching up
on itself we have another case where we
allow joints to rescan and that is if
the thing they are attached to moves too
far from the blankets initial position
this prevents the blanket from being
stretched across the entire level as the
door is torn off here we plan to have
several enemies able to use whip style
weapons versus spider-man
unfortunately we found that a regular
saw cloth sim didn't give us the results
we wanted we also couldn't just animate
the whips because we needed to be able
to reuse animations from other classes
this was especially important for
locomotion and hit react animations
because they're so numerous we needed a
procedural solution that could be fully
animation controlled when necessary the
whips are part of the base model and
they look like this in any animation in
which they haven't actually been
animated when the sim is fully engaged
things look like this for each joint in
the chain we will track its position
from frame to frame and then we'll
naively apply the hosts motion to each
of those joints then we begin to apply
constraints we apply a distance
constraint to correct for this the
constraint uses a 0 to 1 stiffness
parameter and is performed in two passes
forwards down the chain and then
backwards up it we perform half of the
error correction on each pass
without a curvature constraint the chain
falls through two tombstones hand even
though the chains root is oriented
horizontally the first pass keeps each
child joint aligned with its parent the
second curvature pass constrains each
joint to both its parent and its child
this prevents kinks in the chain the
curvature Pass was extremely important
for making sure that the simba joints
blended smoothly out of the animated
ones here you can see tombstone walking
normally we apply a gravity constraint
to keep the chain from floating and we
apply a ground constraint to keep it
from sinking through the floor like it
is here this character or this
constraint uses the normal of the ground
that the character is standing on which
we assumed just defines a flat plane the
final constraint ensures that each of
the joints is outside of the characters
move capsule without it you can see the
wet you can see the whip clipping
through his legs but with it you can see
that the chain will move around his body
the actual order that we apply
constraints is gravity then curvature
then move pill then distance then ground
then we iterate over all of those up to
eight times but ten depending on how
close the character is to the camera for
controlling if the chain is driven by
animation or the sim we start by
assuming that it is always simulated
then we place events on animation clips
when the chain is supposed to transition
from being animate or transition to
being animated and then again when it
transitions back to being sinned these
events just contain a blend speed that
speed is used to change a zero to one
value that represents what percent of
the chain is sinned no joint is ever
driven by both sim and animation we'd
plan to give a lot more control to
animation but we found that we got very
good results with just this and that
more wasn't necessary one of our main
goals for this enemy class was to either
hold spider-man in place or pull him out
of the air therefore we needed to be
able to blend the whip during his attack
animations to arbitrary worldspace
positions and have them look good for
each joint in the whip we
compute where it would be if the enemy
was perfectly aligned with their target
the difference between these two
positions is the error we then apply a
portion of that error to make take the
animated joint to its procedurally
aligned position the portion of that
error we apply increases linearly as we
move down the chain and here's what that
looks like in action this procedural
alignment is controlled with four events
and start blending on finished blending
on we start blending off and we finish
blending off for the last section I'd
like to briefly talk about some of the
things that gave us a lot of trouble we
didn't have a lot of time to spend on
developing TAC for flying enemies and
this really hurt us when it came time to
develop the sable jetpack troopers and
our two flying bosses electro & vulture
we wound up having the jetpack troopers
just hover over nav we then gave them
the ability to go off nav if they could
see their destination while off nav they
kept a list of points that would allow
them to breadcrumb their way back
however even with both of these there
are still times when you can see them
steering around stuff at ground level
and here's an example of how that
happened
this jetpack er wants to go to
spider-man the red capsule indicates the
physics query he performed to see if he
could fly directly to him however
because it clips the wall he's gonna
have to follow this path this blue path
along the ground until he around the
fence until he can actually see
spider-man oops
our flying boss for our flying boss
enemies electro and vulture
they used a collection of volumes that
were linked by splines
inside the volumes the characters could
move freely and they could use the
splines to transfer between the volumes
however all of these had to be hand
authored so it was not a useable
solution for the whole game both of
these solutions got our game shipped but
I hope you can see why they're far from
ideal
we had one mission that had a lot of
combat on top of a moving truck and it
was a never-ending source of pain our
navmesh currently can't move and so we
had to make the space as simple as
possible so that box could reliably
straight find spider-man additionally
many states many states and components
do their did their calculations in world
space and this would cause bugs when
positions wouldn't line up from frame to
frame furthermore our engine doesn't
support attachment as well as something
like unity or unreal and so we had to
use specific components to manually
update all of the attached entities the
final problem we had was that was with
enemies or the player that got thrown
off the truck being chugged through
geometry by the truck if they were
standing next to it we solved this by
placing kill volumes along the side of
the truck as it moves navmesh was
another challenge that we underestimated
we did get some new features for this
project such as the ability to perform
dynamic cutouts from now and this was
instrumental in getting parked cars and
many crimes functional however there was
a creative push to change spider to
change Manhattan with the story as
things got worse for spider-man we
wanted to reflect that in the world
stable international would set up
checkpoints
prisoners escaped prisoners would erect
bases across the island and garbage and
debris would begin to pile up as things
went from bad to worse our navmesh is
built and loaded on a per tile basis we
did not have the time to rework this
because we would have needed the ability
to load different messes based on
various game State and we'd need the
ability to express all of that state at
build time we evaluated generating
navmesh at runtime but we found the cpu
performance to be a non-starter our
solution was to generate nav in its most
complicated state and then try and just
disable parts of it from script here's
an example space this is what it looks
like during act 1 when things are normal
you can see patches of navmesh floating
in the air and the yellow patches you
can see are places where the designer is
going to try and toggle the mesh on and
off from script
you'll also be able to see holes cut in
the match by objects that will be
present in act 3 but are not currently
present now and here's what it looks
like in act 3 once the base is actually
active while this scheme worked well
enough for us to make the base it was
oftentimes buggy enough that we had to
remove crimes or other open-world
activities from happening in a space
like this during act 1 and 2 I'm sure
it's a big surprise to most of you but
we actually had performance problems
running all of our complex AI in an open
world we ultimately had a limit of
around 30 enemies active and engaged
once you've factored in spider-man
traffic and all the pedestrians we
typically saw two large reasons for this
the first is physics
here's a typical frame where things have
gotten bad and I'm going to highlight
the parts of the frame where physics
creeps up as the bots count Rises here
it is on the main thread and then here
it is on two of our four worker threads
it's many many milliseconds spread out
over all the cores and well it isn't all
directly resulting from bots it does
grow proportionally as the bots count
Rises the other thing is that a lot of
our core AI logic still runs on the main
thread here's a capture of our main
thread during a normal frame the
highlighted sections are bought specific
logic and they're consuming well over
four milliseconds of time there's even
more time when you consider that neither
the character move system nor the
animation system are included here
alright I hope that you've learned a few
things today and I'd like to leave you
with a few high-level thoughts the first
is that iteration is king I hope it's
obvious but it took quite a few tries to
get the things that you've seen in this
presentation to where they are now we
almost never got things right on the
first try and that was ok because we
kept pushing with Marvel spider-man we
had the pivot from making shooters to
making character action games when your
problem fundamentally changes like that
you need a fundamental change in your
solutions I know it sounds obvious when
I say it like that but it's actually
harder than it's
pounds in just this talk we've seen how
this affected behavior authoring
positioning and combat management and
there were many many others finally
focus on your games core I believe that
Marvel's spider-man was a success
because everybody mentions how the game
makes you feel like spider-man and this
wasn't an accident this happened because
we took the time to get our fundamentals
right be these player controls attack
pacing enemy hit reacts or just the feel
of using your web shooters you'll get
the best game when you focus on your
core first real quick I'd like to thank
a few people that helped me put this
presentation together
Alicia lyedecker Andrew Richter Brad
Fitzgerald David Kim Alan Ruskin seth
england sean mccabe as well as a very
big thank you to everyone at insomniac
games Marvel and Sony I'd like to remind
you all to please fill out your speaker
evaluations and thank you for listening
and if there are any questions I think
we've got two mics set up here
[Applause]
hi this game was ridiculously awesome so
thank you very much and so one thing I'm
struggling with a lot in my game and I'm
one of the people who like to torture
myself and played it on hard mode the
first time so so what sort of parameters
you have to change the difficulty level
in like hard and normal mode so if you
remember I pointed we had the whole
section on timings that all of those
timings can be changed per difficulty
level perfection and per act of the
story so the the rate at which enemies
attack the amount of time that the game
gives you in between successful dodges
all of that changes additionally we
would usually go through on a per enemy
to enemy basis and identify one or two
parameters that we could tune to to make
things much more difficult an example of
this would be ranged enemies tend to
fire in like a five to seven round burst
and on easy
they will deliberately miss with like
the first six of those and then on hard
they'll only deliberately miss with the
first two or three so it kind of further
requires the player to react a little
bit faster cool thanks you're welcome
hi great presentation thank you so doing
when you're playing game there are
different factions that come across each
other so how is the AI set up for that
and how do different factions react when
spider-man jumps into play so we have a
kind of agro based targeting solution
for how enemies choose which targets to
attack like usually in a multi faction
fight one set of enemies is aware of the
other set of enemies as potential
targets and spider-man and then there
are rules based on how close
spider-man is to the target versus how
close other targets are that caused what
we call a proximity aggro amount to
increase and then every time those
enemies take damage from another target
that also adds additional aggro on top
of on
of that and whoever has the highest agro
is a BOTS target thanks you're welcome
hey hello
so what exactly was the moment when you
guys realize that the system you had for
sunset overdrive and ratcheting flank
and such wasn't going to work it was
probably about a year and a half into
production as we were finishing
pre-production and looked at how many
enemy classes we had made and how many
enemy classes we still need to needed to
make and we're like well we've made
about four we've got somewhere in the
realm of 60 to go we need we need to
pick up the pace thank you you're
welcome
hi similar to her question how do you
know when you need to design a more
complex system or what you're talking a
lot about the model and data protector
that you guys had created and how do you
know that you needed to iterate from the
previous one and get to there and then
how do you know when you got there that
that would be good enough to not need to
iterate further to even essentially
better architecture honestly trial and
error it kind of happened because we
would be getting feedback from our
design team about hey we want to be able
to do XY and Z and they would constantly
come back to us wanting to be wanting to
say things like hey can you move this
attack from this character to this other
character or can we have them play this
animation instead or can they can they
select between all of these things and
just looking at okay the kind of
requests that we're getting are this
therefore we could figure out a way to
do all of that in data so it's as quick
as possible to do the iteration that
they're asking us to do and when we kind
of got to the point that okay we're
actually able to handle seventy-five
eighty percent of the stuff that design
is asking us for that's around where we
stopped thank you how are we on time
I really awesome top thank you thank you
I'm wondering what would you say is the
the difference between like those
systemic enemies in the world and
between them and uh phosphites that you
guys made it's like are the boss fights
are mainly like heavily scripted
authored or um the boss fights still use
the kind of combo systems that we have
they they typically have so we had we
had 39 combo moves and probably about
half of those are for regular combat and
the other half are boss specific attacks
and bosses usually had their own engaged
behaviors so we talked about behaviour
melee which is what the normal enemies
use most of the bosses would have their
own engaged behavior like Rhino had his
own and scorpion had his own what's kind
of described how they would move about
the environment but then wherever they
whenever they got into position to use
an attack they would reuse the kind of
combo stuff that was driven by a
designer to say you know do a charge or
throw something or jump to another
position thank you you're welcome how do
you divide the work between your
designers and your programmers do the
designers work in behavior trees or in
the finite state machines or just in
data they just work with data they don't
our designers don't work with behavior
trees or finite state machines