I just bought A* pathfinding today and I’m still getting to grips with it.
I’m working on a project in 2D and I would like to employ the equivalent of the “Moving” demo, which uses the LocalSpaceRichAI. I notice from a comment in the AIBaseEditor class that Rich AI is not compatible with 2D and is exclusively designed for XZ traversal.
I’ve therefore been trying to get an equivalent set up for AIPath i.e LocalSpaceAIPath, the documentation for LocalSpaceRichAI suggests it should be relatively straightforward to convert the logic and I basically understand what is being done behind the scenes - perform pathfinding at a graph at zero space and inverse transform the path at runtime, but for the life of me I can’t seem to get it working.
I was hoping somebody could shed some light on how the LocalSpaceRichAI actually does local space pathfinding in a bit more detail and give any pointers towards replicating that logic.
You can make a LocalSpaceAIPath by copy-pasting that script into a new one and changing the base class from RichAI to AIPath. However you need a few extensions to the AIPath script.
You need to add a GraphTransform field to the PathInterpolator class and for all Vector3 parameters that functions get you need to call transform.InverseTransform(value) and for all Vector3 values that are returned from methods and properties in that class you need to call transform.Transform(value).
Then in the LocalSpaceAIPath you need to replace the
richPath.transform = graph.transformation;
line with
interpolator.transform = graph.transformation;
I haven’t tried this myself, but I think this should work.
Then as you discussed in the referenced forum post, I changed the position and circleCenter3D in the functions MoveToClosestPoint, MoveToLocallyClosestPoint, and MoveToCircleIntersection2D. In the beginning of the functions I write:
I re-read my code and I found out that Aron said “…from graph space to world space” and then realized I was accidentally doing the opposite. It works like a charm right now!
I suggest everyone who is interested in a local space AI path to read the second forum post with this answer.
Make sure to use transform.Transform(); in the get functions of position, endPoint, and tangent. And visa versa. I set the point and circleCenter3D after checking the path like this:
point = transform.InverseTransform(point);
I also changed the transform name to graphTransform, as there’s already a transform being used in the MoveToCircleIntersection2D function.
@aron_granberg would it be possible for me to give you my code, so that you can add it to the moving example? I think it would save some people a slight headache!
The AI’s jerk every time the path is recalculated.
To get this to work I made copies of the following scripts (and renamed them) in the project so that I could modify them (because they are now in the package manager):
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding
{
/// <summary>
/// Movement script for curved worlds.
/// This script inherits from AIPath, but adjusts its movement plane every frame using the ground normal.
/// </summary>
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_a_i_path_aligned_to_surface.php")]
public class AIPathAlignedToSurfacePapaJ : AIPathPapaJ
{
/// <summary>Root of the object we are moving on</summary>
public LocalSpaceGraph graph;
protected override void Start()
{
GameObject go = GameObject.Find("Earth");
if (go == null)
return;
graph = go.GetComponent<LocalSpaceGraph>();
base.Start();
movementPlane = new Util.SimpleMovementPlane(rotation);
}
protected override void OnUpdate(float dt)
{
base.OnUpdate(dt);
UpdateMovementPlane();
}
protected override void ApplyGravity(float deltaTime)
{
// Apply gravity
if (usingGravity)
{
// Gravity is relative to the current surface.
// Only the normal direction is well defined however so x and z are ignored.
verticalVelocity += float.IsNaN(gravity.x) ? Physics.gravity.y : gravity.y;
}
else
{
verticalVelocity = 0;
}
}
protected override Vector3 ClampToNavmesh(Vector3 position, out bool positionChanged)
{
// This code clamps the AI to the sphere of radius 5.0f
positionChanged = false;
// Normalize position then scale to radius of sphere
Vector3 newPosition = Vector3.Normalize(position) * 5.0f;
if (newPosition != position) positionChanged = true; // not sure what this does
//Debug.Log(position.ToString());
return newPosition;
}
/// <summary>Find the world position of the ground below the character</summary>
protected override void UpdateMovementPlane()
{
// Construct a new movement plane which has new normal
// but is otherwise as similar to the previous plane as possible
var normal = Vector3.Normalize(transform.position);
var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, normal);
if (fwd != Vector3.zero)
{
movementPlane = new Util.SimpleMovementPlane(Quaternion.LookRotation(fwd, normal));
rvoController.movementPlane = movementPlane;
}
else
{
Debug.Log("APPathAlignedToSurfacePapaJ UpdateMovementPlane fwd Vector3.zero");
}
}
void RefreshTransform()
{
graph.Refresh();
interpolator.graphTransform = graph.transformation;
movementPlane = graph.transformation.ToSimpleMovementPlane();
}
protected override void CalculatePathRequestEndpoints(out Vector3 start, out Vector3 end)
{
RefreshTransform();
base.CalculatePathRequestEndpoints(out start, out end);
start = graph.transformation.InverseTransform(start);
end = graph.transformation.InverseTransform(end);
}
}
}
Any insight on where to look to fix the inspector would be helpful. I think I can figure out the jerking on my own.
Resolved the inspector crashing by creating child classes based on AIPath and PathInterpolator that basically just copy all the code from the parent class. The inspector see them for what it wants it to be yet it is different.
@aron_granberg I changed my mind, I stumped on the jerking when the path is recalculated. I am speculating that it has to do with the mesh moving and there being a disconnect between the local space and world space that is corrected on the next update.
I think the AIPath script might try to set the movement plane when the path is calculated. I think you need to override a function to prevent that from happening. I don’t have any code with me right now, but try to search the AIPath and AIBase scripts for where it sets the movement plane.
@aron_granberg Ahhh, brilliant!!! That worked. I haven’t quite wrapped my head around what the movement plane is all about or why this causes the agents to jump. Here is what I did before you pointed this out (that was mostly working):
After making the last video I noticed that if I lowered the path recalculation rate that the Navemesh wasn’t rotating with the sphere. This made me realize that the Navmesh was not being refreshed in the in the OnUpdate(). So I changed OnUpdate() to this:
Which worked great, and I just turned off automatic path recalculation and just called SearchPath() when setting a new waypoint. This worked great but the agents still hopped when selecting a new way point. By commenting out the updating of the movement plane in RefreshTransform() like this:
Thanks guys! This thread was super helpful. Aron instructors were clear and @ patrickscheper notes were able to reduce the time to implement this to a matter of minutes.
Being a solo dev, I do really appreciate having such a great community of folks creating a strong knowledge base.
Im having the same difficulty trying to make similar for AILerp script. Can you be a little bit more specific on how to do this like it is not clear how the graphTransform field should look like and where to add it… Ive already submitted a post similar to this one but 20 hours and still no reply. I have pro version btw.