Seeking Help with NavMesh and A* Pathfinding Issues

Hello everyone, and a special shout-out to Aron,

I’ve found myself deep into the lengthy process of manually retopologizing an entire level again. The outcome is precise and satisfactory. I’ve tried importing various versions of models, both low poly and high poly, and the results are more or less the same. However, I’m still facing significant challenges with getting any sort of reasonable behavior from the pathfinding algorithm. Whenever a path goes around a corner or over a threshold, the AI starts to act erratically, wandering in circles and often failing to find the ground.

I suspect this issue might be related to how navigation paths between tiles are constructed, connecting their centers, which seems to cause a strange effect. And don’t get me started on AI walking on walls. The NavMesh variant of the graph seems extremely limited in capabilities.

Why is this happening, and what can be done to address it? At this point, I’ve invested a lot of time into this library but haven’t been able to obtain a clear result or find an answer on forums, unfortunately.

Any insights or suggestions would be greatly appreciated. Thank you in advance for your help.

Additionally, I’m attaching a couple of screenshots below to illustrate the issue more clearly. For some reason, spiders in the game are attempting to navigate through walls, even though there’s a perfectly viable path on the floor right next to them. It’s baffling. I’ve disabled my custom scripts to ensure there’s no interference, and I’m using the standard setup recommended in the documentation for a NavMesh graph in a spherical world.

Additionally, considering the extensive and time-consuming process of manually retopologizing large levels with dynamic geometry, I’m starting to question whether the NavMesh approach is truly suitable for my project. The manual retopology of NavMesh for such levels is both challenging and inefficient, especially when planning for significant dynamic changes in the game environment. Is there a more dynamic alternative to NavMesh that could accommodate large levels with dynamic geometry more efficiently? What alternatives would you recommend for handling pathfinding in such scenarios? I’m looking for a solution that would allow for easier updates and adjustments as the game world changes.

Your suggestions and recommendations on this matter would be extremely helpful. I’m open to exploring new methods and technologies that could provide a more flexible and dynamic solution to pathfinding challenges. Thank you once again for your support and advice.

This persistent issue has left me puzzled, and I’m eager to hear if anyone has encountered something similar or has insights on how to resolve it. Your feedback would be invaluable to me as I try to navigate these challenges.









I haven’t been sleeping for three nights :frowning: I’ve run out of ideas why he’s taking such a strange path through the walls when there’s a perfect path on the floor. What’s causing him to deviate? I need help. SOS, people.

the option with extra raycast beams works quite poorly; the beams don’t account for all the bends and inclines. The option with the curved mesh also didn’t solve the problem. The nav mesh is still far from ideal, but it’s fairly even, and its paths are much straighter than going through walls. It’s as if the algorithm doesn’t consider the walls at all. It would be great to determine ground normals based on the path point or something entirely different.

This code is currently the best solution for this problem, but it’s far from ideal.

using UnityEngine;

namespace Pathfinding {
    public class WallCrawler : AIPathAlignedToSurface {
        [Header("Grounding")]
        [SerializeField]
        private CapsuleCollider capsuleCollider;
        [SerializeField]
        private LayerMask walkableLayer;

        [Header("Ray Adjustments")]
        [Range(0.0f, 10.0f)]
        [SerializeField]
        private float forwardRayLength;
        [Range(0.0f, 10.0f)]
        [SerializeField]
        private float forwardRayRadius;
        [Range(0.0f, 10.0f)]
        [SerializeField]
        private float downRayLength;
        [Range(0.0f, 10.0f)]
        [SerializeField]
        private float downRayRadius;

        private Vector3 GroundNormal;
        private RaycastHit HitInfo;
        
        protected override void OnUpdate(float dt) {
            base.OnUpdate(dt);
            UpdateMovementPlane();
        }

        protected override void UpdateMovementPlane() {
            if (Physics.SphereCast(transform.TransformPoint(capsuleCollider.center), downRayRadius, -transform.up, out HitInfo, downRayLength, walkableLayer))
            {
                Debug.Log("Bottom Hit");
                GroundNormal = HitInfo.normal.normalized;
            }

            if (Physics.SphereCast(transform.TransformPoint(capsuleCollider.center), forwardRayRadius, transform.forward, out HitInfo, forwardRayLength, walkableLayer))
            {
                Debug.Log("Forward Hit");
                GroundNormal = HitInfo.normal.normalized;
            }

            if (GroundNormal != Vector3.zero)
            {
                var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, GroundNormal);
                movementPlane = new Pathfinding.Util.SimpleMovementPlane(Quaternion.LookRotation(fwd, GroundNormal));
            }
        }
    }
}

Here’s the translation:


Hello everyone, I want to share my current results, attaching a video.

I currently have 2 problems.

  1. As seen in the video at the end. When a certain number of agents is reached, everything suddenly stops, and everyone stops moving, with no errors whatsoever.

  2. Is there a way to specify an offset from the surface of the navmesh for an agent? It seems there is no such setting, in the documentation centerOffset is marked as deprecated. And it talks about a hack with height, but I think this isn’t quite right. To clarify, I would like to somehow make the agent completely ignore this threshold.

Is there a way to make agents perceive the navmesh more smoothly? With some kind of tolerance, roughly speaking. There’s a similar setting in Unity’s default navigation system.

Hello, everyone. As usual, I want to share the results of my work this morning.

I read about an interesting method of ray casting. I divided them into 2 groups: some are cast directly under the agent, while others are slightly offset. This is clearly visible in the video. I am also attaching the latest version of the script. Maybe someone will have ideas or a review. I would be very grateful.


using UnityEngine;

namespace Pathfinding {
    // Custom AI pathfinding class that aligns the agent with surfaces
    public class WallCrawler : AIPathAlignedToSurface {
        // Smoothness of the agent's rotation to align with the surface
        [SerializeField]
        private float smoothness = 360f;
        // Number of rays cast to determine the surface alignment
        [SerializeField]
        private int raysNb = 130;
        // Affects the distribution of rays cast around the agent
        [SerializeField]
        private float raysEccentricity = 0.3f;
        // Offset for the outer ring of rays
        [SerializeField]
        private float outerRaysOffset = 0.2f;
        // Offset for the inner ring of rays
        [SerializeField]
        private float innerRaysOffset = 31f;

        // Called every frame to update the agent's movement and alignment
        protected override void OnUpdate(float dt) {
            base.OnUpdate(dt);
            UpdateMovementPlane();
        }

        // Updates the movement plane based on the agent's current position and orientation
        protected override void UpdateMovementPlane() {
            var transform1 = transform;
            var rayDirections = GetClosestPoint(transform1.position, transform1.forward, transform1.up, 0.5f, raysEccentricity, outerRaysOffset, innerRaysOffset, raysNb);
            var averageNormal = rayDirections[1];

            // If no valid surface is detected, exit the function
            if (averageNormal == Vector3.zero) return;
            
            // Calculate forward direction based on surface normal
            var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, averageNormal).normalized;
            // Determine the target rotation for alignment
            var targetRotation = Quaternion.LookRotation(fwd, averageNormal);
        
            // Smoothly rotate the movement plane to align with the surface
            movementPlane = new Util.SimpleMovementPlane(Quaternion.Lerp(movementPlane.rotation, targetRotation, Time.deltaTime * smoothness));
        }

        // Casts rays to detect the closest surface and its normal
        private static Vector3[] GetClosestPoint(Vector3 point, Vector3 forward, Vector3 up, float halfRange, float eccentricity, float offset1, float offset2, int rayAmount)
        {
            var res = new Vector3[2] { point, up }; // Initial results array
            var right = Vector3.Cross(up, forward); // Calculate right vector
            
            // Initialize counters for averaging results
            var normalAmount = 1f;
            var positionAmount = 1f;

            // Array to store directions of rays
            var dirs = new Vector3[rayAmount];
            // Calculate angular step for even distribution of rays
            var angularStep = 2f * Mathf.PI / rayAmount;
            var currentAngle = angularStep / 2f;
            
            // Generate ray directions based on eccentricity and angular step
            for(var i = 0; i < rayAmount; ++i)
            {
                dirs[i] = -up + (right * Mathf.Cos(currentAngle) + forward * Mathf.Sin(currentAngle)) * eccentricity;
                currentAngle += angularStep;
            }

            // Cast rays in calculated directions to detect surfaces
            foreach (var dir in dirs)
            {
                var largener = Vector3.ProjectOnPlane(dir, up);
                var ray = new Ray(point - (dir + largener) * halfRange + largener.normalized * offset1 / 100f, dir);
                Debug.DrawRay(ray.origin, ray.direction); // Visualize ray for debugging
                if (Physics.SphereCast(ray, 0.01f, out var hit, 2f * halfRange))
                {
                    // Accumulate hit points and normals for averaging
                    res[0] += hit.point;
                    res[1] += hit.normal;
                    normalAmount += 1;
                    positionAmount += 1;
                }
                // Repeat with adjusted offset for additional surface detection
                ray = new Ray(point - (dir + largener) * halfRange + largener.normalized * offset2 / 100f, dir);
                Debug.DrawRay(ray.origin, ray.direction, Color.green); // Visualize ray for debugging
                if (!Physics.SphereCast(ray, 0.01f, out hit, 2f * halfRange)) continue;
                // Accumulate hit points and normals for averaging
                res[0] += hit.point;
                res[1] += hit.normal;
                normalAmount   += 1;
                positionAmount += 1;
            }
            
            // Average out the results to get the closest point and its normal
            res[0] /= positionAmount;
            res[1] /= normalAmount;
            
            return res;
        }
    }
}

Hi

I did some experiments today with adding better support for following a curved surface in the FollowerEntity movement script. It takes a smoothed average of the navmesh normals around itself.

This is the result:

I think it should work pretty well for your use case?
Note how it can pretty easily handle 90 degree angles in the navmesh, in contrast with the previous approaches. Though small bevels look a bit better.

1 Like

Aron, hello, wow, this looks amazing on video. Fantastic work. Is this already available in the new version of the library? Can I update? How can I try this out?

It’s not available yet. I hope to release an update in the next few days, though.

When that’s done, you can just add a FollowerEntity component to your agents (removing the Seeker and AIPathAlignedToSurface scripts) and set the Movement Plane Source to “Navmesh Normal”. Hopefully everything should then work automagically.

1 Like

This looks absolutely stunning. I can’t wait to see more. It’s very interesting how such an impressive behavior was achieved under such complex conditions. The normals of the meshes are nearly touching each other at a 180-degree angle, and everything looks super cool. The 90-degree turn followed immediately by another 90-degree turn also seems almost unreal. Overall, I am very impressed, a perfect 10 out of 10.

1 Like

The update is published now :slight_smile:

1 Like

Thank you, Aron. I downloaded the new version of the library, updated it, and did everything you said. I removed the Seeker and AIPathAlignedToSurface scripts and installed the FollowerEntity on the agent. It works marvelously. It’s a completely different matter compared to AIPathAlignedToSurface. Everything has become significantly simpler. Just two clicks and it works perfectly.

Thank you for the great job done.

Awesome! I’m glad it’s working so well for you now :slight_smile:

1 Like