Game Development Community

dev|Pro Game Development Curriculum

How to keep bots from getting stuck

by Trent Reimer · 02/21/2004 (8:52 pm) · 2 comments

This is a simple approach to keeping your bots moving smoothly through the mission. It requires a little more work when setting up the path markers for the mission but once they are set up properly your bots will never be lost.

First you will need to design your mission in such a way that there are no spots on the mission where a bot cannot "see" a path marker if it looks around. You do not need to oversaturate the mission with markers - simply ensure that there are no blind spots. This may take a few tries if you have rolling terrain but watching the bots in action will quickly point out where the problem areas are. Keep in mind that something as simple as a gentle rise in ground can block visibility.

Secondly you'll need a repeating function which checks to see if the bot(s) is/are stuck. The GameBeavers code base uses a function called "think" to check all the bots every half second. You certainly wouldn't want to call it more frequently than that as it triggers a good number of checks. You may decide on a different function so the following is the heart of the matter. This logic will appear in whatever function you choose.

// "%bot" is the bot in question. "oldposition" is a property set for
// the bot each time through the function so we can measure its progress
// from one iteration to the next.

%botPosition = %bot.getTransform();
%botMovement = vectorDist(%bot.oldposition, %botPosition);
%bot.oldposition = %botPosition;    // ready for the next iteration
if(%botMovement < 2)    // hmm, not moving much
{
   %this.shakeStep(%index);
   %moveBot.joinPath();
} else {    // proceed along the path
   if(isObject(%moveBot.path))
   {
      if (%moveBot.currentNode < 0
         || %moveBot.currentNode > %moveBot.path.getCount())
      {
         %moveBot.currentNode = 1;
      }
      %moveBot.moveToNode(%moveBot.currentNode);
   }
}

OK, now for the functions which the above code calls. You can put these in "aiPlayer.cs" (which is usually in the "server/scripts" directory of your game or mod folder). These next 2 functions are taken from resources already submitted by other coders to GG.

// get the bot to shake loose of minor obstacles

function AIPlayer::shakeStep(%this)
{
   %xrand = getRandom(-15,50);
   %yrand = getRandom(-15,45);
   %newLoc = %this.getTransform();

   //adjust the x pos too by 80% of the bot's index number
   %newLoc = setWord(%newLoc, 0, (getWord(%newLoc, 0) + (%xrand)));
   %newLoc = setWord(%newLoc, 1, (getWord(%newLoc, 1) + (%yrand)));

   %this.setMoveDestination(%newLoc);
}


// move the bot along the path, the bot also looks where it's going

function AIPlayer::moveToNode(%this,%index)
{
   // Move to the given path node index
   %this.currentNode = %index;
   %node = %this.path.getObject(%index);
   %this.setMoveDestination(%node.getTransform());
   %this.setAimLocation(%node.getPosition());
}

And finally these last two functions detect "visible" path markers and instruct the bot to move accordingly. You may want to consider the typemasks used to detect obstacles. If your mission has other possible obstacles to navigate you will want to add them to that part - it will be commented below.

// join the closest visible path node

function AIPlayer::joinPath(%this)
{
   if(isObject(%this.path))
   {
      %nodeCount = %this.path.getCount();
      if(%this.currentNode < 0) %this.currentNode = 1;
      
      // check if the selected marker is visible
      if(%this.checkLOSToMarker(%this.path.getObject(%this.currentNode)))
      {
         %this.moveToNode(%this.currentNode);
         return;
      } else {
         // first try a limited range search to be nice to the CPU
         
         if(%this.currentNode + 3 >= %nodeCount) %topNode = %nodeCount - 1;
          else %topNode = %this.currentNode + 3;
         
          for(%i = %topNode; %i >= 0; %i--)
          {
            if(%this.checkLOSToMarker(%this.path.getObject(%i)))
            {
                %this.currentNode = %i;
                %this.moveToNode(%this.currentNode);
               return;
            }
         }
         
         // if we're still here it's time for a full path search
         
         for(%i = %nodeCount - 1; %i >= 0; %i--)
         {
            if(%this.checkLOSToMarker(%this.path.getObject(%i)))
            {
               %this.currentNode = %i;
               %this.moveToNode(%this.currentNode);
               return;
            }
         }
         
         // if we're still here there's problem with the mission layout
         
         echo("mission error - path node visibility error");
      }
   }
   else echo("mission error - no path to follow");
}


// check if the selected path node is visible

function AIPlayer::checkLOSToMarker(%this, %obj)
{
   if(!isObject(%obj)) return false;
   %eyeTrans = %this.getEyePoint();
   %eyeEnd = %obj.getTransform();
   // you may think of other typemasks which represent obstacles
   %searchResult = containerRayCast(%eyeTrans, %eyeEnd,
      $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType, %this);
   %foundObject = firstWord(%searchResult);
   if(!%foundObject) return true;   // path is clear
   else return false;
}

Hope it helps. Feel free to post improvements so the rest of us can view them too.

#1
02/22/2004 (2:04 am)
Thanx Trent, I will be able to add a few modifications and add this to my game. Thanks again!
#2
07/09/2007 (4:01 am)
Killer Kork has an approach feature that works pretty well. When the AI has an obstacle in the way to a destination, the AI casts a horizontal line out at his position and checks 40 pooints at 0.3 meters apart. For every point, the AI checks to see if a rayCast running from that point to the destination doesn't hit that obstacle. If it does (not clear path), try another spot. If it doesn't hit anything, move to that point along the horizontal line and then go to the destination.

This works too, don't get me wrong :-). I've been trying to figure out interior navigation so I can stick that into a resource.