NPC-AI Random Behaviour System doesn't work, need urgent help

I have a script for my NPC system, this script gets a random point from the path points on a navmesh and sets the agent’s destination as this point with the SetDestination() function.
Then when the agent reaches this point, the Idle animation is activated, a timer starts and after a certain amount of time a new random destination is set and the previous steps are repeated…
At least that’s how it SHOULD work, but the last step never happens. So when the agent stops, it doesn’t never move again. What could be the problem here?

Note: All points are on the Navmesh surface, no runtime errors occur.

  • Agent Components


Timer works but when it elapsed nothing happens

public class NPCControllerRandomize : MonoBehaviour
{
    private NavMeshAgent agent;
    private Transform[] pathPoints;
    private int index = 0;
    private int minDistance = 6;
    private float walkingAnimParameter = 0;

    [SerializeField]
    private GameObject path;

    [Title("PathPoints only")]
    [Required]
    [SerializeField]
    private Transform pathStartPoint;

    [Required]
    [SerializeField]
    private Transform pathFinishPoint;

    private System.Random random;
    private static Timer timer;
    private bool isTimerRunning = false;

    private void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        pathPoints = new Transform[path.transform.childCount];
        random = new();
        timer = new Timer(4000);
    }

    private void Start()
    {
        for (int i = 0; i < pathPoints.Length; i++)
        {
            pathPoints[i] = path.transform.GetChild(i);
        }

        timer.AutoReset = false;
        timer.Elapsed += OnTimed;

        SetNewDestination(); // Initial random target

        Debug.Log(Vector3.Distance(transform.position, pathPoints[0].position));
        Debug.Log(pathPoints.Length);
    }

    private void Update()
    {
        Roam();
        SetStatusAnimationParameter();
    }

    private void Roam()
    {
        if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
        {
            if (!agent.hasPath || agent.velocity.sqrMagnitude == 0f)
            {
                if (!isTimerRunning)
                {
                    Debug.Log("Agent is stopped!");
                    timer.Start();
                    isTimerRunning = true;
                    agent.isStopped = true;
                    agent.velocity = Vector3.zero;
                }
            }
        }
        else if (agent.destination == pathFinishPoint.position)
        {
            if (agent.isStopped)
                Destroy(agent);
        }
    }

    private void SetNewDestination()
    {
        try
        {
            if (!agent.isOnNavMesh)
            {
                Debug.LogError("Agent is not on NavMesh!");
                return;
            }

            Debug.Log("Agent is on NavMesh");

            agent.ResetPath();
            Debug.Log("Path reset");

            agent.isStopped = false;
            Debug.Log("Agent is started");

            int randomNum = random.Next(0, pathPoints.Length - 1);
            Debug.Log("Random number generated: " + randomNum);

            var destination = agent.SetDestination(pathPoints[randomNum].position);
            if (!destination)
            {
                Debug.Log("Invalid destination");
                return;
            }

            Debug.Log("Destination set: " + pathPoints[randomNum].position);

            isTimerRunning = false;
            Debug.Log("Timer reset");

            Debug.LogWarning("New destination is : " + agent.destination + "   " + destination);
        }
        catch (Exception e)
        {
            Debug.LogError("Exception caught: " + e.Message);
        }
    }

    private void OnTimed(object source, ElapsedEventArgs e)
    {
        Debug.Log("Time has finished.");
        Invoke(nameof(SetNewDestination), 0.0f);
    }

    public float SetStatusAnimationParameter()
    {
        walkingAnimParameter = !agent.isStopped ? 1 : 0;
        return walkingAnimParameter;
    }
}

public class NPCAnimator : MonoBehaviour
{
    private const string walkingParameterName = "vertical";

    private Animator animator;

    [Title("Choose one of the controllers:")]
    [SerializeField]
    private NPCControllerRandomize npcr;

    // OR

    [SerializeField]
    private NPCController npc;

    private void Awake()
    {
        animator = GetComponent<Animator>();
    }

    private void Update()
    {
        SetFloatParameter();
    }

    private void SetFloatParameter()
    {
        if (npc != null)
            animator.SetFloat(walkingParameterName, npc.SetStatusAnimationParameter());
        else
            animator.SetFloat(walkingParameterName, npcr.SetStatusAnimationParameter());
    }
}

Sounds like you wrote a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

1 Like

Hey, thanks for answer. I’m already using Debug.Log(...); s (I have already added their outputs to the post) and I think the problem is not about my code, It’s about how unity works because the code is running properly but when Timer is Elapsed it doesn’t keep to work.

I think your problem is a little more basic and it is you :stuck_out_tongue: hear me out

Timer.AutoReset Property (System.Timers) | Microsoft Learn

The autoreset property says
" true if the Timer should raise the Elapsed event each time the interval elapses; false if it should raise the Elapsed event only once, after the first time the interval elapses. The default is true ."

your code says

Therefore it only fires once… which matches your behavior, which therefore its doing exactly what you wanted.

2 Likes

I would not use the system Timer. There’s just no benefit, plus now you have to learn how they work, and as @bugfinders points out, “it’s complicated.”

Make a float, set it, count it down.

Side benefit is you can reason about 100% of its functionality right in front of you.

More reading.

GunHeat (gunheat) spawning shooting rate of fire (and don’t use a Timer):

Cooldown timers, gun bullet intervals, shot spacing, rate of fire:

2 Likes

It is much better than a timer, thanks for your tips and responses Kurt Dekker and bugfinders, you are
real heroes guys <3

1 Like