Grid graph movement for units with different size

The _ai.SetPath call should be at the end of OnPathComplete, like in my previous code snippet.

I trying like you said, but I got exception

Exception text:
ArgumentException: You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished.

Then I trying like this, and I got new several exceptions

Exceptions:
ArgumentException: You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished.

Unhandled exception during pathfinding. Terminating.
UnityEngine.Debug:LogError (object)
Pathfinding.PathProcessor:CalculatePathsThreaded (Pathfinding.PathHandler) (at Assets/AstarPathfindingProject/Core/Misc/PathProcessor.cs:408)
Pathfinding.PathProcessor/<>c__DisplayClass24_0:<.ctor>b__0 () (at Assets/AstarPathfindingProject/Core/Misc/PathProcessor.cs:110)
System.Threading.ThreadHelper:ThreadStart ()

Error : This part should never be reached.
UnityEngine.Debug:LogError (object)

Look how I modified my code. Now it’s works without exception, but still I can’t setup offsets for 2x2 objects.

By the way, if I use AstarPath.StartPath and his callback, that doesn’t work and throw exceptions

Hmmm. Right. I think you are encountering an issue in the currently released version in which calling ai.SetPath from an OnPathComplete callback is not allowed. I have fixed this in the beta, but I haven’t backported the fix yet.

Your new code will not work because you are trying to modify the vectorPath before the path has been calculated. At that time the vectorPath will always have a length of zero. You need to offset it in the OnPathComplete method.

Sorry for all the trouble.

No problem. I offset modifying of vector path in the OnPathComplete. But it’s still not desired behaviour :disappointed_relieved:

image

Here’s a script that I have verified works:

using UnityEngine;
using Pathfinding;
using Pathfinding.Drawing;

public class GridShapeTraversalProviderTest : VersionedMonoBehaviour {
    IAstarAI ai;
    Seeker seeker;
    public Transform target;
    public int width;

    void Awake () {
        ai = GetComponent<IAstarAI>();
        seeker = GetComponent<Seeker>();
    }

	class GridShapeTraversalProvider : ITraversalProvider {
		Int2[] shape;

		/** Create a GridShapeTraversalProvider using a square shape.
		 * The width parameter must be an odd number.
		 */
		public static GridShapeTraversalProvider SquareShape (int width) {
			var shape = new GridShapeTraversalProvider();
			shape.shape = new Int2[width*width];

			// Create an array containing all integer points within a width*width square
			int i = 0;
			for (int x = 0; x < width; x++) {
				for (int z = 0; z < width; z++) {
					shape.shape[i] = new Int2(x, z);
					i++;
				}
			}
			return shape;
		}

		public bool CanTraverse (Path path, GraphNode node) {
			return DefaultITraversalProvider.CanTraverse(path, node);
		}

		public int OverlappingNodes (Path path, GraphNode node) {
			GridNodeBase gridNode = node as GridNodeBase;

			// Don't do anything special for non-grid nodes
			if (gridNode == null) return 0;
			int x0 = gridNode.XCoordinateInGrid;
			int z0 = gridNode.ZCoordinateInGrid;
			var grid = gridNode.Graph as GridGraph;

			// Iterate through all the nodes in the shape around the current node
			// and check if those nodes are also traversable.
			int count = 0;
			for (int i = 0; i < shape.Length; i++) {
				var inShapeNode = grid.GetNode(x0 + shape[i].x, z0 + shape[i].y);
				if (inShapeNode == null || !DefaultITraversalProvider.CanTraverse(path, inShapeNode)) count++;
			}
			return count;
		}

		public bool CanTraverse (Path path, GraphNode from, GraphNode to) {
			return CanTraverse(path, to);
		}

		public uint GetTraversalCost (Path path, GraphNode node) {
			// Use the default traversal cost.
			// Optionally this could be modified to e.g taking the average of the costs inside the shape.
			return DefaultITraversalProvider.GetTraversalCost(path, node) + (uint)OverlappingNodes(path, node) * 10000;
		}

		// This can be omitted in Unity 2021.3 and newer because a default implementation (returning true) can be used there.
		public bool filterDiagonalGridConnections {
			get {
				return true;
			}
		}
	}

    Vector3 centerOffset() {
        var gg = AstarPath.active.data.gridGraph;
        return gg.nodeSize * new Vector3((width-1)*0.5f, 0, (width-1)*0.5f);
    }

    void SearchPath() {

        // Note: use AstarPath.SearchPath here instead to avoid the movement script picking it up automatically
        var path = ABPath.Construct(transform.position - centerOffset(), target.position - centerOffset(), OnPathComplete);
		path.traversalProvider = GridShapeTraversalProvider.SquareShape(width);
        AstarPath.StartPath(path);
    }

    void OnPathComplete(Path _path) {
		var path = _path as ABPath;
		if (path.error) {
			Debug.LogError(path.errorLog);
			return;
		}
        var offset = centerOffset();
        for (int i = 0; i < path.vectorPath.Count; i++) {
            path.vectorPath[i] += offset;
        }
        path.originalStartPoint += offset;
        path.originalEndPoint += offset;
		path.startPoint += offset;
		path.endPoint += offset;

        // Apply any path modifiers
        seeker.PostProcess(path);

        // Send the path to the agent so that it can follow it
        ai.SetPath(path);

		using(Draw.WithDuration(5.0f)) {
			Draw.Polyline(path.vectorPath, Color.green);
		}
    }

	void Update () {
		if ((Time.frameCount % 60) == 0) {
			SearchPath();
		}
	}
}

I changed it to apply a large penalty to nodes instead of making them completely unwalkable. That will allow it to recover if it happens to move too close to a wall, instead of the path just failing.