Lets face it, monster AI in most games leaves a lot to be desiredā¦.. the beasties are either too stupid, too predictable or just so uninteresting that the game becomes an endless, repetitive hack and slash. I donāt object to mindless violence, of course. But it would be nice to think that some the talented coders out there could come up with something a little more impressive and atmospheric.
What Iām going to suggest here is a system of āplug-inā AI that might just make doing some interesting monsters a little bit easier.
Iām going to write the examples in Java. This is partly because itās my new language of the moment. But mainly itās because having a good object-oriented model with garbage collection makes life much more pleasant. Java also lends itself to making quite elegant solutions to problems of this type.
Whatās good about this model? Here are the nice pointsā¦..
Fantastic, you say, but what is the downside? Well, it is quite a difficult concept at first, and it needs quite a lot of care in the implementation. But that, of course, is where the real fun starts. :)
The basic idea is this: Each mobile (creature) in the game is represented by an object that maintains a reference to a separate AI object.
The basic stripped down class definitions are:
// this is the definition for our basic mobile class
class Mobile extends .... implements ..... {
// this is the key line.... a reference to the current AI object
public AI ai;
public void setAI(AI newAI) {
ai = newAI
// previous AI object will be automatically garbage collected
// and some other stuff as required
}
// All other fields and methods
// e.g. stats, inventory
}
// And this is the generic AI object from which all other AI objects
// will inherit
class AI extends Object implements ...... {
public void action(Mobile m) {
//This is where the intelligence goes
}
// other AI functions go here, e.g psychology effects etc.
}
Whenever it is the particular mobileās turn to move, you just make the call
monster.ai.action(monster);
This instructs the AI object to work itās magic, and make the monster perform whatever behaviour it is currently following.
Well, thatās the basic theory, now we move on to:
So far so goodā¦.. but whatās the point? Well, what we want to achieve is a way of quickly and simply specifying a creatureās behaviour. More importantly for realism and the sake of interesting AI, it allows us to change the behaviour of creatures dynamically as circumstances dictate.
We can do this by creating subclasses of the AI object, inheriting all the generic behaviour and adding whatever specific behaviour we desire. The most obvious AI to implement is that of the standard monster, who justs moves towards the Hero and attacks automatically.
class AttackPlayerAI extends AI {
// Just override the action method
public void action (Mobile m) {
//call standard monster movement function if player is visible
if m.canSee(Hero)
{
m.moveTowards(Hero.x,Hero.y);
} else {
//Just lurk here and do nothing
}
}
}
If we now want to create a kobold at location (10,10) with this simple behaviour, we just write:
Mobile m = new Kobold(10,10);
m.setAI(new AttackPlayerAI());
Et voilaā¦. one particularly unfriendly Kobold has just been summoned into existence.
As a second example, this class makes a creature that walks between two points, e.g. a guard patrolling a side of a castle.
class GuardAI extends AI {
// these hold the co-ordinates of target squares (tx1,ty1) and (tx2,ty2)
int tx1;
int ty1;
int tx2;
int ty2:
// direction==1 means move towards target 1 else move towards target 2
int direction
// Constructor for AI to walk between (x1,y1) and (x2,y2)
public GuardAI (int x1, iny y1, int x2, int y2) {
tx1=x1;
ty1=y1;
tx2=x2;
ty2=y2;
direction=2; //start walking from 1st to 2nd target
}
// Now override the action method
public void action (Mobile m) {
//work out current destination
int tx = (direction==1) ? tx1 : tx2;
int ty = (direction==1) ? ty1 : ty2;
//call standard monster movement function
m.moveTowards(tx,ty)
//change direction if target reached
if ((m.x==tx)&&(m.y==ty)) {
direction = (direction==1) ? 2 : 1;
}
}
}
Now to create a guard that patrols between (10,10) and (20,5), just use:
g = new Guard(10,10);
g.setAI ( new WanderAI(10,10,20,5) );
If the player attacks the guard, however, then we want to change the guard into a hostile creature, represented by a different AI. This is done simply as follows:
g.setAI ( new AttackPlayerAI() );
where AttackPlayerAI is just a standard AI descendant that makes the creature charge towards the player in a berserk rageā¦ā¦
Of course, inheritance is a very useful concept in programming, and never more so than here. Withg only minor modifications, we can make a HostileGuard who patrols like a normal guard but attacks the player as soon as he/she is visible:
class HostileGuardAI extends GuardAI {
// override action
public void action(Mobile m) {
if (m.canSee(Hero)) {
// Charge! Install new AI into the mobile.
m.setAI (new AttackPlayerAI());
Hero.message("The " + m.name() + " yells with rage!");
} else {
// call parent method to continue patrol....
super.action(m);
}
}
}
The really neat thing about this system is that it is almost infinitely extensible. You can make a new AI object for almost any type of monster imaginable. Just a few ideas for AI objects:
Stays still and casts spells at the player UNLESS there are no cretures between it and the angry hero, in which case it makes for the nearest escape route.
Sleeps for time periods, then gets up uses the new AI object given. Brilliant for a mesmerize spell, for example, that makes the creature friendly upon waking.
Waits on the same spot, until the player comes within a specified distance. Then does something completely different, e.g. run away, attack, or offer to tradeā¦..
Run away! This AI object makes sure that the creature stays as far away from the player as possible. You could give the creature a valuable object, and make it play a fun game of hide+seek all round the dungeon.
Use firstAI for the specified number of time periods, then swotch to secondAI. Nested SequenceAI objects could be used to create complex behaviour.
The list of possibilities is endlessā¦ā¦.
Having thought about it for a while, I think that psychological effects such as certain spells should probably be passed to the AI object, which can deal with them as approprite. For example, UndeadAI might filter out any panic or confusion effects. If necessary, the AI could react by installing a new AI object. For example an initially hostile goblin who is hit by a panic spell could be given a FleeAI object.
It is probably useful to create a default AI for each type of creature, so that you donāt need to explicitly create a new AI object whenever you create a mobile. Behaviour could also be allowed to return to this default AI when psychological effects wear off.
Thereās lots of things that could be added to this AI model. Iād like to describe just one of them very briefly that I think could be a useful way of defining behaviour for roguelike creatures.
Since each mobile contains only a reference to itās AI object, the thought naturally occurs that several mobiles could share the same AI object.
If this was a simple AI with no internal data, e.g. AttackPlayerAI, this would make no real difference, apart from saving the overhead of constructing a seperate AI object for each hostile monster.
But you could create a special set of āGroupā AI objects that would allow a set of monsters to act as a pact, with a built-in flocking instinct and some idea of co-operation in battles. Basically, you would construct a single AI object and assign it to all the monsters in the group, who would then take on the relevant shared behaviour.
e.g.
AI ai = new HositlePackAI ();
Mobile m1 = new Orc(10,10);
Mobile m2 = new Orc(10,10);
Mobile m3 = new Goblin(10,10);
Mobile m4 = new Goblin(10,10);
Mobile m5 = new Troll(10,10);
m1.setAI(ai);
m2.setAI(ai);
m3.setAI(ai);
m4.setAI(ai);
m5.setAI(ai);
This kind of system would be brilliant for outdoor skirmishes. You could monsters capable of laying an ambush. You could have them charging and retreating as a single unit. I leave it to your imagination to dream up all of the evil ways you could get fighters to flock in defence of a vulnerable mage,
Well, I hope this gives a little bit of inspiration to everyone out there who is trying to craft the ultimate creature from the pit. I hope that Iāve shown that although thereās a lot of different things that you need to consider when building an AI system, the problem really can be manageable and flexible if you find the right structure.
Happy Coding!
Mike Anderson
mike@mikera.net
5th March 1999