Movement cost in 2D Grid Graph

Thank you, it works perfectly now.

About my previous post, can you give me some advice about it?

1 is about penalty point and other one is ITraversalProvider.

It’s OK if you are too busy to tell me more about TraversalProvider but I hope in future there will be more document or example about it.

But the important thing is Penalty point of Tag, please help me, how do I apply it correctly to make Unit limit number of nodes they can move.

Hi

Here’s an example script for you:

Due to some limitations right now, making this behave just like you want it to requires some somewhat ugly tweaks.
The cost of a path is defined as:

sum(traversalProvider.GetTraversalCost(node) for node in path) +
sum(connection cost between each adjacent node in path)

There are two things that need tweaking:

  1. I don’t think you want to consider the movement cost for the start node, right? In the example code below, I have compensated for that by returning 0 for the start node.
  2. The connection cost between nodes can currently not be modified by the ITraversalProvider. This is something I’m working on improving. However, we can compensate for it by subtracting the default connection cost when we run the ITraversalProvider.GetTraversalCost method. This is pretty ugly, but it works for your use case.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;

public class MovementPointTest : MonoBehaviour {
    public int movementPoints = 2;

    class MyCustomTraversalProvider : ITraversalProvider {
        public uint defaultCostToMoveBetweenTwoNodes;

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

		public uint GetTraversalCost(Path path, GraphNode node) {
            if (node == (path as ConstantPath).startNode) return 0;

			uint c = 0;
            if (node.Tag == 0) {
                c = 1000;
            } else if (node.Tag == 1) {
                c = 2000;
            } else if (node.Tag == 2) {
                c = 4000;
            }
            return (uint)(c - defaultCostToMoveBetweenTwoNodes);
		}
	}

    void Update() {
        var defaultCostToMoveBetweenTwoNodes = AstarPath.active.data.gridGraph.neighbourCosts[0];
        var maxGScore = 1000 * movementPoints;
        // +1 to make the path find all nodes up to *and including* the max g score
        maxGScore += 1;
        ConstantPath path = ConstantPath.Construct(transform.position, maxGScore, null);
        path.traversalProvider = new MyCustomTraversalProvider() { defaultCostToMoveBetweenTwoNodes = defaultCostToMoveBetweenTwoNodes };

        AstarPath.StartPath(path);
        path.BlockUntilCalculated();

        var allNodes = path.allNodes;
        foreach (var node in allNodes) {
            Pathfinding.Drawing.Draw.SolidBox((Vector3)node.position, Vector3.one * 0.5f, Color.red);
        }
    }
}

This produces a result like this:

where the blue region has tag 1, so it costs 2 movement points to traverse each node, using the above code.

It will indeed. But you you want to post-process the path using the Seeker’s modifiers you can use the seeker.PostProcess(path) method.
See Seeker - A* Pathfinding Project

Haha, even with limitations, it’s nice to see you still can give solution.

I got it, so with penalty point of tag alone won’t be enough to do it, must use TraversalProvider for this move cost.

Also thank you for example about TraversalProvider and the solution for move cost using it.

By the way, because I’m following your Block Manager code as below on UnitController

private void Awake() {
        TraversalProvider = new BlockManager.TraversalProvider(BlockManager, BlockManager.BlockMode.AllExceptSelector, new List<SingleNodeBlocker> { SingleNodeBlocker });
    }

And below is the modifed code for GeneratePossibleMoves base on your guide which include block all SingleNodeBlocker instance.

var path = ConstantPath.Construct(unit.transform.position, unit.MovementPoints * 1000 + 1);

        path.traversalProvider = unit.TraversalProvider;
        path.enabledTags = unit.GetSeeker().traversableTags;
        path.tagPenalties = unit.GetSeeker().tagPenalties;
        // Schedule the path for calculation
        AstarPath.StartPath(path);

Now it raises another problem, how to use BlockManager.TraversalProvider and MyCustomTraversalProvider together ?

You can put the block manager’s traversal provider as a field inside your own custom traversal provider. And then just forward the CanTraverse call to it.

1 Like

Hi, thank to your support, I have created CustomTraversalProvider to suite my need as below

public class CustomTraversalProvider : ITraversalProvider {

    private uint _defaultCostToMoveBetweenTwoNodes = AstarPath.active.data.gridGraph.neighbourCosts[0];

    private ITraversalProvider _blockManagerTraversalProvider;

    public CustomTraversalProvider(ITraversalProvider blockManagerTraversalProvider) {
        _blockManagerTraversalProvider = blockManagerTraversalProvider;
    }

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

    public uint GetTraversalCost(Path path, GraphNode node) {
        if (path is ConstantPath && node == (path as ConstantPath).startNode) return 0;
        if (path is ABPath && node == (path as ABPath).startNode) return 0;

        uint cost = 0;
        if (node.Tag == 0) {
            cost = 1000;
        } else if (node.Tag == 1) {
            cost = 2000;
        }

        return cost - _defaultCostToMoveBetweenTwoNodes;
    }
}

It works perfectly for now, but I have encountered another problem relate to BlockManager.

Following document, I have attached 2 components into my Unit which are SingleNodeBlocker and UnitController, both of them have field public BlockManager BlockManager; which will be assign by Drag and Drop in Editor.

But when making my unit into a Prefab, the BlockManager can’t be assigned anymore, I can modify my UnitController to solve this but what about SingleNodeBlocker ?

How do you usually apply Blocking nodes method for prefab case ?

This is more of a general Unity question of how to handle prefabs.
Generally, you’d use a script to assign this reference at the same time that you instantiate the object into your scene.

1 Like

Yeah, I knew it, just asking to know if you handle your component in any build-in way.

Thank for your answer anyway. :wink:

1 Like