A-Star Guard
by Tim Lang · 07/21/2008 (7:24 am) · 11 comments
Download Code File
OVERVIEW
This modification takes the basic aiGuard by Mark Holcomb and modifies it to use Dan Keller's Flexible A* Pathfinding system.
Basically, all it does is a few thigs:
* it changes the aiGuard.cc and aiGuard.h to use the Flexible A* aiplayer.cc and aiplayer.h
* it uses findPathTo instead of SetMoveDestination
* it switches SetAimLocation to SetAimObject
I also made a few changes to the spawning:
* I broke out the datablock definitions into their own file so that it's easy to add a new character datablock.
* You can specify a datablock on the aiGuardMarker to have different characters running the same script. (see setup below)
* You can specify different weapons on the aiGuardMarker to have the guards use different weapons.
The biggest change is the spawning by Trigger. The code now creates a triggerObject that you can use to spawn your enemies instead of using LoadEntities(); just in case you don't want all your opponents to spawn at once.
Installation
Installation is the same as the original aiGuard, EXCEPT:
This requires Dan Keller's Flexible A* Pathfinding resource. you can get that here:
Dan Keller's A* System
once the A* is setup, do the following:
copy aiGuard.cc and aiGuard.h to engine/game/
Add them to your project and recompile your .exe
copy aiGuard.cs and aiGuardDatablock.cs to example/[yourproject]/server/scripts/
open game.cs in example/[yourproject]/server/scripts/
add exec("./aiGuard.cs"); to game.cs in onServerCreated() where all the other exec("./whatever"); are.
in game.cs in startGame() add AIGuard::LoadEntities(); just below AIManager.think();
NOTE: If you're using triggers to spawn the aiGuard, then you don't need LoadEntities().
Setup
* BASIC SETUP
In your maps, place the aiGuardMarker where you want enemies to spawn.
If you want them to use different bodies or weapons, add these parameters to the aiGuardMarker:
block = "AdamPlayer";
Weapon = "ak47";
Where "AdamPlayer" is your datablock and "ak47" is the weapon you want to use.
* USING DIFFERENT DATABLOCKS
A few things need to be set up before you can use different datablocks. First, the model has to exist under data/shapes with all the
necessary .dsq and .dts files. Second the new character needs to have a definition script in /server/scripts/ (eg. adam.cs or kork.cs or skeleton.cs). Finally, that definition file must be called in onServerCreated() in game.cs
Once those are all set up, add the datablock to aiGuardDatablock.cs making sure that the name of the datablock is unique, and the body type is valid. (eg. datablock PlayerData(UniqueName : ValidBody)).
If all those are set up correctly, just add a block = "UniqueName" parameter to your auGuardMarker and you should be ready to go.
* USING DIFFERENT WEAPONS
Setting up different weapons is very similar to using different datablocks:
* The model has to exist in data/shapes
* A Script has to be made to handle the weapon (eg. crossbow.cs)
* That script must be called in onServerCreated() in game.cs
Where it differs is in adding the datablock. for weapons, you don't have to add the datablock into aiGuardDatablock.cs. You should just be able to add a Weapon = "myNewWeapon" parameter to your aiGuardMarker.
* SPAWNING BY TRIGGER
A bit of setup is needed to spawn by a trigger. First, you have to place a triggerObject down. You can name it whatever you want, but you have to specify a few things:
* dataBlock = "guardTrigger";
* spawnGroup = "number";
you have to specify a spawnGroup for each trigger, and then add a matching spawnGroup parameter on each aiGuardMarker you want to spawn in that group.
Once that's done, comment out AIGuard::LoadEntities(); in game.cs and you should be good to go.
Final Notes
Well, that's it. gotta send out props to Mark Holcomb and Dan Kellar for creating their resources in the first place. I couldn't have done it without them.
if you have any questions or comments, feel free to email me at timbecile@gmail.com.
OVERVIEW
This modification takes the basic aiGuard by Mark Holcomb and modifies it to use Dan Keller's Flexible A* Pathfinding system.
Basically, all it does is a few thigs:
* it changes the aiGuard.cc and aiGuard.h to use the Flexible A* aiplayer.cc and aiplayer.h
* it uses findPathTo instead of SetMoveDestination
* it switches SetAimLocation to SetAimObject
I also made a few changes to the spawning:
* I broke out the datablock definitions into their own file so that it's easy to add a new character datablock.
* You can specify a datablock on the aiGuardMarker to have different characters running the same script. (see setup below)
* You can specify different weapons on the aiGuardMarker to have the guards use different weapons.
The biggest change is the spawning by Trigger. The code now creates a triggerObject that you can use to spawn your enemies instead of using LoadEntities(); just in case you don't want all your opponents to spawn at once.
Installation
Installation is the same as the original aiGuard, EXCEPT:
This requires Dan Keller's Flexible A* Pathfinding resource. you can get that here:
Dan Keller's A* System
once the A* is setup, do the following:
copy aiGuard.cc and aiGuard.h to engine/game/
Add them to your project and recompile your .exe
copy aiGuard.cs and aiGuardDatablock.cs to example/[yourproject]/server/scripts/
open game.cs in example/[yourproject]/server/scripts/
add exec("./aiGuard.cs"); to game.cs in onServerCreated() where all the other exec("./whatever"); are.
in game.cs in startGame() add AIGuard::LoadEntities(); just below AIManager.think();
NOTE: If you're using triggers to spawn the aiGuard, then you don't need LoadEntities().
Setup
* BASIC SETUP
In your maps, place the aiGuardMarker where you want enemies to spawn.
If you want them to use different bodies or weapons, add these parameters to the aiGuardMarker:
block = "AdamPlayer";
Weapon = "ak47";
Where "AdamPlayer" is your datablock and "ak47" is the weapon you want to use.
* USING DIFFERENT DATABLOCKS
A few things need to be set up before you can use different datablocks. First, the model has to exist under data/shapes with all the
necessary .dsq and .dts files. Second the new character needs to have a definition script in /server/scripts/ (eg. adam.cs or kork.cs or skeleton.cs). Finally, that definition file must be called in onServerCreated() in game.cs
Once those are all set up, add the datablock to aiGuardDatablock.cs making sure that the name of the datablock is unique, and the body type is valid. (eg. datablock PlayerData(UniqueName : ValidBody)).
If all those are set up correctly, just add a block = "UniqueName" parameter to your auGuardMarker and you should be ready to go.
* USING DIFFERENT WEAPONS
Setting up different weapons is very similar to using different datablocks:
* The model has to exist in data/shapes
* A Script has to be made to handle the weapon (eg. crossbow.cs)
* That script must be called in onServerCreated() in game.cs
Where it differs is in adding the datablock. for weapons, you don't have to add the datablock into aiGuardDatablock.cs. You should just be able to add a Weapon = "myNewWeapon" parameter to your aiGuardMarker.
* SPAWNING BY TRIGGER
A bit of setup is needed to spawn by a trigger. First, you have to place a triggerObject down. You can name it whatever you want, but you have to specify a few things:
* dataBlock = "guardTrigger";
* spawnGroup = "number";
you have to specify a spawnGroup for each trigger, and then add a matching spawnGroup parameter on each aiGuardMarker you want to spawn in that group.
Once that's done, comment out AIGuard::LoadEntities(); in game.cs and you should be good to go.
Final Notes
Well, that's it. gotta send out props to Mark Holcomb and Dan Kellar for creating their resources in the first place. I couldn't have done it without them.
if you have any questions or comments, feel free to email me at timbecile@gmail.com.
#2
07/21/2008 (4:00 pm)
I don't have the RTS Kit, so I can't be totally sure, but as long as the Flexible A* system is set up, it should work fine.
#3
I use AiGuard, so implementing this should be a breeze.
07/21/2008 (6:27 pm)
Wow, this sounds really great. Thankyou.I use AiGuard, so implementing this should be a breeze.
#4
If you change those very minor things the installation should go smoothly, and after a few limited tests it seems to be working well. A great reasource overall.
08/05/2008 (9:33 am)
When installing you may need to get rid of the AdamPlayer and SoldierPlayer datablocks unless you have those resources. And also make sure to set $AI_GUARD_ENABLED = true;If you change those very minor things the installation should go smoothly, and after a few limited tests it seems to be working well. A great reasource overall.
#5
When path.size() = 1, it will pull random data - This needs to be changed as follows:
This cleaned up some erratic behavior for me also.
08/08/2008 (7:25 pm)
This section of code in AIGuard.cpp throws a runtime alert in TGEA 1.7.1 (index out of bounds on vector) when debugging:// Check if we should mMove, or if we are 'close enough'
// AStar clearence is used to give us enough room to get around corners
if (mFabs(xDiff) < tol && mFabs(yDiff) < tol) {
if (path.size())
{
mMoveDestination = path[1];
path.pop_front();
}When path.size() = 1, it will pull random data - This needs to be changed as follows:
// Check if we should mMove, or if we are 'close enough'
// AStar clearence is used to give us enough room to get around corners
if (mFabs(xDiff) < tol && mFabs(yDiff) < tol) {
if (path.size() > 1)
{
mMoveDestination = path[1];
path.pop_front();
}This cleaned up some erratic behavior for me also.
#6
needs to be changed to:
This was also in the original AIGuard.cs
08/08/2008 (8:22 pm)
There is a typo in Aiguard::Think. The following code:if (!%obj || %obj.getstate $="Dead") return;
needs to be changed to:
if (!%obj || %obj.getstate() $="Dead") return;
This was also in the original AIGuard.cs
#7
08/08/2008 (8:28 pm)
Note: This message deleted. Do NOT follow the steps in the original AIGuard to modify the player.cs to call delaybeforerespawn in the damage function.
#8
function GuardPlayer::onReachDestination(%this,%obj)
function GuardPlayer::OnDamage(%this, %obj, %delta)
function GuardPlayer::onEndSequence(%this,%obj,%slot)
So to get different datablocks to work properly you have to copy and paste each of those functions and change GuardPlayer to whatever the name of your datablock is (or figure out some way to get it to work automatically).
Also to get it to respawn as the correct datablock you need to change:
To:
Should be:
08/11/2008 (12:11 pm)
As for using different datablocks for different spawn points, the following functions are datablock dependant:function GuardPlayer::onReachDestination(%this,%obj)
function GuardPlayer::OnDamage(%this, %obj, %delta)
function GuardPlayer::onEndSequence(%this,%obj,%slot)
So to get different datablocks to work properly you have to copy and paste each of those functions and change GuardPlayer to whatever the name of your datablock is (or figure out some way to get it to work automatically).
Also to get it to respawn as the correct datablock you need to change:
function AIGuard::respawn(%this, %name, %obj)
{
// Create the demo player object
%player = new AIGuard() {
dataBlock = GuardPlayer;To:
function AIGuard::respawn(%this, %name, %obj, %block)
{
// Create the demo player object
%player = new AIGuard() {
dataBlock = %block;function GuardPlayer::OnDamage(%this, %obj, %delta)
{
if (obj.action !$="GetHealth")Should be:
function GuardPlayer::OnDamage(%this, %obj, %delta)
{
if (%obj.action !$="GetHealth")
#9
I added a DoThink() to datablock as well, so we could override the thought process on a per bot level if we wanted to (return 0 on DoThink() to use the default behavior). At the top of the Think() function in AIGuard.cs, insert this block of code, right after
Here is a sample datablock and function definition.
I moved a bunch of global variables to dynamics on the AIGuard, so you could tune them to have different weapons at runtime, different ammo, thinking time, etc. The following variables are new:
%obj.holdcountmax was $AI_GUARD_HOLDCNT_MAX
%obj.maxAttention was $AI_GUARD_MAX_ATTENTION
%obj.Weapon was $AI_GUARD_WEAPON
%obj.Ammo was calculated from $AI_GUARD_WEAPON
%obj.AmmoCount was hardcoded to 4 in EquipBot
I'm going to move the rest of the globals to the player as well, for ultimate tuning, as well as some new behaviors (running away when out of ammo, melee attacks, use inventory items to heal, use inventory items to reload, animations to pick up items, etc). I'm thinking about releasing an updated version as a resource, but I'm not sure if anyone would be interested.
08/12/2008 (6:55 pm)
I did the same thing on the spawn, but then I took it a bit further - I've modified the spawn function to allow you to tune the different kinds of guards that spawn. I moved the spawn function to the datablock, and broke each datablock type out into a separate file. The original spawn function is modified to call a spawn function specific to the datablock. The Respawn function is completely gone (just calls Spawn instead)://This is the spawn function for the bot.
function AIGuard::spawn(%name, %obj, %block)
{
// Create the player on the datablock, so that we can tune parameters by
// player type.
%player = %block.spawn(%name,%obj);
MissionCleanup.add(%player);
// Sets whether the bot will respawn or not based on it's markers
// dynamic variable. If not set on marker, use the bot's default.
if (%obj.respawn !$= "" )
{
if (%obj.respawn == true)
%player.respawn=true;
else
%player.respawn=false;
}
// if the field is not blank, set the weapon variable to the weapon
// otherwise, use the default for the bot.
if (%obj.Weapon !$= "")
{
%player.Weapon = %marker.Weapon;
%player.Ammo = %marker.Weapon @ "Ammo";
%player.AmmoCount = %marker.AmmoCount;
}
//Equipbot is called to set the bots beginning inventory.
%player.EquipBot(%player);
//Sets the name displayed in the hud above the bot.
%player.setShapeName(%name);
//Sets the bot's initial position to that of it's marker.
%player.setTransform(%obj.gettransform());
//Sets the bot to begin thinking after waiting the length of $AI_GUARD_CREATION_DELAY
%chance = getRandom(($AI_GUARD_CREATION_DELAY-1)) +1000;
%player.schedule(%chance,"Think", %player);
return %player;
}I added a DoThink() to datablock as well, so we could override the thought process on a per bot level if we wanted to (return 0 on DoThink() to use the default behavior). At the top of the Think() function in AIGuard.cs, insert this block of code, right after
%prevaction=%obj.action;
// NEW >>>
// Check to see if the creature processes his own thinking for his current state.
%scTime = %obj.dataBlock.DoThink(%this, %obj);
if (%scTime > 0)
{
%this.ailoop=%this.schedule(%scTime ,"Think" , %obj);
return;
}
// NEW <<<Here is a sample datablock and function definition.
//You can specifiy as many datablocks as you have characters.
// the first variable after PlayerData must be a unique name. the second variable (after :)
// must be a valid body type.
datablock PlayerData(BoombotPlayer : BoomBotData)
{
//The next three variables control the bot's movement speed.
//These are set slightly lower than the stock AIPlayer values.
//Since I wanted the guards to be more lumbering.
maxForwardSpeed = 8;
maxBackwardSpeed = 6;
maxSideSpeed = 8;
// maxDamage = 100; // hitpoints
shootingDelay = 100;
};
// Create our AIGuard here
function BoombotPlayer::spawn(%this,%name, %marker)
{
%maxAttention = 10;
%holdcountmax = 5;
// Create the demo player object
%player = new AIGuard()
{
dataBlock = %this; // Datablock to initialize with
marker = %marker; // Spawning Marker
botname = %name; // Name of the bot.
isbot=true; // True if a bot.
defaultfov = 240; // for restoring FOV
fov=240; // Sets the bots field of vision
respawn = true; // Respawn on death?
//Thinking variables
firing = false; //Firing tells whether or not we're in the midst of a firing sequence.
action = "search"; //The 'action' variable holds the state of the bot - which controls how it thinks.
holdcountmax = %holdcountmax; //The number of think cycles that the bot will 'hold' for before trying
holdcnt=%holdcountmax-1; //to return to his post.
maxAttention = %maxAttention; //The maximum attention level (higher is slower)
attentionlevel = %maxAttention/2; //The bots starting attention level is set to half of it's range.
//This number and scantime are multiplied to set the delay in the
//thinking loop. Used to free up processor time on bots out of the mix.
//Oldpos holds the position of the bot at the end of it's last 'think' cycle
//This is used to help determine if a bot is stuck or not.
oldpos = %marker.getposition();
Weapon = "RocketLauncher";
Ammo = "RocketLauncherAmmo";
AmmoCount = 10;
};
return %player;
}
// *****************************************************************************
// DoThink retuns the next scheduled "think" time. If zero is returned,
// that means the think process wants the default Think process
// to continue.
// *****************************************************************************
function BoombotPlayer::DoThink(%aiguard, %player)
{
return 0;
}
//The onReachDestination function is responsible for setting the bots 'action'
//state to the appropriate setting depending on what action the bot was following
//to reach the destination.
function BoombotPlayer::onReachDestination(%this,%obj)
{
//Picks an appropriate set of actions based on the bots 'action' variable
switch$(%obj.action)
{
//If the bot is attacking when it reaches it's target it will go into a hold.
case "Attacking":
%obj.action="Holding";
//If the bot is returning it has two possible scenarios for reaching a destination
//The first case is the the bot sidestepped and has reached it's sidestep location.
//If that is the case, then the bot goes into a quick hold. (Which sets the bot to
//only hold for 1 cycle before returning to his post.)
//The other alternative is that the bot has returned as is back at it's original position.
//If this is the case, then the bot's transform is set to match that of it's marker's
//transformation.
//This will cause a snapping into position - but it ensures that your guard always faces the
//direction you want it to when it returns to it's post.
//(It also helps to make sure that your markers are set as close to the ground as possible.
//Otherwise your bots will hop up and drop from the sky when they return to post.)
case "Returning":
//basedist is the distance from the bot to it's original position
%basedist = vectorDist(%obj.getposition(), %obj.marker.getposition());
//if the bot is close to his original position then set it's action to
//Guarding and set it to it's original facing and position.
if(%basedist < 1.0)
{
%obj.action = "Guarding";
%obj.settransform(%obj.marker.gettransform());
%obj.clearaim();
}
//if the bot is away from his post, then he must have gotten here
//as a result of sidestepping so set him to do a quick hold to scan
//for targets then return to post.
else
{
//Sets holdcnt to 1 less than the max. Ensures that the bot only holds for 1 cycle.
//before trying to return.
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
%obj.action="Holding";
}
//The bot was defending and sidestepped. So set him to 'hold' to check for targets
//and to prepare to return to post if no targets are found.
case "Defending":
%obj.action = "Holding";
case "RetrievingItem":
%obj.holdcnt=$AI_GUARD_HOLDCNT_MAX-1;
%obj.action="Holding";
}
}
//The OnDamage function sets the bots action to 'Dead' and starts the respawn process
//if called for.
function BoombotPlayer::OnDamage(%this, %obj, %delta)
{
//echo("GuardPlayer::OnDamage");
if (%obj.action !$="GetHealth")
{
%obj.action = "Defending";
}
if(%obj.getstate() $="Dead") %obj.action="Dead";
if(%obj.getState() $= "Dead" && %obj.respawn == true)
{
//echo("GuardPlayer::OnDamage - Guard is Dead!");
%obj.ScheduleRespawn(%obj.botname, %obj.marker, 20000, %obj.dataBlock);
%this.player=0;
}
}
//I dont use this function but left it in - just in case it does something I'm not privy to.
function BoombotPlayer::onEndSequence(%this,%obj,%slot)
{
%obj.stopThread(%slot);
%obj.nextTask();
}I moved a bunch of global variables to dynamics on the AIGuard, so you could tune them to have different weapons at runtime, different ammo, thinking time, etc. The following variables are new:
%obj.holdcountmax was $AI_GUARD_HOLDCNT_MAX
%obj.maxAttention was $AI_GUARD_MAX_ATTENTION
%obj.Weapon was $AI_GUARD_WEAPON
%obj.Ammo was calculated from $AI_GUARD_WEAPON
%obj.AmmoCount was hardcoded to 4 in EquipBot
I'm going to move the rest of the globals to the player as well, for ultimate tuning, as well as some new behaviors (running away when out of ammo, melee attacks, use inventory items to heal, use inventory items to reload, animations to pick up items, etc). I'm thinking about releasing an updated version as a resource, but I'm not sure if anyone would be interested.
#10
EquipBot becomes this:
RestoreFOV is changed as follows:
In addition, I modified GetClosestItemInSightandRange() to skip LOS testing for items that are less than 10 meters away (Bots were always missing items up close).
I wrote a new function called TargetAcquired() that would centralize the targetting system. I also had this function aim and calculate a path, as it would while firing. This stops the bot from shooting off his gun before aiming!
The target acquiring was modified to look like this (in "Guarding", "Holding", and "Returning") parts of the switch in AIGuard::Think()
08/12/2008 (7:16 pm)
Hmmm - maybe a few more changes might be of interest - EquipBot becomes this:
// Add the default equipment to the bot, based on player type.
function AIGuard::EquipBot(%this, %obj)
{
//This adds a weapon to the bots inventory.
%obj.incinventory(%obj.Weapon,1);
//This mounts the weapon on the bot.
%obj.mountImage(%obj.Weapon @ "Image",0);
//This sets the bots beginning inventory of ammo.
%obj.setInventory(%obj.Ammo,%obj.AmmoCount);
}RestoreFOV is changed as follows:
Quote:
//Restore FOV sets the bot's FOV back to it's regular default setting.
function AIGuard::restoreFOV(%this, %obj)
{
%obj.fov = %obj.defaultfov;
}
In addition, I modified GetClosestItemInSightandRange() to skip LOS testing for items that are less than 10 meters away (Bots were always missing items up close).
I wrote a new function called TargetAcquired() that would centralize the targetting system. I also had this function aim and calculate a path, as it would while firing. This stops the bot from shooting off his gun before aiming!
function AIGuard::TargetAcquired(%this, %obj,%tgt)
{
//Set the bots action to 'Attacking' and set it to attack quickly.
%obj.action = "Attacking";
//Set the bot to aim at the target.
//(The code uses the VectorAdd to adjust the aim of the bot to correct for the
//bot trying to shoot at the targets feet.)
%obj.setAimObject(%tgt.player, "0 0 1");
%dest = %tgt.player.getposition();
%obj.findPathTo(%dest);
%this.ailoop=%this.schedule(500,"Think" , %obj);
}The target acquiring was modified to look like this (in "Guarding", "Holding", and "Returning") parts of the switch in AIGuard::Think()
%tgtid = %this.GetClosestHumanInSightandRange(%obj);
//If %tgtid >0 then a target is in sight and range.
if(%tgtid >=0)
{
// Adjust the aiming before we fire!
%tgt = ClientGroup.getobject(%tgtid);
%this.TargetAcquired(%obj,%tgt);
}
#11
I just released an updated version of the original AI Guard Unit resource which includes a few features and ideas from here. Improved AI Guard Unit
08/27/2008 (1:06 am)
Quote:I'm thinking about releasing an updated version as a resource, but I'm not sure if anyone would be interested.
I just released an updated version of the original AI Guard Unit resource which includes a few features and ideas from here. Improved AI Guard Unit

Torque Owner Charin