Friday, October 24, 2008

XNA Series - Modular AI - Waypoints

So in our last AI lesson we talked about 3 methods, TurnTowards, MoveForward and Seek. Where TurnTowards turned our rotation towards a point by an maximum amount, Move Forward moved us at a certain distance based on our rotation and Seek combined the 2 to let us move our GameAgent towards a goal.

In this post we will create a path made from Waypoints that we want our object to follow. We will start by adding a Waypoint class to our project. It will contain a position vector, and a bool indicating if we have visited it yet.

public class Waypoint
{
public Vector2 position;
public bool visted = false;

public Waypoint(Vector2 v){
position = v;
}
}


and then declaring a List collection of Waypoint objects in our game class

List<Waypoint> path;


and then initialize it in our constructor

path = new List<Waypoint>();


we can also add Waypoints to our path either here or in LoadContent like this

path.Add(new Waypoint(new Vector2(10,10)));
path.Add(new Waypoint(new Vector2(400,10)));
path.Add(new Waypoint(new Vector2(20,100)));
path.Add(new Waypoint(new Vector2(120,300)));

in Update we can do this to allow for adding new Waypoints during gametime.

MouseState ms = Mouse.GetState();
if (ms.LeftButton == ButtonState.Pressed)
{
path.Add(new Waypoint(new Vector2(ms.X, ms.Y)));
}


Now we need to create a new method in our GameAgent class called FollowPath. It will take a List<Waypoint> object, a speed and a turn speed as parameters.

public void FollowPath(List<Waypoint> p, float speed, float turnSpeed)
{


Now for each waypoint, we will check if we are within an certain distance and if so we will mark that Waypoint as visited

foreach (Waypoint w in p)
{
if (Vector2.Distance(position, w.position) < 20){
visted = true;


then if the current Waypoint is not visited, we will seek it and return


if (w.visted == false)
{
Seek(w.position, speed, turnSpeed);
return;
}
}
return;
}

So now in our Update method we can assign this action to our Agent

a.FollowPath(path,2.5f,0.1f);

and another action to another

b.Seek(a.position,2f, 0.02f);

So we have a following a path and b Seeking a. With the ability to add new points by clicking. Please be aware though that the mouse is not shown on the screen during gametime, so you have to guess where your mouse is (silly you may think, but I'll post on showing the mouse soon) So this example works out pretty well, You can tinker with the speed and turnSpeeds to see how they effect the movement.

So until next time...


The Project Files for this post

1 comment:

Anonymous said...

What if more than one unit? Why calculate distance for each waypoint not visited, every frame? That involves use of square roots btw, lets say i have 24 waypoints and 64 units marching between waypoint 20 and 21, so i need to calculate 64*20 distances every frame, that's 1280 times? No way!! This is not how you program for games. Games = speed, and maximal speed. Store waypoints as a list of Vector2, then for each unit capable of following have one integer describing waypoint its following by its index in waypoint list. Then if you reach some waypoint, let it seek to next one, by adding to waypoint index, simple and fast. also you don't need actual distance to waypoint, you only need to tell if its in radius so instead of calling Vector2.Distance, check if (position.x * position.x + position.y * position.y < 20*20), that is squared distance. cheers.