2
\$\begingroup\$

So I have a few AIs walking between waypoints. What I want to accomplish here is to have my AI move to the waypoint, when he reaches it, he must stop, wait for a few seconds (Not walk just stand still), then move to the new waypoint. Lets pretend he was observing while at this point.

Issue right now - He moves to the waypoint with a twitchy behavior where he would usually avoid objects, sometimes gets stuck while moving to objects or its like the AI cant decide what to do, move or stand still. Without the Waitforseconds method the AI avoids objects perfectly.

Please note I want to use Coroutines for this even if you wouldn't use / recommend it for this. If you have a suggestion for using Unity's Random to decide when he stands still after reaching a point or not, that would be a bonus for me.

We define all fields and call the FSM() Coroutine with a default patrol state in the Start Method, from Here, This is my Code:

Update - Implemented A Boolean IsMoving variable - This works to some extent. The AI now moves perfectly to the waypoint. The Twitching however continues once the AI reaches the waypoint vicinity until he leaves the waypoint radius - The movement then continues to a normal state.

IEnumerator FSM()
{
        while (alive) 
        {
            switch (state) 
            {
            case State.PATROL:
                StartCoroutine(Patrol());
                break;
            case State.CHASE:
                Chase ();
                break;
            default:
                break;
            }
            yield return null;
        }
    }

    /// <summary>
    /// Patrol will move to a waypoint if too far, otherwise move to a next waypoint if one is reached.
    /// </summary>
    IEnumerator Patrol()
    {

            //Sets a common speed for the agent to move in.
            agent.speed = patrolSpeed;

            //Now we want to check the position the character is at vs the waypoint location - If i am too far, move closer
            //                        My Position           Waypoint Indexes - We Set Multiple
            if (Vector3.Distance (this.transform.position, waypoints [wayPointIndex].transform.position) >= 2) 
            {
                //Telling the navmesh agent where to go.
                agent.SetDestination (waypoints [wayPointIndex].transform.position);

                //                                  false, false - Specifies if we are jumping or crouching
                character.Move (agent.desiredVelocity, false, false);


            }
            //Now we want to check the position the character is at vs the waypoint location - If i am at a waypoint, move to a new index
            else if (Vector3.Distance (this.transform.position, waypoints [wayPointIndex].transform.position) <= 2) 
            {
                character.Move (Vector3.zero, false, false);
                yield return new WaitForSecondsRealtime (6.0f);

                wayPointIndex += 1; //Move towards a new waypoint in the waypoint indexes flags

                //Move to a random waypoint
                wayPointIndex = UnityEngine.Random.Range (0, waypoints.Length);

            } 
            else 
            {
                //Doesn't move, plays the idle movement - We know this point wont be reached but if so...
                character.Move (Vector3.zero, false, false);
                yield return null;
            }
        }
\$\endgroup\$
3
  • \$\begingroup\$ From a cursory reading, it looks like you could be starting multiple Patrol coroutines in parallel, since each one can run for 6 seconds, but the FSM keeps ticking and firing off new actions every frame, even if a patrol is in progress. Did you intend to either wait for the patrol method to finish its work before the FSM state switches again, or for the FSM to update/cancel the current patrol instead of stacking up multiple? \$\endgroup\$
    – DMGregory
    Commented Sep 15, 2016 at 20:08
  • \$\begingroup\$ Hi DMGregory - Yes, i want the patrol to finish IE reach the waypoint, and only then wait for 6 seconds (To observe or wait), then continue patrolling until a new point is reached and repeat the wait. \$\endgroup\$ Commented Sep 15, 2016 at 20:16
  • \$\begingroup\$ Minor issue - you're also checking if the distance is <= 2 in the first if block, and then in the second, you're checking if the distance is >= 2. This means if the distance is 2 it will always choose the first block. Not a huge deal but just FYI. \$\endgroup\$
    – Savlon
    Commented May 4, 2017 at 21:51

2 Answers 2

1
\$\begingroup\$

From what I seen in the code, there is a little problem: You are calling the patrol coroutine every frame, without considering if the previous call was completed. This have several side effecs/bugs/problems:

  • Each cycle you are telling the character to calculate his path to its objective

  • If the agent reaches the waypoint, the patrol coroutine waits for 6 seconds... but the FSM coroutine is still running, so it will make another decision.. which will probably be starting another Patrol coroutine and wait for 6 seconds, again.

  • Once 6 seconds are passed, the Patrol coroutine will continue executing, which is good... but in the very next cycle, the other patrol coroutine that was called 6 seconds ago will finish waiting for 6 seconds.. and will decide another waypoint, again, and again. It will be making the same random decision, with a different result, once for each cycle that passed since the Patrol coroutine execution first waited for 6 seconds.

I guess this would make the agent move like twitching and erratically.

You could add a bool to your class that would indicate if the FSM should be executed next cycle.

IEnumerator FSM()
{
    while (alive) 
    {
        if(runFSM){
            switch (state) 
            {
            case State.PATROL:
                runFSM = false;
                StartCoroutine(Patrol());
                break;
            case State.CHASE:
                Chase ();
                break;
            default:
                break;
            }
        }
        yield return null;
    }
}

/// <summary>
/// Patrol will move to a waypoint if too far, otherwise move to a next waypoint if one is reached.
/// </summary>
IEnumerator Patrol()
{

    //Sets a common speed for the agent to move in.
    agent.speed = patrolSpeed;

    //Now we want to check the position the character is at vs the waypoint location - If i am too far, move closer
    //                        My Position           Waypoint Indexes - We Set Multiple
    if (Vector3.Distance (this.transform.position, waypoints [wayPointIndex].transform.position) >= 2) 
    {
        //Telling the navmesh agent where to go.
        agent.SetDestination (waypoints [wayPointIndex].transform.position);

        //                                  false, false - Specifies if we are jumping or crouching
        character.Move (agent.desiredVelocity, false, false);


    }
    //Now we want to check the position the character is at vs the waypoint location - If i am at a waypoint, move to a new index
    else if (Vector3.Distance (this.transform.position, waypoints [wayPointIndex].transform.position) <= 2) 
    {
        //THIS IS WHERE I NEED HELP - I try to stop the AI here to wait for a few seconds before moving to the new waypoint.
        character.Move (Vector3.zero, false, false);
        yield return new WaitForSecondsRealtime (6.0f);

        wayPointIndex += 1; //Move towards a new waypoint in the waypoint indexes flags

        //Move to a random waypoint
        wayPointIndex = UnityEngine.Random.Range (0, waypoints.Length);

    } 
    else 
    {
        //Doesnt move, plays the idle movement - We know this point wont be reached but if so...
        character.Move (Vector3.zero, false, false);
        yield return null;
    }
    runFSM = true;
}

Make sure to set runFSM=true in the Awake() callback

\$\endgroup\$
11
  • \$\begingroup\$ Hi. Thanks, i noticed Gregory answered the same. Might you have a solution as to "Finishing the current patrol state by walking to the waypoint 1st" before firing off the FSM coroutine to move again. \$\endgroup\$ Commented Sep 15, 2016 at 20:20
  • \$\begingroup\$ You could add a bool to your class, that indicates if the FSM should be executed in the next cycle. Set it to false when calling the patrol coroutine, and set it to true when the patrol coroutine is over. I'll update the answer. \$\endgroup\$
    – Leo
    Commented Sep 15, 2016 at 20:29
  • \$\begingroup\$ An update : I implemented the Bool for an IsMoving flag. This works to some extent. When the AI reaches the Waypoint, the movement is still stop, go, stop go behaviour. Once the AI moves a certain range away from the Waypoint, the AI moves as he should. Therefore we have contained the slugish behaviour to the waypoint, almost like we want it but not there yet. \$\endgroup\$ Commented Sep 15, 2016 at 20:40
  • \$\begingroup\$ Only saw your update now, let me try this 1st \$\endgroup\$ Commented Sep 15, 2016 at 20:40
  • \$\begingroup\$ Hi Leo. Tried your solution. It has the same effect as my previous comment. The AI moves to the waypoint normally as he should, once in a close vicinity of the waypoint the movement becomes twitchy again (The AI moves to the waypoint - Doenst walk, just gets slided onto the waypoint position), then the movement is twitchy again till he gets further away from the waypoint, then moves perfectly again. It seems the waypoint radius is an issue of some sort. \$\endgroup\$ Commented Sep 15, 2016 at 20:49
0
\$\begingroup\$

The proper way to do a nested coroutine is

IEnumerator MainCoroutine () 
{
    yield return StartCoroutine(NestedCoroutine01());
    yield return StartCoroutine(NestedCoroutine02());
}

With this set-up the NestedCoroutine02 will wait until NestedCoroutine01 is finished.

With that being said, the FSM function should look like below to make sense:

IEnumerator FSM()
{
    while (alive) 
    {
        switch (state) 
        {
        case State.PATROL:
            yield return StartCoroutine(Patrol());
            break;
        case State.CHASE:
            yield return StartCoroutine(Chase());
            break;
        default:
            // if state is not PATROL or CHASE, we do nothing but wait 
            // for a small amount of time before continuing, 
            // in most cases FSM for AI doesnt need to be updated every frame

            yield return new WaitForSeconds(0.5f);
            break;
        }
    }
}

The whole process is held until the selected action is done, the rest of code can remain unchanged.

Besides, I noticed in your code that yield return null is used at the end of function or block that doesn't make much sense.

In the FSM function, if the reason is that in coroutine at least one yield instruction must be present than you can safely place a yield break at the end of function, ie:

IEnumerator FSM()
{
    while (alive) 
    {
        switch (state) 
        {
        case State.PATROL:
            yield return StartCoroutine(Patrol());
            break;
        case State.CHASE:
            yield return StartCoroutine(Chase());
            break;
        default:
            // if state is not PATROL or CHASE, we do nothing but wait 
            // for a small amount of time before continuing, 
            // in most cases FSM for AI doesnt need to be updated every frame

            yield return new WaitForSeconds(0.5f);
            break;
        }
    }
    yield break;
}

Of couse in my code there are already other yield instructions in the function so it actually doesnt need yield break at the end, I added for the sake of demonstration. By placing yield break at the end of function, your code's functionallity stays unchanged and problem's gone.

\$\endgroup\$
2
  • \$\begingroup\$ I will try this and let you know how this goes. \$\endgroup\$ Commented Sep 22, 2016 at 7:29
  • \$\begingroup\$ sure, just feel free to ask if you encounter further problem \$\endgroup\$
    – tc07
    Commented Sep 22, 2016 at 16:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .