Custom GridGraphRule - removing connections

Hello!

So im trying to add a custom GridGraphRule where if a directonal-block tilemap has a tile, it would remove the connection going in that direction. Ive tried several things and there is no helpful example that I could find for how to achieve something like this. Ive pasted my code below and I have a few questions about it.

  1. Is using the Pass.AfterConnections correct?
  2. I cant access nodes through context.graph.nodes because its null in this pass. How can i access the nodes? Should I even access them?
  3. Assuming I cant access them, I tried using GetNeighbourDataIndex passing in the values you see below and it always returns null. It should never be null because i dont have a directional block that goes off the map, so all neighbors should be in bounds.
  4. Does a value of 0 mean no connection when accessing context.data.nodeConnections?

I mainly want to know how to remove a connection in one of these custom rules. Id appreciate any help!


    public class DirectionalBlockGridRule : GridGraphRule
    {

        public Tilemap[] FunctionalTilemaps;

        // if you wanna add 1-way connections:
        //  https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
        //  https://forum.arongranberg.com/t/bug-with-one-way-links-floodfill-areas-and-abpath/1794
        public override void Register(GridGraphRules rules)
        {
            base.Register(rules);

            if (!FunctionalTilemaps.Any())
            {
                Debug.LogWarning("Must have functional tilemap assigned");
                return;
            }

            rules.AddMainThreadPass(Pass.AfterConnections, context =>
            {
                NativeArray<Vector3> nodePositions = context.data.nodePositions;
                NativeArray<int> nodeConns = context.data.nodeConnections;

                for (int i = 0; i < nodePositions.Length; i++)
                {
                    foreach (Tilemap fnMap in FunctionalTilemaps)
                    {
                        Vector3Int tilePos = Vector3Int.FloorToInt(nodePositions[i]);
                        TileBase tile = fnMap.GetTile(tilePos);
                        if (tile == null) continue;

                        //   Z
                        //   |
                        // 6 2 5
                        // 3 x 1  - X
                        // 7 0 4
                        if (tile.name.ToLower().Contains("up"))
                        {
                            int? neighborIndex = GetNeighbourDataIndex(
                                context.data.bounds, context.data.nodeConnections, context.data.layeredDataLayout,
                                tilePos.x, tilePos.y, tilePos.z, 2);

                            context.data.nodeConnections[neighborIndex.Value] = 0;
                        }
                    }
                }
            });

        }

    }

#if UNITY_EDITOR
    [CustomGridGraphRuleEditor(typeof(DirectionalBlockGridRule), "Directional Block Grid Rule")]
    public class DirectionalBlockGridRuleEditor : IGridGraphRuleEditor
    {

        public void OnInspectorGUI(GridGraph graph, GridGraphRule rule)
        {
            DirectionalBlockGridRule target = rule as DirectionalBlockGridRule;

            if (!target.FunctionalTilemaps.Any() || target.FunctionalTilemaps[0] == null)
                target.FunctionalTilemaps = GameObject.FindGameObjectsWithTag("FunctionMap").Select(x => x.GetComponent<Tilemap>()).ToArray();
            
            if (target.FunctionalTilemaps.Any())
                for (int i = 0; i < target.FunctionalTilemaps.Length; i++)
                    target.FunctionalTilemaps[i] = (Tilemap)EditorGUILayout.ObjectField("Fn Tilemap "+i, target.FunctionalTilemaps[i], typeof(Tilemap), true);
        }

        public void OnSceneGUI(GridGraph graph, GridGraphRule rule)
        {
        }

    }
#endif

hopefully this image will help illustrate what Im trying to do, which is remove the connections going in the direction each arrow is pointing
image

I figured it out using jobs instead. Here is my script if anyone needs this in the future!

using System.Linq;
using Extensions;
using Pathfinding;
using Pathfinding.Jobs;
using Unity.Collections;
using Unity.Jobs;
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;

namespace Mapping
{

    public class DirectionalBlockGridRule : GridGraphRule
    {

        [System.Flags]
        public enum Dir
        {
            None = 0,
            Down = 1,
            Right = 2,
            Up = 4,
            Left = 8,
        }

        struct RemoveConnJob : IJob, IConnectionFilter
        {
            //public Tilemap[] FunctionMaps;
            public IntBounds bounds;
            public NativeArray<int> nodeConnections;

            [WriteOnly]
            public NativeArray<bool> nodeWalkable;
            [ReadOnly]
            public NativeArray<Vector3> nodePositions;
            public bool layeredDataLayout;
            [ReadOnly]
            public NativeArray<Dir> Dirs;

            public void Execute()
            {
                FilterNodeConnections(bounds, nodeConnections, layeredDataLayout, ref this);
            }

            public bool IsValidConnection(int dataIndex, int dataX, int dataLayer, int dataZ, int direction)
            {   
                // Find the data index of the adjacent node
                var neighbourDataIndex = GetNeighbourDataIndex(bounds, nodeConnections, layeredDataLayout, dataX, dataLayer, dataZ, direction);

                // Get our position and the adjacent node's position
                var position = nodePositions[dataIndex];
                var neighbourPosition = nodePositions[neighbourDataIndex.Value];
                var dir = Dirs[dataIndex];

                if (dir.HasFlag(Dir.Up) && Vector3Int.RoundToInt(neighbourPosition - position) == Vector3Int.up)
                    return false;
                if (dir.HasFlag(Dir.Down) && Vector3Int.RoundToInt(neighbourPosition - position) == Vector3Int.down)
                    return false;
                if (dir.HasFlag(Dir.Left) && Vector3Int.RoundToInt(neighbourPosition - position) == Vector3Int.left)
                    return false;
                if (dir.HasFlag(Dir.Right) && Vector3Int.RoundToInt(neighbourPosition - position) == Vector3Int.right)
                    return false;

                return true;
            }
        }

        public Tilemap[] FunctionalTilemaps;

        public override void Register(GridGraphRules rules)
        {
            base.Register(rules);

            if (!FunctionalTilemaps.Any())
            {
                Debug.LogWarning("Must have functional tilemap assigned");
                return;
            }

            rules.AddJobSystemPass(Pass.AfterConnections, context =>
            {
                NativeArray<Dir> dirs = new NativeArray<Dir>(context.data.nodePositions.Length, Allocator.TempJob);

                for (int i = 0; i < context.data.nodePositions.Length; i++)
                {
                    Dir dir = 0;
                    var pos = context.data.nodePositions[i];
                    Vector3Int tilePos = Vector3Int.FloorToInt(pos);

                    foreach (var fnMap in FunctionalTilemaps)
                    {
                        TileBase tile = fnMap.GetTile(tilePos);
                        if (tile == null) continue;

                        dir |= tile.name.ToLower().Contains("down") ? Dir.Down
                            : tile.name.ToLower().Contains("right") ? Dir.Right
                            : tile.name.ToLower().Contains("up") ? Dir.Up
                            : tile.name.ToLower().Contains("left") ? Dir.Left
                            : Dir.None;
                    }

                    dirs[i] = dir;
                }


                RemoveConnJob job = new RemoveConnJob
                {
                    bounds = context.data.bounds,
                    nodeConnections = context.data.nodeConnections,
                    nodeWalkable = context.data.nodeWalkable,
                    nodePositions = context.data.nodePositions,
                    layeredDataLayout = context.data.layeredDataLayout,
                    Dirs = dirs,
                };
                job.Schedule(context.tracker);
            });

        }

    }

#if UNITY_EDITOR
    [CustomGridGraphRuleEditor(typeof(DirectionalBlockGridRule), "Directional Block Grid Rule")]
    public class DirectionalBlockGridRuleEditor : IGridGraphRuleEditor
    {

        public void OnInspectorGUI(GridGraph graph, GridGraphRule rule)
        {
            DirectionalBlockGridRule target = rule as DirectionalBlockGridRule;

            if (!target.FunctionalTilemaps.Any() || target.FunctionalTilemaps[0] == null)
                target.FunctionalTilemaps = GameObject.FindGameObjectsWithTag("FunctionMap").Select(x => x.GetComponent<Tilemap>()).ToArray();
            
            if (target.FunctionalTilemaps.Any())
                for (int i = 0; i < target.FunctionalTilemaps.Length; i++)
                    target.FunctionalTilemaps[i] = (Tilemap)EditorGUILayout.ObjectField("Fn Tilemap "+i, target.FunctionalTilemaps[i], typeof(Tilemap), true);
        }

        public void OnSceneGUI(GridGraph graph, GridGraphRule rule)
        {
        }

    }
#endif

}

1 Like

Hi

Iā€™m glad you managed to solve it :slight_smile:

  1. Yes, AfterConnections seems reasonable. Though, PostProcess could also work.
  2. In GridGraphRules you must access node data using the arrays in context.data.