Difference between revisions of "Plug-In Monster AI"

From RogueBasin
Jump to navigation Jump to search
 
(Wikified ;-))
 
Line 1: Line 1:
<pre>
Lets face it, monster AI in most games leaves a lot to be desired.....
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  
the beasties are either too stupid, too predictable or just so  
Line 18: Line 17:
What's good about this model? Here are the nice points.....
What's good about this model? Here are the nice points.....


- Allows for an infinite variety of friendly and unfriendly behaviour
* Allows for an infinite variety of friendly and unfriendly behaviour
- Easily extensible
* Easily extensible
- Relatively little extraneous coding, so you can focus on the AI itself
* Relatively little extraneous coding, so you can focus on the AI itself
- Once created, monsters can just take care of themselves
* Once created, monsters can just take care of themselves
- No huge switch statements (Hooray!!)
* No huge switch statements (Hooray!!)


Fantastic, you say, but what is the downside? Well, it is quite a
Fantastic, you say, but what is the downside? Well, it is quite a
Line 29: Line 28:




The AI Model
== The AI Model ==
============


The basic idea is this: Each mobile (creature) in the game is  
The basic idea is this: Each mobile (creature) in the game is  
Line 38: Line 36:
The basic stripped down class definitions are:
The basic stripped down class definitions are:


// this is the definition for our basic mobile class  
// 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
}


class Mobile extends .... implements ..... {
// And this is the generic AI object from which all other AI objects
 
// will inherit
  // this is the key line.... a reference to the current AI object
  public AI ai;
class AI extends Object implements ...... {
 
  public void setAI(AI newAI) {
  public void action(Mobile m) {
    ai = newAI
    // previous AI object will be automatically garbage collected
    //This is where the intelligence goes
 
    // and some other stuff as required
  }
  }
 
  // other AI functions go here, e.g psychology effects etc.
  // 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
Whenever it is the particular mobile's turn to move, you just make
Line 83: Line 78:
Well, that's the basic theory, now we move on to:
Well, that's the basic theory, now we move on to:


 
== Real-World Example ==
Real-World Example
==================


So far so good..... but what's the point? Well, what we want to achieve
So far so good..... but what's the point? Well, what we want to achieve
Line 97: Line 90:
justs moves towards the Hero and attacks automatically.
justs moves towards the Hero and attacks automatically.


class AttackPlayerAI extends AI {
class AttackPlayerAI extends AI {
  // Just override the action method
  // Just override the action method
  public void action (Mobile m) {
  public void action (Mobile m) {
    //call standard monster movement function if player is visible
    //call standard monster movement function if player is visible
    if m.canSee(Hero)
    if m.canSee(Hero)
    {
    {
      m.moveTowards(Hero.x,Hero.y);
      m.moveTowards(Hero.x,Hero.y);
    } else {
    } else {
      //Just lurk here and do nothing
      //Just lurk here and do nothing
    }
    }
  }
  }
}
}


If we now want to create a kobold at location (10,10) with this simple
If we now want to create a kobold at location (10,10) with this simple
Line 122: Line 115:
points, e.g. a guard patrolling a side of a castle.
points, e.g. a guard patrolling a side of a castle.


class GuardAI extends AI {
class GuardAI extends AI {
  // these hold the co-ordinates of target squares (tx1,ty1) and (tx2,ty2)
  // these hold the co-ordinates of target squares (tx1,ty1) and (tx2,ty2)
  int tx1;
  int tx1;
  int ty1;
  int ty1;
  int tx2;
  int tx2;
  int ty2:
  int ty2:
  // direction==1 means move towards target 1 else move towards target 2
  // direction==1 means move towards target 1 else move towards target 2
  int direction   
  int direction   
 
  // Constructor for AI to walk between (x1,y1) and (x2,y2)
  // Constructor for AI to walk between (x1,y1) and (x2,y2)
  public GuardAI (int x1, iny y1, int x2, int y2) {
  public GuardAI (int x1, iny y1, int x2, int y2) {
    tx1=x1;
    tx1=x1;
    ty1=y1;
    ty1=y1;
    tx2=x2;
    tx2=x2;
    ty2=y2;
    ty2=y2;
    direction=2; //start walking from 1st to 2nd target
    direction=2; //start walking from 1st to 2nd target
  }  
  }  
 
 
  // Now override the action method
  // Now override the action method
  public void action (Mobile m) {
  public void action (Mobile m) {
    //work out current destination
    //work out current destination
    int tx = (direction==1) ? tx1 : tx2;
    int tx = (direction==1) ? tx1 : tx2;
    int ty = (direction==1) ? ty1 : ty2;  
    int ty = (direction==1) ? ty1 : ty2;
 
    //call standard monster movement function
    //call standard monster movement function
    m.moveTowards(tx,ty)
    m.moveTowards(tx,ty)
 
    //change direction if target reached
    //change direction if target reached
    if ((m.x==tx)&&(m.y==ty)) {
    if ((m.x==tx)&&(m.y==ty)) {
      direction = (direction==1) ? 2 : 1;
      direction = (direction==1) ? 2 : 1;
    }
    }
  }
  }
}
}
 


Now to create a guard that patrols between (10,10) and (20,5), just use:
Now to create a guard that patrols between (10,10) and (20,5), just use:
Line 162: Line 153:
   g = new Guard(10,10);
   g = new Guard(10,10);
   g.setAI ( new WanderAI(10,10,20,5) );
   g.setAI ( new WanderAI(10,10,20,5) );


If the player attacks the guard, however, then we want to change the guard
If the player attacks the guard, however, then we want to change the guard
Line 174: Line 164:




Extensibility
== Extensibility ==
=============


Of course, inheritance is a very useful concept in programming, and  
Of course, inheritance is a very useful concept in programming, and  
Line 182: Line 171:
soon as he/she is visible:
soon as he/she is visible:


class HostileGuardAI extends GuardAI {
class HostileGuardAI extends GuardAI {  
 
  // override action
  // override action
  public void action(Mobile m) {
  public void action(Mobile m) {
   
   
    if (m.canSee(Hero)) {
    if (m.canSee(Hero)) {
 
      // Charge! Install new AI into the mobile.
      // Charge! Install new AI into the mobile.
      m.setAI (new AttackPlayerAI());  
      m.setAI (new AttackPlayerAI());  
      Hero.message("The " + m.name() + " yells with rage!");
      Hero.message("The " + m.name() + " yells with rage!");
 
    } else {
    } else {
 
      // call parent method to continue patrol....
      // call parent method to continue patrol....
      super.action(m);
      super.action(m);
 
    }
    }
  }
  }
}
}
 


The really neat thing about this system is that it is almost infinitely
The really neat thing about this system is that it is almost infinitely
Line 207: Line 195:
imaginable. Just a few ideas for AI objects:
imaginable. Just a few ideas for AI objects:


CowardlyMageAI ()
=== CowardlyMageAI () ===
-----------------
 
Stays still and casts spells at the player UNLESS there are no cretures
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
between it and the angry hero, in which case it makes for the nearest
escape route.
escape route.


SleepAI (int time, AI wakeup)
=== SleepAI (int time, AI wakeup) ===
-----------------------------
 
Sleeps for time periods, then gets up uses the new AI object given.
Sleeps for time periods, then gets up uses the new AI object given.
Brilliant for a mesmerize spell, for example, that makes the creature
Brilliant for a mesmerize spell, for example, that makes the creature
friendly upon waking.
friendly upon waking.


LurkAI (int dist, AI whatnext)
=== LurkAI (int dist, AI whatnext) ===
-----------------------
 
Waits on the same spot, until the player comes within a specified
Waits on the same spot, until the player comes within a specified
distance. Then does something completely different, e.g. run away, attack,
distance. Then does something completely different, e.g. run away, attack,
or offer to trade.....
or offer to trade.....


FleeAI ()
=== FleeAI () ===
---------
 
Run away! This AI object makes sure that the creature stays as far away
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
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.
object, and make it play a fun game of hide+seek all round the dungeon.


SequenceAI (AI firstAI, int time, AI secondAI)
=== SequenceAI (AI firstAI, int time, AI secondAI) ===
----------------------------------------------
 
Use firstAI for the specified number of time periods, then swotch to
Use firstAI for the specified number of time periods, then swotch to
secondAI. Nested SequenceAI objects could be used to create complex
secondAI. Nested SequenceAI objects could be used to create complex
behaviour.
behaviour.


The list of possibilities is endless.......
The list of possibilities is endless.......
Line 252: Line 239:
default AI when psychological effects wear off.
default AI when psychological effects wear off.


 
== Extensions ==
Extensions
==========


There's lots of things that could be added to this AI model. I'd like  
There's lots of things that could be added to this AI model. I'd like  
Line 295: Line 280:
vulnerable mage,  
vulnerable mage,  


 
== Conclusion ==
Conclusion
==========


Well, I hope this gives a little bit of inspiration to everyone out there
Well, I hope this gives a little bit of inspiration to everyone out there
Line 307: Line 290:
Happy Coding!
Happy Coding!


Mike Anderson


Mike Anderson
mike@mikera.net
mike@mikera.net
5th March 1999
5th March 1999
</pre>


[[Category:Articles]][[Category:AI]]
[[Category:Articles]][[Category:AI]]

Latest revision as of 13:39, 15 September 2006

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.....

  • Allows for an infinite variety of friendly and unfriendly behaviour
  • Easily extensible
  • Relatively little extraneous coding, so you can focus on the AI itself
  • Once created, monsters can just take care of themselves
  • No huge switch statements (Hooray!!)

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 AI Model

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:

Real-World Example

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......


Extensibility

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:

CowardlyMageAI ()

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.

SleepAI (int time, AI wakeup)

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.

LurkAI (int dist, AI whatnext)

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.....

FleeAI ()

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.

SequenceAI (AI firstAI, int time, AI secondAI)

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.

Extensions

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,

Conclusion

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