This is a long recipe that could be regarded as a big two-step process. First, we build the Path class, which abstracts points in the path from their specific spatial representations, and then we build the PathFollower behavior that makes use of that abstraction in order to get actual spatial points to follow:
- Create the Path class, which consists of nodes and segments; only the nodes are public and are assigned manually:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Path : MonoBehaviour
{
public List<GameObject> nodes;
List<PathSegment> segments;
}
- Define the Start function to set the segments when the scene starts:
void Start()
{
segments = GetSegments();
}
- Define the GetSegments function to build the segments from the nodes:
public List<PathSegment> GetSegments ()
{
List<PathSegment> segments = new List<PathSegment>();
int i;
for (i = 0; i < nodes.Count - 1; i++)
{
Vector3 src = nodes[i].transform.position;
Vector3 dst = nodes[i+1].transform.position;
PathSegment segment = new PathSegment(src, dst);
segments.Add(segment);
}
return segments;
}
- Define the first function for abstraction, which is called GetParam:
public float GetParam(Vector3 position, float lastParam)
{
// body
}
- We need to find out the segment the agent is closest to:
float param = 0f;
PathSegment currentSegment = null;
float tempParam = 0f;
foreach (PathSegment ps in segments)
{
tempParam += Vector3.Distance(ps.a, ps.b);
if (lastParam <= tempParam)
{
currentSegment = ps;
break;
}
}
if (currentSegment == null)
return 0f;
- Given the current position, we need to work out the direction to go to:
Vector3 currPos = position - currentSegment.a;
Vector3 segmentDirection = currentSegment.b - currentSegment.a;
segmentDirection.Normalize();
- Find the point in the segment using vector projection:
Vector3 pointInSegment = Vector3.Project(currPos, segmentDirection);
- Finally, GetParam returns the next position to reach along the path:
param = tempParam - Vector3.Distance(currentSegment.a, currentSegment.b);
param += pointInSegment.magnitude;
return param;
- Define the GetPosition function:
public Vector3 GetPosition(float param)
{
// body
}
- Given the current location along the path, we find the corresponding segment:
Vector3 position = Vector3.zero;
PathSegment currentSegment = null;
float tempParam = 0f;
foreach (PathSegment ps in segments)
{
tempParam += Vector3.Distance(ps.a, ps.b);
if (param <= tempParam)
{
currentSegment = ps;
break;
}
}
if (currentSegment == null)
return Vector3.zero;
- GetPosition converts the parameter as a spatial point and returns it:
Vector3 segmentDirection = currentSegment.b - currentSegment.a;
segmentDirection.Normalize();
tempParam -= Vector3.Distance(currentSegment.a, currentSegment.b);
tempParam = param - tempParam;
position = currentSegment.a + segmentDirection * tempParam;
return position;
- Create the PathFollower behavior, which derives from Seek (remember to set the order of execution):
using UnityEngine;
using System.Collections;
public class PathFollower : Seek
{
public Path path;
public float pathOffset = 0.0f;
float currentParam;
}
- Implement the Awake function to set the target:
public override void Awake()
{
base.Awake();
target = new GameObject();
currentParam = 0f;
}
- The final step is to define the GetSteering function that relies on the abstraction created by the Path class to set the target position and apply Seek:
public override Steering GetSteering()
{
currentParam = path.GetParam(transform.position, currentParam);
float targetParam = currentParam + pathOffset;
target.transform.position = path.GetPosition(targetParam);
return base.GetSteering();
}