Glitchy pathfinding with recast graph

  • A* version: 5.3.8
  • Unity version: 6000.0.32f1

So i’m trying to integrate the package with our game. it is not behaving well when the recast graph has a lot of dense triangle(thats what i think is causing the issue). the agent would spin around where the triangles share a vertex.

would love to post a link to the video but don’t have the permission. if i can send the video in another way please let me know.

i don’t actually want rotation at all in my game, by setting the turn in place rotation speed to a very high value, the glitch is even worse, the agent will rapidly changing between 2 opposite direction.

ok finally i can upload video!

Which movement script are you using?

we are using ECS, wrote our own baker

using Pathfinding.ECS;
using Pathfinding.ECS.RVO;
using Pathfinding.PID;
using Pathfinding.RVO;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using DestinationPoint = Pathfinding.ECS.DestinationPoint;
using MovementControl = Pathfinding.ECS.MovementControl;

public struct FollowerEntityBody : IComponentData {
    public float velocity;
    public float remainingDistance;
    public float3 previousPosition;
}

public class FollowerEntityAuthoring : MonoBehaviour {
    [SerializeField] AgentCylinderShape shape = new AgentCylinderShape {
        height = 2,
        radius = 0.5f,
    };

    [SerializeField] MovementSettings movement = new MovementSettings {
        follower = new PIDMovement {
            rotationSpeed = 600,
            speed = 5,
            maxRotationSpeed = 720,
            maxOnSpotRotationSpeed = 720,
            slowdownTime = 0.5f,
            desiredWallDistance = 0.5f,
            allowRotatingOnSpot = true,
            leadInRadiusWhenApproachingDestination = 1f,
        },
        stopDistance = 0.2f,
        rotationSmoothing = 0f,
        groundMask = -1,
        isStopped = false,
    };

    [SerializeField] OrientationMode orientation = OrientationMode.ZAxisForward;
    [SerializeField] MovementPlaneSource movementPlane = MovementPlaneSource.Graph;

    [SerializeField] public ManagedState managedState = new ManagedState {
        enableLocalAvoidance = false,
        pathfindingSettings = PathRequestSettings.Default,
    };

    [SerializeField] Pathfinding.ECS.AutoRepathPolicy autoRepath = Pathfinding.ECS.AutoRepathPolicy.Default;

    public PhysicsScene physicsScene;

    public class Baker : Baker<FollowerEntityAuthoring> {
        public override void Bake(FollowerEntityAuthoring authoring) {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);

            float3 pos = authoring.transform.position;
            quaternion rot = authoring.transform.rotation;

            if (authoring.orientation == OrientationMode.YAxisForward)
                rot = math.mul(rot, SyncTransformsToEntitiesSystem.YAxisForwardToZAxisForward);

            AddComponent(entity, new FollowerEntityBody());
            AddComponent(entity, new MovementState(pos));
            AddComponent(entity, new AgentMovementPlane(rot));
            AddComponent(entity, authoring.managedState.rvoSettings);

            AddComponent(entity, new DestinationPoint {
                destination = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity)
            });

            var autoRepath = authoring.autoRepath;
            autoRepath.Reset();
            AddComponent(entity, autoRepath);
            AddComponent(entity, authoring.movement);

            AddComponent(entity, new MovementStatistics {
                estimatedVelocity = float3.zero,
                lastPosition = pos
            });

            AddComponent(entity, authoring.shape);

            AddComponent(entity, new GravityState());
            SetComponentEnabled<GravityState>(entity, authoring.managedState.enableGravity);

            if (authoring.orientation == OrientationMode.YAxisForward)
                AddComponent<OrientationYAxisForward>(entity);

            AddComponent(entity, new ReadyToTraverseOffMeshLink());
            SetComponentEnabled<ReadyToTraverseOffMeshLink>(entity, false);

            AddSharedComponent(entity, new AgentMovementPlaneSource { value = authoring.movementPlane });
            AddSharedComponent(entity, new PhysicsSceneRef { physicsScene = authoring.physicsScene });

            AddComponent(entity, new MovementControl { });
            AddComponent(entity, new SearchState { });
            AddComponent(entity, new ResolvedMovement { });
            AddComponent(entity, new SimulateMovement { });
            AddComponent(entity, new SimulateMovementRepair { });
            AddComponent(entity, new SimulateMovementControl { });
            AddComponent(entity, new SimulateMovementFinalize { });
            AddComponent(entity, new ManagedStateInit { });

            ResolvedMovement resolvedMovement = default;
            MovementControl movementControl = default;
            AgentMovementPlane movementPlane = new AgentMovementPlane(rot);
            ResetControl(ref resolvedMovement, ref movementControl, ref movementPlane, pos, rot, pos);

            SetComponent(entity, movementPlane);
            SetComponent(entity, resolvedMovement);
            SetComponent(entity, movementControl);

            AddComponentObject(entity, authoring.managedState);
        }

        static void ResetControl(ref ResolvedMovement resolvedMovement, ref MovementControl controlOutput,
            ref AgentMovementPlane movementPlane, float3 position, quaternion rotation, float3 endOfPath) {
            resolvedMovement.targetPoint = position;
            resolvedMovement.speed = 0;
            resolvedMovement.targetRotation = resolvedMovement.targetRotationHint = controlOutput.targetRotation =
                controlOutput.targetRotationHint = movementPlane.value.ToPlane(rotation);
            controlOutput.endOfPath = endOfPath;
            controlOutput.speed = 0f;
            controlOutput.targetPoint = position;
        }
    }
}

public struct ManagedStateInit : IComponentData {
}

[UpdateInGroup(typeof(DroneUpdateGroup))]
public partial struct ManagedStateSetupSystem : ISystem {
    void OnUpdate(ref SystemState state) {
        var world = World.DefaultGameObjectInjectionWorld;
        NativeList<Entity> removeEntities = new NativeList<Entity>(Allocator.Temp);
        foreach (var (managedState, entity) in SystemAPI.Query<ManagedState>().WithAny<ManagedStateInit>()
                     .WithEntityAccess()) {
            //The path tracer gets disposed in the authoring component we need to create it
            if (!managedState.pathTracer.isCreated) {
                managedState.pathTracer = new PathTracer(Allocator.Persistent);
                world.EntityManager.SetComponentData(entity, managedState);
            }

            removeEntities.Add(entity);
        }

        //Clear the init tag
        world.EntityManager.RemoveComponent(removeEntities.AsArray(), typeof(ManagedStateInit));
        removeEntities.Dispose();
    }
}

[UpdateInGroup(typeof(DroneUpdateGroup))]
public partial struct FollowerEntityBodySystem : ISystem {
    void OnUpdate(ref SystemState state) {
        foreach (var (followerEntityBody, entity) in SystemAPI.Query<RefRW<FollowerEntityBody>>().WithEntityAccess()) {
            var position = SystemAPI.GetComponent<LocalToWorld>(entity).Position;

            followerEntityBody.ValueRW.remainingDistance = math.distance(SystemAPI.GetComponent<MovementState>(entity).endOfPath, position);
            followerEntityBody.ValueRW.velocity = math.distance(position, followerEntityBody.ValueRW.previousPosition) / Time.deltaTime;
            followerEntityBody.ValueRW.previousPosition = position;
        }
    }
}

command code:

ref MovementSettings movementSettings,
        ref DestinationPoint destinationPoint
---
                    movementSettings.isStopped = false;
                    destinationPoint.destination = destinationPosition;

some additional info: increasing the voxel size to 0.1 from 0.0666 seems to improve on this issue, i am not sure if i just got lucky because of that though.

Strange. Do you think you can enable all movement debug options in the MovementState, and send a video of that?

Also can you send a high-res screenshot of the corner where it gets stuck?

in this video i turned on the debug flags.
i am managing my own rotation hence the model doesnt line up with agent rotation.

When setting the navmesh voxel size to 0.06666, and the grid size to 128, i see a lot of “Possible numerical imprecision. Consider adjusting tileSize and/or cellSize” error. setting voxel size to 0.1 and grid size to 256 eliminate those warnings.

i’ve also attached the graph maybe this would help:

I looked at the graph, and indeed the issue seems to stem from a precision issue near a tile border. Does the issue disappear when you change the tile size to 0.1 or 0.067?

In the next update, I’ll include a change to the editor to round the voxel size to a multiple of 0.001 (internal integer precision used for node coordinates).

with 0.1 the issue seems to go away, i have not tested extensively though. i’ll report back if the issue arises again

2 Likes