Writing Modifiers
Modifiers are small scripts which post-process paths to, for example, simplify or smooth them.
They are built into the system using extendable add-on architecture which means that it is easy to add your own modifier.
In this tutorial, I will show you how to write a simple path smoother similar to the one included in the project (Components–>Pathfinding–>Modifiers–>SimpleSmooth)
New Script
Begin by creating a new C# script somewhere in your project, name it ModifierTutorial.
Open it up in your favourite script editor, you will have a basic class looking something like this: using UnityEngine;
using System.Collections;
public class ModifierTutorial : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
Now, we are going to make a modifier which can be attached to any GameObject with a Seeker. To do that, we need to inherit from the MonoModifier class.
This class will handle basic communication between the Seeker and the Modifier and will greatly help the writing of the modifier.
It is an abstract class, so some functions need to be implemented in our modifier to not throw compiler errors:
using UnityEngine;
What we have here now is the most basic modifier... which doesn't really do anything, but it will serve as a template for writing modifiers in the future.
using System.Collections;
using Pathfinding;
public class ModifierTutorial : MonoModifier {
public override int Order { get { return 60; } }
public override void Apply (Path p) {
}
}
I have added the "using Pathfinding" statement because MonoModifier exists in the Pathfinding namespace, so we need to include it in our script.
There is also the Order property which decides when, in relation to other modifiers, this modifier is going to get called. The higher the value, the later it is going to get called. So if you have one modifier with an order of 10 and one with order of 20, the one with an order of 10 will be called first. The built-in modifiers use values ranging from 0 to 50, since we have set the order of this modifier to 60, it will be executed after all built-in modifiers (if any are attached).
The Apply function is where we are going to put our code, it will be called when a path needs post-processing.
The Path object supplied is the path which we are going to post-process.
Smoothing the Path
The smoothing algorithm we are going to use is quite simple, it should just draw the points closer together by some amount, and we are going to work on the Pathfinding.Path.vectorPath array. The Pathfinding.Path.path array should never be changed as it might break other modifiers.
First though, we need to check if the path succeeded and if the vectorPath array is not null, otherwise we might end up with evil NullReferenceExceptions.
And also, how are we going to smooth the path if it consists of only a single segment (i.e., <= 2 points)? It can't be done, so we will skip that case too. public override void Apply (Path path) {
Then for the actual smoothing, the algorithm will work as follows:
if (path.error || path.vectorPath == null || path.vectorPath.Count <= 2) {
return;
}
subdivide the path into smaller segments, then loop through the path array, and move each point except the first and the last ones closer to its adjacent points, do that a few times until the path is sufficiently smooth.
Note that the new path gets assigned to the p.vectorPath field, that will enable other scripts to find it.
public int iterations = 5;
public int subdivisions = 2;
public override void Apply (Path path) {
if (path.error || path.vectorPath == null || path.vectorPath.Count <= 2) {
return;
}
// Subdivisions should not be less than zero
subdivisions = Mathf.Max(subdivisions, 0);
// Prevent unknowing users from entering bad values
if (subdivisions > 12) {
Debug.LogWarning("Subdividing a path more than 12 times is quite a lot, it might cause memory problems and it will certainly slow the game down.\n" +
"When this message is logged, no smoothing will be applied");
subdivisions = 12;
return;
}
// Create a new list to hold the smoothed path
List<Vector3> newPath = new List<Vector3>();
List<Vector3> originalPath = path.vectorPath;
// One segment (line) in the original array will be subdivided to this number of smaller segments
int subSegments = (int)Mathf.Pow(2, subdivisions);
float fractionPerSegment = 1F / subSegments;
for (int i = 0; i < originalPath.Count - 1; i++) {
for (int j = 0; j < subSegments; j++) {
// Use Vector3.Lerp to place the points at their correct positions along the line
newPath.Add(Vector3.Lerp(originalPath[i], originalPath[i+1], j*fractionPerSegment));
}
}
// Add the last point
newPath.Add(originalPath[originalPath.Count-1]);
// Smooth the path [iterations] number of times
for (int it = 0; it < iterations; it++) {
// Loop through all points except the first and the last
for (int i = 1; i < newPath.Count-1; i++) {
// Set the new point to the average of the current point and the two adjacent points
Vector3 newpoint = (newPath[i] + newPath[i-1] + newPath[i+1]) / 3F;
newPath[i] = newpoint;
}
}
// Assign the new path to the p.vectorPath field
path.vectorPath = newPath;
}
Pooling
For additional memory efficiency, you can pool the lists so that the garbage collector doesn't have to work so hard. // Get an empty list from the pool
List<Vector3> newPath = Pathfinding.Pooling.ListPool<Vector3>.Claim();
List<Vector3> originalPath = p.vectorPath;
...
// Assign the new path to the p.vectorPath field
p.vectorPath = newPath;
// Pool the previous path, it is not needed anymore
Pathfinding.Pooling.ListPool<Vector3>.Release(originalPath);
The End
That was the end of this tutorial, I hope it will help you get started writing path modifiers.
You can find the full script here: ModifierTutorial.cs