Grid graph movement for units with different size

That would have to be done by you in your OnPathComplete function. Or I guess in a custom path modifier (but that’s more complex). Iterate over all points in the path.vectorPath list and add a constant offset to each of the points.

Ok, correct me if I’m wrong: I can leave the implementation as it is in the example

And then I change offset like on second screenshot?

Yes. That looks perfectly correct.
Then you just need to offset the path by nodeSize * (width/2, height/2)

Thank you, seems like it’s working
image

But what if I want the object to fit exactly on the grid squares. Something like that:
image

Again, sorry for annoying)

Right, I think my offset that I mentioned was incorrect. It should be:
nodeSize * ((width - 1)/2, (height - 1)/2)

Thank you for reply. But nothing really changes(Sorry can’t load more than 2 image, but it’s same as in a previous reply)

I show you again my code. I think problem in ITraversalProvider realization.


You are creating your SquareShape with a constant size of 2. But your _width and _height fields may not be 2. Are they also 2 or are they set to different values?

_width and _height are also set to 2. I trying to make movement for 2x2 size objects

That’s odd. In that case, the offset should be 0.5 nodes in each direction. So the object should end up between nodes, not at the center of nodes.

Yeah, I get it. But how I can do this)? If I do it like on picture, nothing changes
image
image

Hmm. That script doesn’t move the agent. How do you move the actual agent?

If you are using a movement script, you probably want to send the path to the movement script after you have modified the path:

void SearchPath() {
    ...
    // Note: use AstarPath.SearchPath here instead to avoid the movement script picking it up automatically
    AstarPath.SearchPath(path);
}

void OnPathComplete(Path path) {
    ... modify the path here
    path.originalStartPoint += offset;
    path.originalEndPoint += offset;
   
     // Apply any path modifiers
     seeker.PostProcess(path);
     // Send the path to the agent so that it can follow it
    _ai.SetPath(path);
}

My code look like that:


If I change it to that, my object doesn’t move anymore(and I don’t have SearchPath() method in AstarPath class

Sorry. AstarPath.StartPath. You should call that after you set the traversal provider.

ABPath path = ...
path.traversalProvider = ...
AstarPath.StartPath(path);

Hmmm, my code now looks like this:

But it’s still doesn’t move my object. Am I doing everything right?

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.