|
Features

Personality Parameters:
Flexibly and Extensibly Providing a Variety of AI Opponents' Behaviors
Extensible
Parameters
We've
just considered adding parameters for weaponry in our data file,
but what should we do about characters with more than one weapon?
We could provide slots for a fixed maximum number of weapons, which
may or may not, all be utilized for any particular character. However,
a more flexible solution would be to encode our parameters in a
format which would allow us to arbitrarily add as many weapons as
we wanted (see side-bar). If the format is well thought through
it should allow us to add extra sound effects for more variation
in the sound of the gun fire, or the footsteps, to add extra idle
animations, and also to add extra behaviors. But we'll come on to
that later.
As
an example of where we've got to, with the parameters we have so
far, it would be a simple matter for someone else to take the parameters
for an orc and create a new parameter file for an orc commander
who has a different model, some different animations and sound effects,
better armor, and a crossbow as well as the standard sword.
Decision
Parameters
There
is one problem with our orc commander though: how does he know
which weapon to use at any particular point in combat? The problem
is, of course, increased as we add extra weapons to a character,
and if we're going to be adding weaponry at will, we'll need a
very flexible solution to this decision problem. A mechanism I
have found to be very effective is, given a particular choice
for an AI character, score each of the alternative options, and
whichever has the highest score is the winning choice. The most
basic scoring system would be to just randomly choose any one
of the available options. This would be very simple to implement,
and would show off the versatility of the character, but they
would quickly be seen to be pretty unintelligent.
A
better method would be to have the score for an option dependant
on the current situation, perhaps using compound fuzzy switches
(products of values between 0 and 1) -- see example
4. Another option, which could be used along with fuzzy switches,
would be to access script functions (if your game has a scripting
language) passing a reference to the character, along with whatever
other information might be helpful, and getting back a value from
0 to 1 which can be multiplied with all the other fuzzy switch
values. A common problem with decision making, or state changes,
in AI characters, is that it is sometimes very predictable to
the player where the decision boundary is. To avoid this predictability
it is easy to multiply each option's score by some random scale
whenever they are assessed (perhaps between something like 0.8
and 1.2), thus making the boundary between choices a little more
blurred.
Behavior
Parameters
The
decision parameters above leave some questions un-answered.
When should the character reassess the weapon it's trying to
use? How does the character know how to use a particular weapon
(e.g., move in close for a sword, pull back to the best range
for a crossbow)? Also, we'd really like AI agents that are able
to do more than just attack the player. Even if they can use
more than one weapon, just flying into battle all the time would
make our characters very predictable, and rather boring--certainly
not very believable as thinking beings. We'd like our characters
to attack, side-step, fall-back, run-away, fire from cover,
ring an alarm bell, etc. My favorite little AI behavior was
demonstrated by the lower-level soldiers in the original PlayStation
game Medal of Honor, who dove on a grenade you just threw
at their feet, attempting to protect a nearby officer. It also
adds immensely to the player's belief in the characters if they
aren't always standing still in the same corner waiting for
him, but actually seemed to have a (limited) life of their own
before the player turned up. Halo is an excellent example
of this, in which Covenant aliens are always in a slightly different
place and doing something slightly different every time you
restart from a check point.
All
the above behaviors, and as many more as you can think of, can
be flexibly added to our AI characters using parameters in our
data files. In the same way as we added arbitrarily many weapons
to our characters, we can add an arbitrary number of behaviors
as well. Each behavior can be given a "type"
value which defines which pieces of code will be run to carry
the behavior out. Depending on the behavior type, there will be
other parameters we may wish to add to customize the behavior
more accurately for the character in question. For example, we
may have a "useWeapon" behavior, which can take a "range"
parameter, which defaults to hand-to-hand range, but can be set
to whatever we want. We can then use the range parameter to set
the distance from which our orc commander will try to fire his
crossbow bolts. If we just give a "range"
parameter though, we may have the problem that any orc commander
in our game will always go to exactly the same distance away from
the target before firing, damaging our illusion of thought. One
way around this predictability would be to build into the "useWeapon"
behavior, variability in the range the character tries to use
a weapon from (so it takes the "range" parameter and
multiplies it by a random number between 0.8 and 1.2). More flexibly,
we could provide a parameter in the data file which specifies
the variability in the range, which defaults to +/- 0.2, but could
be overridden to be whatever we want for that character.
For
each behavior, we can provide a set of decision parameters (as
described in the previous section). Whenever our character then
needs to choose a behavior to carry out, it can score each available
behavior according to the current situation, and action the behavior
with the highest score. We'll want as rich, but meaningful, a
set of decision parameters as possible to help our character decide
between possible actions. The more carefully we set up the decision
parameters, the more intelligent the character's choices will
appear to be. Not all the decision parameters need be purely dependant
on the current state of the game world though. As an example,
in Worms 3D, the AI certainly considers many different
world conditions when choosing between plans, but also takes into
account things like whether it has tried a similar plan before,
and failed, and even whether the weapon it is thinking about using
has been used recently (to help add variety to the game experience,
and avoid the AI getting into a rut)--of course these two considerations
may well not make sense in all game types. See figures 1-3 for
some debug graphics illustrating the AI in Worms 3D considering
alternative plans.
An
obvious question, begged by adding decision parameters to our
behaviors, is when should we score all our behaviors to choose
a new one? The answer certainly isn't every frame. Re-scoring
behaviors every frame will, of course, be slow, particularly if
we have many agents active concurrently. But it also introduces
the danger that we may end up with schizophrenic characters, who
constantly change their minds about what they want to do and vacillate
between different actions. A more natural system would be to reassess
the characters' behaviors when an action terminates, and also
every quarter-second to second (again I'd recommend a randomness
factor in the time between reassessments). This will mean that
if the situation around the character changes, in a way they should
respond to, they will take note fairly quickly, but there will
be a short unpredictable "reaction-time" built into
the system. A great advantage of assessing the behaviors so infrequently
(roughly every 15 to 60 frames in NTSC) is that the system can
be designed to stagger the processing between characters, deferring
the calculations for a character to the next frame, if another
character has already been assessed this frame. Use of this, and
similar techniques, can smooth the processor load from the AI
to a great extent, allowing you to get more thinking done, with
less frame-rate hit, than an un-smoothed system--but that's a
different topic.
|
|
Example
1
As
a very simple example, consider the two parameters "health"
(or hit-points) and "armor" (an amount taken
off any damage done to the character). Obviously, to make
a weak character (e.g., an orc) you give it low health
and low armor. Conversely, for a strong character (e.g.,
a dragon) you give them oodles of health and high armor.
What could be simpler? But what about a Jabberwocky with
low health, but very high armor. This character could
decimate thousands of villagers unscathed, but would fall
to one good hit from a high-rank Paladin, something which
the programmer may not have foreseen when she originally
wrote the system.
To
give an idea of what our starting data files might look
like at this point, we might have two files, "orc.dat"
and "dragon.dat" with "orc.dat" containing:
name
= "orc"
health = 10
armor = 1
and
"dragon.dat" containing:
name
= "dragon"
health = 500
armor = 10
Example
2
Probably
the next most obvious, and easiest, parameters to add
to your data file for an agent are its movement characteristics.
For example: "forward speed", "side-steep
speed", "backward speed", and "turn
speed". These values will certainly want to be different
for different kinds of agents, and their differences will
immediately cause them to behave in distinguishable ways
and require different tactics from the player. As an example,
a high forward speed but low turn speed opponent will
encourage the player to use circle-strafing (in an FPS),
whereas a high turn speed but low forward speed opponent,
with close range weapons, will elicit playing styles involving
the use of ranged weapons, and repeated falling back.
One
particular alien race in Men in Black: Crashdown
(the Kayzor) was a smallish crab/scorpion type thing,
with one eye, which came alive during development when
it was given a slow forward speed, but a very fast sideways
motion. This tended to mean it was always circle-strafing
the player, and made it an unusual and challenging enemy,
especially in packs.
Example
3
As
an example of how our orc commander data file might look,
we might have a file "orc_commander.dat" containing
something like the following:
name
= "orc commander"
model = "orc_commander.mdl"
health = 20
armor = 3
moveForward = 1.3
...
START weapon
name = "sword"
model = "orc_sword.mdl"
damage = 6
range = 5
animation Idle = "orc_hold_sword_breathe.ani"
animation Attack = "orc_attack1.ani"animation
Attack = "orc_attack2.ani"
...
END weapon
START weapon
name = "crossbow"
model = "crossbow.mdl"
damage = 4
range = 120
...
END weapon
with
"..." meaning that some of the imagined file
has been omitted for brevity.
Example
4
Given
our orc commander with a sword and a crossbow, we could
encode the decision about which weapon to use in a situation,
in the following way:
FS_range
= FuzzySwitch( range,
weapon.rangeScale,
weapon.rangeBase
)
FS_armour
= FuzzySwitch( armor,
weapon.armorScale,
weapon.armorBase
)
Score(
weapon ) = FS_range * FS_armor
where
range is the distance of the target from the character,
armor is the armor of the target, weapon.parameter means
the value given to parameter in the description of weapon
in the character's data file, and
FuzzySwitch(
val, scale, base ) =
clamp( (val - base) * scale, 0.0, 1.0 )
Each
weapon could be scored using its parameters from the data
file, and the one with the highest score is chosen for
use. Example values might be:
sword.rangeBase
= 100
sword.rangeScale = -0.01
sword.armorBase = 0
sword.armorScale = 0.1
crossbow.rangeBase
= 0
crossbow.rangeScale = 0.01
crossbow.armorBase = 5
crossbow.armorScale = -0.2
In
the situation with an opponent who is 20 units away and
has armor of value 3, we'd have Score(
sword ) = 0.8 * 0.3 = 0.24, and Score(
crossbow ) = 0.2 * 0.4 = 0.08, meaning the sword
gets selected. With an opponent further away, say 90 units,
and less armor, say 1, we'd get Score(
sword ) = 0.1 * 0.1 = 0.01,and Score(
crossbow ) = 0.9 * 0.8 = 0.72, meaning that the
crossbow gets favored.
Example
5
Putting
together everything we've discussed, a very abbreviated
personality data file for an orc might look something
like the following:
name
= "orc"
model = "orc.mdl"
health = 10
armor = 1
moveForward = 1.1
...
START weapon
name = "sword"
model = "orc_sword.mdl"
damage = 6
range = 5
animationIdle = "orc_hold_sword_breathe.ani"
animationAttack = "orc_attack1.ani"
animationAttack = "orc_attack2.ani"
...
END weapon
START behavior
name = "attack with sword"
type = "attack"
weapon = "sword"
scoreRangeBase = 100
scoreRangeScale = -0.01
scoreArmorBase = 0
scoreArmorScale = 0.1
...
END behavior
START behavior
name = "run away"
type = "moveAwayFrom"score
HealthBase = 10score
HealthScale = -0.05
END behavior
...
|
 |
 |
 |
|
______________________________________________________
|