Personality Parameters: Flexibly and Extensibly Providing a Variety of AI Opponents' Behaviors
How do you distinguish the behavior of different AI characters? Tweaking the stats they have in common (health, armor, damage) is a common technique, but it's not very extensible. This article presents a flexible way to imbue AI agents with a rich palette of behaviors using personality data files.
A common problem for game AI programmers is how to distinguish the behavior of different AI characters. As an example, how do we make an orc and a dragon genuinely different as opponents? We can increase the health, armor, and damage of the dragon, but unless we give it a different personality in some way, and make it behave differently, it might as well just be a very strong orc.
The techniques described in this article are applicable to a wide variety of game types. I have applied (and refined) them over the course of developing a console FPS (Men in Black: Crashdown), a PC and console board game (Monopoly Party), and now a PC and console, real-time, turn-based, strategy/action game (Worms 3D). The specific methods described are most applicable to games which have a variety of different kinds of AI agents. The most obvious examples of games requiring agents with differing behaviors are probably RPGs, with various races of enemies, or character classes; and FPSs with various species/types of opponent.
This article will present a flexible, robust, and easily extensible solution to the problem of how to give your AI agents a rich palette of behaviors, as well as a sense of character and personality.
Same Code, Different Data
A much-discussed concept in game programming is writing systems to be "data-driven" (see references at the end of the article), and this is a perfect place to put the concept into practice. Essentially the idea is to program your system to work according to data fed into it, so that the same code can produce very different results by changing the parameters it is given. The advantage of coding this way is that program behavior can be changed without altering the code at all -- by simply altering the data it is fed. This means that agent behaviors can be tweaked, or even completely changed or created, without having to edit and recompile code, interrupt a programmer (it can be done by a level designer), or even exiting from the game, if you include the ability to reload the data the engine is using on the fly.
The disadvantages of writing a system to be data driven, instead of using hard-coded values, is that it will run marginally slower (although this will be negligible for the systems described in this article), but that more importantly, you will need to spend a bit longer up front, designing and coding your system. Even starting from scratch, if you are writing a game that will have several types of AI characters, the time you save in the long-run by designing your system to work in this way will far outweigh the extra time required at the beginning. (See the sidebar, "Data Format", for a discussion regarding what form your data should be in.) My hope is that this article should give you a head-start with designing your system, and cut down the time you'll spend up front.
An important thing to realize about data-driving is that well chosen parameters can combine in unexpected ways to give greater variety than was originally visualized. In other words, if you give the power to get your code to do different things to other people, then they will find ways of making it do things you never intended it to do. This can be bad -- it will certainly expose some bugs in your code -- but it can also be very good, as a flexible enough system will allow the designers feeding your system with data to exercise their imaginations, without being limited by yours.
But a dragon and an orc shouldn't just have different health and armor; they should behave differently. Of course, in a full-blown game orcs and dragons will have different models, animations, visual effects, sound effects, rewards for defeating them, and so-on, but if a dragon just behaves like a very tough orc, the game world will inevitably feel flat and artificial. Encountering the two types of opponents should be very different experiences, and should require very different kinds of response from the player.
Asset Parameters
The next aspect of most AI characters that we'll consider has already been mentioned in Example 2, namely its weaponry. Most AI characters in games have weapons of some sort, except of course those guys who like hanging around in taverns or star-ports, even if it might be a claw or psion-blast, instead of a gun or axe. Naturally then, we'll want parameters for the weapons an agent has. The first ones you'll think of are probably "damage", "range", "accuracy", and maybe "time between hits", but for a full-blown game you'll want to include a lot more. The weapon may have a separate model made for it (if that kind of agent can use more than one weapon), in which case you'll want to include the name of the weapon model as one of your parameters rather than having a big hard-coded switch statement somewhere saying which weapon model each character uses. But don't stop there. How about having parameters for the animation to be played when firing the weapon, and the name of the particle effect when it is fired, and when it hits? Don't forget all the sound effects as well.
Maybe all these extra parameters sound like a lot of hard work? Wouldn't it be easier to just hard-code the names of the assets in? It certainly would be easier for a game just using two or three kinds of characters, but if you want to have more, and you want others to be involved in creating them (which will probably improve your game in the long-run), then it will save huge amounts of time to encode all this information into your data file. If you do, then someone else will be able to add a weapon, with all the graphical and audio assets, without ever bothering you.
Obviously you'll want to have the details of all the assets (models, animations, sound effects, particle effects, etc.) for the actual agent included in the data file too. Strictly speaking, these sort of things aren't part of the AI, but it certainly makes sense to put them all together from the point of view of game content creation. Before long you'll be playing the game against AI opponents that you've never seen before, and had no immediate hand in creating, even though they're using your code. If your design teams are talented and can get to grips with the tools you provide them with, this can be a very exciting prospect.
______________________________________________________
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: and "dragon.dat" containing: 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: 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: 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 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: 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: |
______________________________________________________
Debugging and Logging
With all the flexibility we have created, and the open-ended nature of the system, which runs finite code but is driven by infinite varieties of personality parameters passed to it, have we created a debugging nightmare? The answer is, of course, "yes!", but we didn't want to make a simple, linear and predictable AI, which would have been easy to debug. What we do have the framework for is a complex, colorful, sometimes unpredictable, but plausible AI which can operate a great variety of characteristic agents. There are two classes of bugs which we are most likely to come across related to this system. The first is "that character shouldn't be able to do that" type bugs. Examples would be characters walking through locked doors, waving their axe at you from 200 meters away and doing you damage--and other things which fly in the face of the way your game world is meant to work. The best way to avoid these bugs is to separate out the code modules which decide what the character wants to do, and the code which actually makes these actions happen in the game world. This second code module can then be the enforcer of the game rules, so that even if the character wants to walk through the locked door, it won't be allowed to (of course, it is better if the character realizes that the door is locked and stops bashing its head against it).
The second, and probably more tricky, certainly more nebulous, type of bugs will be those of the "why did that character do that?" kind (for example, "why does that character keep trying to use its crossbow even though it has run out of bolts?"). These bugs are likely to be trickier to fix because they are dependant on both the code which selects different behaviors, and the personality parameters being passed in. They are likely to be more nebulous, because sometimes you may be unsure if what you are seeing is a bug or not. What the character is doing may be what the designer wanted, even though it looks dumb to you. On the plus side, bugs of this sort are often not spotted by the lay observer, so that what to you is an annoying bug, to the average player can appear to be a sophisticated emotional response of the agent who is so scared by the violence of the battle that they keep trying to fire their crossbow a couple of times before they realize all the ammo is gone (believe me, I've seen it).
The best way to attack the second kind of bugs is probably by having a debug-logging system. The AI system in Worms 3D logs all its evaluations of the various plans it is considering, and why they were scored up or down. It logs what decisions it came up with, and what actions were carried out as a result of those decisions. The system has various "verbosity" levels, so that it is able to create these logs in a release build given to QA, with minimal speed impact, but can be run in verbose mode when debugging in greater depth, to give full details of everything it did. A logging system for a game with many agents active at the same time may want to have a separate log file for each agent, or it may be more useful to keep them together and provide a custom "reader" program which is able to filter the log by agent. Once you have a good logging system, you will be able to dissect the behavior of your characters at leisure, and, if your testers can be persuaded to add the AI logs to any bug report related to the AI, you'll have evidence and additional information for each bug they find.
Extensions
The beauty of the system that has been described is that it is very flexible and easily extended. One area that I have not discussed so far is that of multi-agent behavior, i.e., squad behavior, inter-agent communication, and so-on. The system most naturally lends itself to an "artificial life" (emergent/bottom-up) approach, which could easily be achieved by adding decision parameters which took account of other nearby agents (e.g., a "ring alarm bell" behavior could be scored down if another nearby agent was already running a "ring alarm bell" behavior). An alternative approach to squad behavior would be to have a nominated leader amongst a group of agents (which might, or might not, be a different type of character), who could have a "pass command" behavior (with appropriate grunts and gesticulations if so desired).
Conclusion
By designing your AI system to be driven by personality parameters provided in data files for each kind of agent, you can make possible a much greater quantity, variety, (and probably quality) of AI characters than would be possible by hard coding them in by hand. With thought, it is possible to provide great flexibility and extensibility to the data files, so that whole character types can be added without programmer intervention.
By including parameters which drive the choices between behaviors for the characters and the details of their behaviors, the same AI system can run a huge variety of genuinely varied and distinguishable agents, which not only look different, but have different personalities and abilities.
For Further Information
Data Driving:
"The Magic of Data-Driven Design", in Game Programming Gems, Steve Rabin.
"A Data-Driven Architecture for Animation Selection", in AI Game Programming Wisdom, Jeff Orkin.
AI Behavior Architecture:
Variety In Agent Behavior:
http://www.gamasutra.com/features/designers_notebook/20001222/adams_01.ht
______________________________________________________
Read more about:
FeaturesAbout the Author
You May Also Like