Game Development Community

dev|Pro Game Development Curriculum

AI Guard Unit

by Mark Holcomb · 11/27/2004 (5:18 pm) · 335 comments

Download Code File

This is my second attempt at making an AI controlled character. The first was an
AIPlayer that would follow paths. This character is a guard unit. The guard will
wait at it's post (spawn point) until it sees a target. It will then attack that
target while trying to close with it.

If the target is lost the bot will wait at the last place it saw the character and
look around for a little bit, and then it will try to return to it's post.

There is a chance the bot will get stuck trying to return, but there is a simple
routine that has the bot try to move in random directions to try and clear itself
of any obstacles between it and it's post. It's not perfect.

The thinking routine is a simple state machine - which can be expanded on to give the bot more responses and actions.

As with my first ai character, I am using a simple system that allows the designer to drop
markers in the game map that will mark where the bots will start out. When the mission is loaded, the markers are detected and bots are spawned at the marker locations. The markers are then hidden from view. (The markers can be left visible to help in map editing.)

The markers for the guards can be given a dynamic variable called respawn. (Assigned to the marker during map editing.) 'Respawn' will determine whether a bot respawns or not upon death.

The bots have an attention setting. How often they scan is determined by their attention level. The bots get more sluggish (freeing up processor time) when targets are too far away. Conversely, the bot becomes incrementally more attentive as targets come within range, and further aware as targets come into sight.

When a target is found in range and in sight the bot will shoot at the target. The firing sequence is a step of scheduled calls that call for a firing cycle, a trigger down cycle, and a firing delay to control bot rate of fire.

When a bot is attacked it will attempt to sideatep and it's field of vision is temporarily increased to a 360deg field of vision - to emulate looking around to see what happened. The bot's attention level is also set to make it think at it's fastest rate when attacked.

To use the AIGuard...the following changes need to be made.

1. Back up your original game.cs, player.cs, and your current build of Torque. To install AIGuard will require a recompile, since I have created a new class cloned by copying AIPlayer.cc and AIPlayer.h and renaming all references to AIPlayer to AIGuard.
(I did this because I wanted to be able to run both my AIPatroller and AIGuards in the same maps and did not want to have any confusion between them.)

2. Add the files AIGuard.cc and AIGuard.h to your Torque project. (In my instance I saved them to my c:\Torque\engine\game directory and then added them into my project.)

3. Recompile your project and copy your new executable to the appropriate directory for your app.

4. Copy the file AIGuard.cs into your server/scripts directory.

5. Modify game.cs to add the line

exec("./aiGuard.cs");

to the function onServerCreated().

6. Also in game.cs: The section for function StartGame needs to modified the following ways:

Below the lines that read:

// Start the AIManager
new ScriptObject(AIManager) {};
MissionCleanup.add(AIManager);
AIManager.think();

add this:

AIGuard::LoadEntities();

(If you followed my previous resource you may not have any reference to AImanager. Don't fret... just look for the place in game.cs where you have

AIPlayer::LoadEntities

and put in

AIGuard::LoadEntities();

right underneath it.

7. In player.cs in the code for Armor::Damage modify the lines

// Deal with client callbacks here because we don't have this
// information in the onDamage or onDisable methods
   %client = %obj.client;
   %sourceClient = %sourceObject ? %sourceObject.client : 0;
   if (%obj.getState() $= "Dead")
      %client.onDeath(%sourceObject, %sourceClient, %damageType, %location);

to read:

// Deal with client callbacks here because we don't have this
   // information in the onDamage or onDisable methods
   %client = %obj.client;
   %sourceClient = %sourceObject ? %sourceObject.client : 0;   if (%obj.isbot == true)
   {
     %obj.attentionlevel=1;
     %obj.enhancefov(%obj);
   }
 
  if (%obj.getState() $= "Dead")
 {
     if (%obj.isbot == true)
    {
        if (%obj.respawn == true)
          {
           %obj.delaybeforerespawn(%obj.botname, %obj.markerpos, %obj.marker);
           %this.player=0;
           }
    }
   else
  {
     %client.onDeath(%sourceObject, %sourceClient, %damageType, %location);
  }
  }


*** If you followed my previous resource there should be no need to change this code.

8. Load your map - Stronghold as an example.
9. Go into the map editor. (F11) Then go into the Editor Creator (F4)
10. Under Shapes there should be a drop down called AIMarker, under that a new item called AIGuard.
11. Create a new AIGuard marker.
12. Select your marker, position it where you like and hit (F3) to modify the marker.
13. If you want to override the default respawn value - create a dynamic variable called respawn and set it's value to true or false.
14. Update your item by clicking 'APPLY'- very important and easy to miss step.
15. Save your mission and reload it.

A bot called Guard1 should appear at the spot of your marker.
If you come within range of the guard he should shoot at you and try to hunt you down.
If you get away, or when you die, he should return to his post.

I hope the resource helps other people get up and running with some AI code in their game.

And again, I'd like to thank the other members of this website whose code has been used in several places in the scripting to make this all work.

Mark H.

P.S. I've also included my AIPatrol class files with this - to install it follow the same instructions as for AIGuard, just substitute AIPatrol where AIGuard appears in the instructions. They can both be run at the same times with no problems.

P.S.S. 11/28/04 - I modified the AIPatrol.cs file to correct for a couple of typos.

P.S.S.S 12/3/04 - Added ammo and health seeking capabilities and fixed some errors. (Read posts below - or file in .zip for full details.)
#21
12/03/2004 (7:40 am)
@ Firas:
1) Did you add the path name to the patrol marker (see Mark's previous AI resource)? Check console.log for errors. Also check if the spawn points are above ground. I had issues where the spawn point was slightly under the terrain and the bot fell through.

2) check out the EquipBot function. I believe that this is where you should add the weapons, inventory, and set the weapon mount point. If you're using starter.fps, you'll have to add Crossbow and CrossbowAmmo. Mark, please correct if this is not so.

@ebee:
You could modify the scripts to search for vehicles, move toward and mount them. Not sure what the vechicle movement would look like. I have not tried it yet, but if I get it something working I'll post here.
#22
12/03/2004 (8:20 am)
Firas,

Jerry is right in what to do to try and get AIPatrol working.

AIPatrol is different from AIGuard in that when you place a marker for it, the bot will spawn there, but you need to have a path for it to follow for it to move around appropriately. And you need to have a dynamic variable 'pathname' on the marker that has the value of the name of the path.

For "Stronghold" the default path was called path1.

Also, recheck the things listed above in regards to the global variables, if the $AI_PATROL_ENABLED is set to false, they won't spawn.

Again Jerry is right in how to handle the weapons. There is a function called EquipBot that handles giving the weapon and ammo to the bot. If you choose to give the bots a different weapon, then change this code. Also in the code 'openfire' has a line to increase the ammo inventory of the bot when it shoots so they never run out.

Now, on to vehicles. I have not messed with controlling vehicles, but I do know how to handle finding the nearest one. It requires copying several of the functions used to hunt down people and to make them look for items.

Then once you've figured that out, then you have to trigger an 'action' called
GetXXX - where XXX is the item you want. Then you have to create a function like 'RetrievingItem' so that while your bot is running toward the item it will still defend itself and try not to get stuck.

Then you'd have to modify 'OnReachDestination' to where when it got to the item it could go into the proper 'action' state. In your case it would be something like a
'MountVehicle' state - which you would put in 'think' to handle mounting the vehicle.

From there you'd have to change the state to something like 'PilotVehicle' and then - assuming you know how to move the vehicle by now - you send the vehicle and player off to do their damage.

It seems like a lot, but believe me by using a 'state engine' to handle things it's fairly easy to implement new tasks.

That's the bad news - the mediocre news is that by the end of the day I should have a new version of AIGuard that I've added in the code to show exactly how to look for and retrieve items such as ammo and health patches. (Although I haven't tried finding a vehicle yet - but if it doesn't work as is, then you should be able to figure out how to modify the code to do so.)
#23
12/03/2004 (10:13 am)
AIGuard Updates

I have updated AIGuard file to correct some errors and to add some more functionality to the unit.

First the errors...

- I corrected the problem with all of the console errors being generated in 'LoadEntities'
- Corrected a problem in spawn and respawn trying to call an invalid object for getposition.
- Maybe some others, but those were the big issues.

Now on to the additions...
I added the ability for the bot to look for health patches when its damage percent rises above a certain level.

This functionality is controlled via these new global variables:

$AI_GUARD_DETECT_ITEM_RANGE = 100; //Sets how far around itself a bot will look for items to pick up
$AI_GUARD_SEEK_HEALTH_LVL = 70; //Sets the damage level at which the bot will seek health.
//A setting of 70 means that the bot has 30% of it's health left.

The function works similar to looking for a target, it will look for the item first. If it's in sight the bot will begin retrieving the item. While in retrieve mode the bot continues to look for targets and will fire, but will still move toward it's goal. Meaning the bot will crab sideways and backwards if need be to shoot at you and get health at the same time.

Right now the function looks for 'HealthPatch' items to replenish it's health. You can change this to whatever health item you wish.

A note. I went into player.cs and modified the line:

repairRate = .33;

to read

repairRate = 10.0;

this makes the health patches take effect almost immediately rather than slowly build up - which can cause the bots to appear lost as they will continue to hunt for health even as they're healing - and then they will break off once their health rises above the limit level.



Weapons:

I added global variables at the top of the file to control which weapon the bot will carry.

$AI_GUARD_WEAPON = "Crossbow"; //Which weapon do you want the guard to use
$AI_GUARD_ENDLESS_AMMO = false; //When set to true the guard will replenish its ammo perpetually
$AI_GUARD_WEAPON_USES_AMMO = true; //Set this to false for energy weapons that do not use ammo

This system assumes that if your weapon is named XXX that your weapon Image is named XXXImage, and that your ammo is named XXXAmmo. If this is not true, then you will have to modify the code in 'Equipbot'.

You set the bots weapon with the $AI_GUARD_WEAPON variable. $AI_GUARD_ENDLESS_AMMO allows you to not have your bots consume ammo. If you set this to false your bots will eventually run out of ammo.

But not to fear, because they have been told to look for ammo when they run out. They will go grab ammo that applies to their weapon if it is in their range and sight.

There is a downside though, the bots cannot detect ammo clips that do not have bounding boxes - or collision meshes. This includes the standard crossbow clip (ammo.dts) since the collision boxes appear to be messed up. So if you use the crossbow, you'll have to use a .dtf file that has collision boxes in it. I have included a simple but ugly replacement that you can copy into your /data/shapes/crossbow directory

To use the new clip copy ammo.dts and newclip_skin.jpg into your /data/shapes/crossbow directory.

Hopefully someone will redo the original with the corrected collision meshes.
If they do, please let me know.

That said there is one other variable to look at: $AI_GUARD_WEAPON_USES_AMMO
This variable will prevent most error messages when using an energy based weapon instead of an ammo based weapon. (I do not have the code set to provide endless energy.)

For example if you have an energy based weapon named EBeam, then you should set your variables to:

$AI_GUARD_WEAPON = "EBeam";				
$AI_GUARD_ENDLESS_AMMO = false;			
$AI_GUARD_WEAPON_USES_AMMO = false;

For the standard crossbow with endless ammo you should set:
$AI_GUARD_WEAPON = "Crossbow";				
$AI_GUARD_ENDLESS_AMMO = true;			
$AI_GUARD_WEAPON_USES_AMMO = true;

For the standard crossbow where the bots will consume ammo you should set:
$AI_GUARD_WEAPON = "Crossbow";				
$AI_GUARD_ENDLESS_AMMO = false;			
$AI_GUARD_WEAPON_USES_AMMO = true;


That pretty much sums up the changes.

The ability to look for items opens up a lot of other possibilities. I've left the code to look for an item generic - where you can feed it an item name and it will return with the itemid number if it is found, or return a -1 if not.

This means that you can quickly set your bot to look for any valid item name. Just remember the item must have a collision mesh. Otherwise the LOS detection will not find it.
#24
12/03/2004 (10:58 am)
This is great! Thanks for the update Mark.
#25
12/03/2004 (1:59 pm)
Quick update - I made a fix to the beginning of the think cycle. I had a problem where the bots would enter an endless loop. The file has been updated to the latest fix.
#26
12/27/2004 (5:52 am)
Hi Mark,

I like this resource. I would like to make a couple of suggestions. I implemented the same kind of thing for our game. Yours has some advantages that mine do not. If you added some of my code to some of your code this could be one heck of a good resource. There are 2 things in mine that would improve yours quite a bit.

1.Use the paths to spawn the bots.
2.Change the bots site to be more realistic (they can only see in front of themselves instead of 360 degrees).

In reference to 1:

In simPath.cc, bool Path::onAdd() add the following:

if(!Parent::onAdd())
      return false;

   Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));

   return true;

In the cs file you are using spawning the bots use:

function Path::onAdd( %this, %obj)
{
  $paths[$pathCounter] = %obj;
  $pathCounter++;
}

Now all you have to do is have a for loop spawn bots based on $pathCounter. If %path.getCount() < 2 (if there is only one path marker) then have the bot guard and if it
#27
12/27/2004 (7:44 am)
I agree and disagree with a little bit with what you have to say.

First I agree with you that your method of adding the bots along the path is some nice code, and I might take a look at using it at some point.

However, my method allows for multiple bots to share a path, wherein yours assigns one bot per path. This could me changed of course by adding more code and setting up a dynamic variable to determine how many bots per path etc... So that's really just a matter of a little more tweaking.

My original goal with using the markers to drop in the AI players was to put the placement and addition of AI units down at the map editing and design level and get away from it being in the scripting - as it is in the FPS demo. My thought is that a person who wants to build a map and populate it with various AI creatures shouldn't necessarily have to know diddly squat about scripting. Hence the drop in objects to show where and what.

Now the second point is this.
In AI_Patrol.cs there is a variable at the very top of the code called $AI_PATROL_FOV, and in AIGuard.cs the variable is $AI_GUARD_FOV - these variables control the field of vision of the bot as they search for items. (FOV is short for Field of Vision - for those who might read this and wonder.)

I purposely used this setup to allow the code to be modular to make cloning of bots into other classes easier. For example you can convert the guard into a sniper by increasing his detection ranges, narrowing his field of vision - to replicate using a scoped or zoomed weapon - and adding a little error to his shooting accuracy based on distance.

But mostly, I don't give the bots 360 degree vision all the time - because it's not realistic, especially for the AIPatrol bot. How real is it that you can see what's behind you while running full speed ahead?
I do use the 'enhancefov' function to give the bots 360 degree vision when it would make sense for them to look around. Such as when being attacked, and in the guards case - randomly while on station to emulate looking over his shoulders.

So really what we're discussing is a difference of opinion on how best to handle certain aspects of the same things. Doesn't make me right and doesn't make you wrong. Just shows that there are several ways to do the same thing. It just matters on what your philosophy is when you start out, and what your goal is.

Thanks for the comments though, and especially for the code snippets - I always like seeing new ways to do things.
#28
12/27/2004 (8:09 am)
You have a point. But the paths are added in the map view, and there is no scripting involved after the initial scripts are done. Your way works fine though. The reason I hardcoded the bots field of view is because if you look at the code I am using the bots eye node, and if you want them to look over there shoulder change there root animation so that they are looking around. My goal was to make the bots as realistic as possible. A humans field of view never changes, unless you are looking through something, and a console method could be added that would allow you to narrow there FOV for a scope. All you would have to do is add a FOV variable, replace the 90 in the if statement, and add a console method. I started using items as spawn points, but If you have to make a path for the patroling bots to follow I decided instead of adding an item and setting the pathname for the bot to follow I would just make a path and have a bot automatically spawn there. Your multiple bot per path could easily be solved by adding a %path.botNumber=? and have the spawn method spawn the number in the botNumber. The path array captures the whole path object, so you can use that to your advantage and add any number of dynamic variables to it: respawn = true, bots = 1, ..... Why did you use setTransform for the returning bots instead of setMoveDestination?

Your implimentation works great, and of course these are only suggestions.

Marrion Cox
#29
12/27/2004 (8:25 am)
I understand your using the eyenodes of the bots and changing the animations - but let me be straight here. I code for fun. I'm not out to make a game - well maybe a silly one for my friends and I to mess around with. I just wanted to take the existing platform and make it a little more robust.

I also wanted to target my code at other people like myself - those who want to play around with the code some, but don't necessarily want to re-invent the wheel by changing the animations, and lots of other things. Basically I just wanted a 'drop-in' simple AI bot.

So that's why I went the way I did. Your way makes a lot of sense if you're going into it knowing that you're going to change the bots animations to account for looking aorund while stationary, etc...

As for the SetTransfrom... I cheated.

On the AIGuard - I wanted them to return to their original facing and position when returning to their posts. I was having trouble at the time figurig out an easy way to get them to look back in the proper direction, so I cheated and had them transform back to the position on their marker. Since it was their marker that told them where they were to be positioned and 'on station' at.

Not elegant, but functional.

And a little more on the pathing issue. I've been working on a navigation system that eliminates the paths, so I doubt I'll worry too much about going back and revamping the system. But like I said earlier - your idea was a good one - and one I didn't think of at the time.
#30
01/11/2005 (3:20 pm)
I got this resource integrated with my project in a matter of minutes. Thank you. I just have one issue, When I kill an AIGuard, it seem to respawn itself twice. Once where I put the original marker, then once again somewhere else on the map, let's call it point x. At the original aiGuard spawn point, the bots name is something like "Guarding 5.0.4," at point X, the bot is named "Guard 1."
Any ideas?
I am using TGE 1.3 with Bravetree TankPack

Thanks
Dave
#31
01/11/2005 (9:14 pm)
I don't have the tank pack and haven't tried integrating the AIGuard unit with it. I know there are some people out here who have. I'll try to see if those gys have had the same problem.

Offhand though, the problem you're describing sounds as if the 'LoadEntities' or 'Respawn' function is being called twice.

Hmmm... One thing to check... go back and check the modifications to the player.cs file to see if they are correct. I'm thinking that maybe if you don't have them right that you could be getting both the AIGuard 'respawn' code to fire - plus the default player respawn - which would initialize the bot at 0,0,0 and use the default name of Guard 1.

I'm sure that's part of what it is - because the AIGuard respawn calls for the bot to start thinking - and the 'Think' part updates the bot's name to show it's action for the name. And the 2nd bot is not 'Think'ing because the name is static.

Let me know if that does the trick or not, meanwhile I'll see if I can think of why else that might happen.
#32
01/12/2005 (2:21 pm)
I got it working I think. I am going to do more testing to see. I just started from scratch. Reinstalled TGE, AIGuard, then the tank pack. It seems to work fine now. Not sure what was going on but I will post back if I notice it again. Thanks

Nevermind, It's still happening, the "other Bot" is spawning under the terrain so I didn't see it till it happened when I was in the world editor. Being somewhat new to programming, this is great learning tool, but Help is always appreciated. I am going to try this again, I didn't see it happen when I did a fresh install of tge, but I didn't look on the world editor right after I killed a bot. But it could have been spawning under the terrain.
#33
01/17/2005 (12:41 pm)
Thank you for this, Mark got it working fairly quick, considering I've only had Torque for three weeks. ( quick to me was a couple hours...=) ).
The question I have, and please bear with me if I missed this in the above thread, but what I would like to do now is to have the AIGuard be a different model from the default player.dts. I've messed with the AIGuard.cs, Game.cs, Player.cs to no avail. I absolutely know it is something simple that I am missing, but for the life of me I can't figure it out. It appears in the GUI editor when first placed as the right model, then when I save and reload, it falls back to the player.dts.
Excuse my ignorance. Any help would be useful.
#34
01/17/2005 (10:11 pm)
Mark, I figured it out! I read through the CS scripts from SPKit, Beaver Patrol and the AIGuard and saw what I had to do. I created a copy of the player.cs, named it player2.cs, edited it to reference a datablock PlayerData(GuardPlayer : Player2Body) and changed the shapeFile= "~/data/shapes/player/player.dts" to shapeFile = "~/data/shapes/player/mechplayer.dts";did the same in the aiguard.cs. Added exec "./player2.cs"); to the game.cs and it loaded my little mechbots and they started after me guns blazing!

Thanks again for a great resource!
#35
01/17/2005 (10:16 pm)
Glad you figured it out. Sorry I didn't get back to you in time to help you out, but glad you've got things working.
#36
01/19/2005 (11:13 am)
Fail66, did you ever resolve your problem with the second guard respawn? I'm having the same problem. Upon death the guard respawns at the marker location and then a second guard spawns somewhere else (quite often below the terrain). I'm going to go through the aiguard.cs, player.cs and game.cs code to see if it it some type of second or redundant repawn reference, but if you have solved this it would be great to know how you did.
Thanks!
#37
01/19/2005 (12:03 pm)
No I haven't. I have turned my attention to getting some content in our game. Getting my son up to speed on the tools involved. I have been watching this thread, so if you find a resolution before I do, I would really appreciate knowing how you fixed it.
#38
01/19/2005 (12:44 pm)
Hey guys, I'm not ignoring you. I just don't have an answer. I don't own the tank pack, so it's impossible for me to try and pin down exactly where the problem may lie.

I've sent an email to a guy that has helped me find some other errors, and he has used the tank pack, so hopefully he'll be able to help shed some light on things.
#39
01/19/2005 (1:49 pm)
Mark, no problem. I know you're not. But here is a poser for you... I don't have the tank pack either =) . I'm using an orginal model. But now that I've discovered the problem I'm going to try to reproduce it with the orc model (I didn't realize that Fail66 was having the problem with the tank pack specifically). If it doesn't do it with the orc model, then the obvious thing is that whatever the designer of the tank model and I'm doing with my model will have the same design problem...I think...but none of this is especially familiar to me so it is just a process of elimination.
#40
01/19/2005 (2:35 pm)
Just so you know, I am not sure if it is the tank pack causing the issue. I need to do some futher testing. I will do it tomorrow. Tonight I my wifes birthday.