Tactics-Action Hybrid Game Tutorial Part11: Ai Paths
by Steve Acaster · 05/13/2011 (4:06 pm) · 1 comments
Back to Part Ten: GamePlay Test
So we have Enemy Ai that can run in a straight line ... but running around corners would be so much better. Torque3D comes with an "example path system" for it's "example Ai", so we shall integrate this with our customized scripts, so that we can have our Enemy Ai Team members run around corners and not just in a straight line.
First, the script in scripts/server/aiplayer.cs. We'll add a new variable to our custom spawn function called "%path" which we will call when it spawns.
Because we only want the Ai to move on their turn when they are the active playerObject, we introduce another variable to make sure that it starts (startPath). And now to getting it to work - it is important to note that "path" and "goal" are seperate things, so we will retain the script for for goal (move in a straight line with a clear Line Of Sight) and add the new functionality for using paths.
So it checks for a path, checks if it has started one, and then starts a path or moves to the appropriate node on the path whilst it has energy. When it runs out of energy it takes it's shot and then passes the control to the next available playerObject just as before - and when it reaches the end of the path it quits the path and moves on. Note: we use 99 at the end of the "followPath" function to declare that it should keep moving until it reaches the 99th node or until it has no more nodes in the path - so 99 is just a safety measure to make sure that the path is completed. If we used "-1" the path would loop, but we don't want that in this Tutorial. We also need to clear the path once the Ai has finished it or it will get stuck in a loop and not continue to shoot and pass it's turn on.
Note: in T3D 1.1 Final AiPaths start with seqNum "1", previously it was "0". So let's update the followPath function.
To make sure that we can test this, load up T3D and our level, go into the World Editor (F11) and create a "path" (library->level->level->path), and call it "botpath1", drop it in the "bot_paths" folder. Outside the second dead-end alleyway should be a large rock, place a "path node" (library->level->level->path node) at the left side of it (as looking towards the station model). NB: this "path node" will be called a "marker" in the "Scene Inspector". Place another at the corner (away from the station), and a third near the rock down the longer straight section of the route, towards the area where our first 2 Ai have spawned. Now call the first "marker" "path1start", select all the markers and drag/drop them into "botpath1".
We have a path, but we need to organize the "markers" so they are in order. Selecting "path1start", in the "Object Inspector", set it's "seqNum" (sequenceNumber) to "1". Select the next marker (at the corner) and set it's "seqNum" to "2", and set our final marker's "seqNum" to "3". Now when we spawn an Ai at "path1start", and attach it to follow the path, it should go to each node in order.
Select "botpath1" and from use the copy function from menu toolbar and then paste. You'll now have a duplicate path with 3 duplicate markers. Rename the path "botpath2" and the first marker "path2start". Move this first marker to the other side of the rock, away from "path1start". In the "Scene Inspector" select the next marker and drag it out a little so that it is away from the other marker. Do the same for the last marker, and then make sure that the new path is inside the "bot_paths" folder. Finally check that the "seqNum" for each marker is correct (1, 2, 3).
Create a new trigger called "trig2". Position it across the route, just after the first dead-end alley which bot2 comes out of, just before the next corner. Make it large enough to fill the width of the route (scale = "25 5 5") and in the "enterCommand" add:
You can see our new variable "botpath1", and that our spawnpoint is the first node of that path, "path1start". Drop the trigger into the "level_triggers" simGroup to keep things tidy. Now let's test, boot up T3D and load our level, fight your way past the first two Ai and move on to enter our new trigger. If all has gone well, 2 new enemy Ai will spawn, and on their turn start to run around the corner, following their paths. When they run out of energy they will stop and fire, and when it is their turn again, they will continue.
Now, let's go back and fix a couple of issues ...
We need to stop the Enemy Ai from shooting at dead playerObjects when they are running their "passiveThink" routines, and while we are at it, we might as well abort them trying to target if they are dead too. In scripts/server/aiplayer.cs, in "TargetClearView" function add:
When a Client controlled playerObject is killed during his movement phase and has not reached his final destination or run out of energy - apart from the Enemy Ai trying to mutilate the corpse with bullets - the decal he uses will not be deleted, so we want to clear that up. And in scripts/server/player.cs, at the end of the "onDisabled" function add:
If all has gone well, let's finish our gameplay events off so that we have a working game.
Part Twelve: Finale
So we have Enemy Ai that can run in a straight line ... but running around corners would be so much better. Torque3D comes with an "example path system" for it's "example Ai", so we shall integrate this with our customized scripts, so that we can have our Enemy Ai Team members run around corners and not just in a straight line.
First, the script in scripts/server/aiplayer.cs. We'll add a new variable to our custom spawn function called "%path" which we will call when it spawns.
function AIPlayer::tacticsSpawn(%name, %spawnPoint, %team, %path)//<--- yorks added
{
//...
%player = new AiPlayer(%name)
{
dataBlock = %playerType;
path = %path;//<---- yorks new
};
MissionCleanup.add(%player);
%player.team = %team;
%player.action = 0;
%player.hasFired = 0;
%player.path = %path;<---- yorks new
%player.startPath = 0;<---- yorks new
%rand = GetRandom(2000, 2500);
%player.timer = %rand;
//...
}Because we only want the Ai to move on their turn when they are the active playerObject, we introduce another variable to make sure that it starts (startPath). And now to getting it to work - it is important to note that "path" and "goal" are seperate things, so we will retain the script for for goal (move in a straight line with a clear Line Of Sight) and add the new functionality for using paths.
function AIPlayer::activeThink(%this)
{
//...
if(%energy > 0)
{
//do we have a goal to move to?
if(isObject(%this.goal))
{
%range = VectorDist(%this.getPosition(), %this.goal.getPosition());
if(%range > 1)
{
//if the client's playerObject uses up energy, so does the Ai
%this.decEnergy();
echo(%this.getname() SPC %this.getenergyLevel());
%this.setmovedestination(%this.goal.getposition());
%this.schedule(%this.timer, "activeThink");
return;
}
else
%this.goal = 0;
}
//do we have a path? <---- yorks new start
if(%this.path !$="")
{
//if the client's playerObject uses up energy, so does the Ai
%this.decEnergy();
if(%this.startPath == 0)
{
echo("start path for the first time!");
%this.startPath = 1;
%this.getID().followPath("MissionGroup/bot_Paths/" @ %this.path, 99);
echo("our path = " @ %this.path);
}
else
{
echo("already has a path! - path = " @ %this.path);
%this.moveToNode(%this.currentNode);
}
%this.schedule(%this.timer, "activeThink");
return;
}
else
{
echo("we have no path - path = " @ %this.path);
}// <---- yorks new end
}
if(%this.hasFired == 0)
{
//...
}So it checks for a path, checks if it has started one, and then starts a path or moves to the appropriate node on the path whilst it has energy. When it runs out of energy it takes it's shot and then passes the control to the next available playerObject just as before - and when it reaches the end of the path it quits the path and moves on. Note: we use 99 at the end of the "followPath" function to declare that it should keep moving until it reaches the 99th node or until it has no more nodes in the path - so 99 is just a safety measure to make sure that the path is completed. If we used "-1" the path would loop, but we don't want that in this Tutorial. We also need to clear the path once the Ai has finished it or it will get stuck in a loop and not continue to shoot and pass it's turn on.
Note: in T3D 1.1 Final AiPaths start with seqNum "1", previously it was "0". So let's update the followPath function.
function AIPlayer::followPath(%this,%path,%node)
{
//...
if (%this.path $= %path)
%this.moveToNode(%this.currentNode);
else
{
%this.path = %path;
%this.moveToNode(1);//yorks changed to 1 from 0
}
}
//...
function TacticsAiData::onEndOfPath(%this,%obj,%path)
{
%obj.nextTask();
echo(%obj.getname() @ " path ended - " @ %obj.path @ " clear it"); <-- yorks
%obj.path ="";// <--- yorks
}To make sure that we can test this, load up T3D and our level, go into the World Editor (F11) and create a "path" (library->level->level->path), and call it "botpath1", drop it in the "bot_paths" folder. Outside the second dead-end alleyway should be a large rock, place a "path node" (library->level->level->path node) at the left side of it (as looking towards the station model). NB: this "path node" will be called a "marker" in the "Scene Inspector". Place another at the corner (away from the station), and a third near the rock down the longer straight section of the route, towards the area where our first 2 Ai have spawned. Now call the first "marker" "path1start", select all the markers and drag/drop them into "botpath1".
We have a path, but we need to organize the "markers" so they are in order. Selecting "path1start", in the "Object Inspector", set it's "seqNum" (sequenceNumber) to "1". Select the next marker (at the corner) and set it's "seqNum" to "2", and set our final marker's "seqNum" to "3". Now when we spawn an Ai at "path1start", and attach it to follow the path, it should go to each node in order.
Select "botpath1" and from use the copy function from menu toolbar and then paste. You'll now have a duplicate path with 3 duplicate markers. Rename the path "botpath2" and the first marker "path2start". Move this first marker to the other side of the rock, away from "path1start". In the "Scene Inspector" select the next marker and drag it out a little so that it is away from the other marker. Do the same for the last marker, and then make sure that the new path is inside the "bot_paths" folder. Finally check that the "seqNum" for each marker is correct (1, 2, 3).
Create a new trigger called "trig2". Position it across the route, just after the first dead-end alley which bot2 comes out of, just before the next corner. Make it large enough to fill the width of the route (scale = "25 5 5") and in the "enterCommand" add:
if(trig2.triggerOff == 1) return; if(!isObject(bot3)) aiplayer::tacticsSpawn(bot3, path1start, 0, botpath1); if(!isObject(bot4)) aiplayer::tacticsSpawn(bot4, path2start, 0, botpath2); trig2.triggerOff = 1;
You can see our new variable "botpath1", and that our spawnpoint is the first node of that path, "path1start". Drop the trigger into the "level_triggers" simGroup to keep things tidy. Now let's test, boot up T3D and load our level, fight your way past the first two Ai and move on to enter our new trigger. If all has gone well, 2 new enemy Ai will spawn, and on their turn start to run around the corner, following their paths. When they run out of energy they will stop and fire, and when it is their turn again, they will continue.
Now, let's go back and fix a couple of issues ...
We need to stop the Enemy Ai from shooting at dead playerObjects when they are running their "passiveThink" routines, and while we are at it, we might as well abort them trying to target if they are dead too. In scripts/server/aiplayer.cs, in "TargetClearView" function add:
function AIPlayer::TargetClearView(%this, %target)
{
if(%this.getState() $="Dead")//<--- yorks
return;//< ---yorks
if(!isObject(%target))
return false;
if(%target.getState() $="Dead")//<-- yorks
return;//<-- yorks
//...When a Client controlled playerObject is killed during his movement phase and has not reached his final destination or run out of energy - apart from the Enemy Ai trying to mutilate the corpse with bullets - the decal he uses will not be deleted, so we want to clear that up. And in scripts/server/player.cs, at the end of the "onDisabled" function add:
if( %obj.decal > -1 ) decalManagerRemoveDecal( %client.player.decal );
If all has gone well, let's finish our gameplay events off so that we have a working game.
Part Twelve: Finale
About the author
One Bloke ... In His Bedroom ... Making Indie Games ...

Torque Owner Jeff Yaskus
jy games
The enemy Ai fires repeatedly when I come into their view, on my turn.
added a flag on the enemies side, near the end of the AIPlayer::passiveThink function
if(%target != 0) { if ((%this.targetClearView(%target) == true) && (%this.hasFired != 1)) { %this.hasFired = 1;Now they only get a single shot during MY turn - instead of infinite.
And I created a new trigger in art/datablocks/trigger.cs to handle the spawning.
datablock TriggerData(SpawnTrigger : DefaultTrigger) { }; function SpawnTrigger::onEnterTrigger() { echo("SpawnTrigger::onEnterTrigger() entered"); if (trig1.triggerOff != 1) { if (!isObject(bot1)) { echo("spawning bot1"); aiplayer::tacticsSpawn(bot1, botspawn1, 0); bot1.goal =bot1goal; } if (!isObject(bot2)) { echo("spawning bot2"); aiplayer::tacticsSpawn(bot2, botspawn2, 0); bot2.goal=bot2goal; trig1.triggerOff = 1; } } else { if (trig2.triggerOff != 1) // if has not happened yet { if(!isObject(bot3)) { echo("spawning bot3"); aiplayer::tacticsSpawn(bot3, path1start, 0, botpath1); } if(!isObject(bot4)) { echo("spawning bot4"); aiplayer::tacticsSpawn(bot4, path2start, 0, botpath2); } trig2.triggerOff = 1; } } }