SOLUTION: Flying unit pathfinding!

Hello pathfinding community! I’ve seen several inquiries for flying unit pathfinding, and I’m here to provide you with a script that might help with that.

The debug path line on the left uses a point graph with air nodes, and the debug path line on the right uses a navmesh. I have it set up to dynamically generate a point graph based on the dimensions of my navmeshgraph. This means I don’t ever need to worry about my air unit graph - as long as my ground unit graph is configured correctly, it just works!

Note that this is not a one-size-fits all script! This is a proof-of-concept, and should be altered to accommodate your specific use case.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;

public class FlyingGraph : MonoBehaviour
{
    Vector3 checkNodePosition;
    Vector3 createNodeposition;
    Vector3 navMeshBounds;
    public float nodeDistance = 8f;
    void Start()
    {
        PointGraph pg = AstarPath.active.data.AddGraph(typeof(PointGraph)) as PointGraph;
        // maxDistance accommodates diagonal connections as well. If you don't want diagonal connections, remove the 1.75 multiplier
        pg.maxDistance = nodeDistance * 1.75f;

        navMeshBounds = AstarPath.active.data.navmesh.forcedBoundsSize;

        AstarPath.active.AddWorkItem(new AstarWorkItem(ctx =>
        {
            // Iterate through all 3 dimensions and create nodes
            for (checkNodePosition.x = navMeshBounds.x / 2f; checkNodePosition.x >= -navMeshBounds.x / 2f; checkNodePosition.x -= nodeDistance)
            {
                for (checkNodePosition.y = navMeshBounds.y /2f; checkNodePosition.y >= -navMeshBounds.y /2f; checkNodePosition.y -= nodeDistance)
                {
                    for (checkNodePosition.z = navMeshBounds.z / 2f; checkNodePosition.z >= -navMeshBounds.z / 2f; checkNodePosition.z -= nodeDistance)
                    {
                        // Accommodate weird bounding box offset
                        createNodeposition = new Vector3(checkNodePosition.x,checkNodePosition.y+navMeshBounds.y/2f,checkNodePosition.z);
                        // Make sure the node is not too close to any walls. My wall layer mask is 13
                        if (!Physics.CheckSphere(createNodeposition, nodeDistance / 2f, 1 << 13, QueryTriggerInteraction.Collide))
                        {
                            //Uncomment to see primitive spheres where nodes are created
                            //var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                            //sphere.transform.position = createNodeposition;
                            //Destroy(sphere.GetComponent<Collider>());
                            pg.AddNode((Int3)createNodeposition);
                        }

                    }
                }
            }
            // Connect all the dots!
            pg.ConnectNodes();
        }));
        // Force pathfinding to do the work item we just made
        AstarPath.active.FlushWorkItems();
    }
}

Once you have this graph set up, the seeker component on your ground units should use graph 0, and the seeker component on your flying units should use graph 1.

Important note: nodeDistance is highly dependent on your scene’s dimensions. My scene is only about 100x100 units, and if I set the node distance too low (say like around 3), my scene takes an extra 30+ seconds to load. Performance gains/losses are exponential when you increase/decrease this number.

Another important note: This solution depends on the bounding box of another graph, however you can pretty easily modify the code and set your own dimensions.

Let me know if you have any questions or potential optimizations I can make to this code. Thanks!

1 Like

Hi there, i’m trying to do something similar, but i’d like to connect nodes from the Recast to closest nodes from the point graph

(Assuming i’ve only manually placed some Point graph nodes)

AstarPath.active.AddWorkItem((ctx) =>
        {            
            pg.GetNodes((n) =>
            {
                GraphNode closest = rg.GetNearest((Vector3)n.position).node;
                if ((closest.position - n.position).magnitude < 10000)
                {
                    n.AddConnection(closest, 1);
                    closest.AddConnection(n, 1);

                    Debug.DrawLine((Vector3)n.position, (Vector3)closest.position, Color.magenta, 10f);
                }

            });

            pg.ConnectNodes();            
        });

Is what i’m trying possible?

Also, if connections between different graph type is not possible, then maybe i can convert the recast nodes (and the connections) all into Point graph?

Thanks for posting this solution, I have a couple of ideas on how to potentially optimize this. much of it can be done in the editor instead of at run time I believe.

I was just wondering actually if you had any advice on which agent to use for this type of 3D navigation. The AIPath agent doesn’t really work because it was built with 2D planes in mind I believe. AI Lerp seems to work but if it’s seeking a fast moving target the repath causes it to twitch or never actually reach the destination.

Are you using a custom agent for this type of behavior?

Yes, it’s a custom movement script, but is mostly identical to the default one listed in the documentation: Writing a movement script - A* Pathfinding Project