Corountine not behaving correctly?

Hello everyone,

I have been using Unity (no coding though) for a few years but recently started learning coding in C# to create my own games. I lurked around here for quite sometime though. Before anything though, I am really sorry if this is wrong section but it looked suitable to me.

While working on the logic for a boss in my game I got stuck with coroutines; they aren’t functioning properly. Here is the code:

using UnityEngine;
using System.Collections;

public class BossOrcLogic : MonoBehaviour
{

    public GameObject[] Waypoints;
    public float Speed;
    public float wait;
    public Object Projectile;

    private bool loop = true;
    //private bool patrol = true;
    private int currentIndex = 0;
    private Vector3 currentWaypoint;

    /* The code handles the movement process of the Orc where it starts with the orc going the right. After going there, the orc grabs
    one of the skulls and then throw it at the player. He then moves to the left and repeats the same grabbing and throwing.
    The orc throws multiple skulls at 50% and summons zombies at 75% health.*/
    void Start()
    {
        currentWaypoint = Waypoints[currentIndex].transform.position;
    }

    void Update()
    {
        // Get the enemy's HP every frame to test the progress of the player against the boss.
        int hp = gameObject.GetComponent<EnemyCollisions>().hp;

        if (hp > hp / 2)
        {
            NormalPatrol();
        }

        // Test if the player has reduced the boss to 50%? Then make boss aggressive.
        else if (hp <= hp / 2 && hp > (hp / 4 * 3))
        {
            AggressivePatrol();
        }

        // Test if the player has reduced the boss to 75%? Then start summoning sequences and after that keep making him aggressive.
        else if (hp <= (hp / 4 * 3) && hp > 0)
        {
            SummonSequence();
        }

        // Test if the player has reduced the boss to 0%? Then kill the boss
        else if (hp <= 0)
        {
            gameObject.GetComponent<EnemyCollisions>().DestroyEnemy(false);
        }
    }


    void NormalPatrol()
    {
        if (transform.position != currentWaypoint)
        {
            gameObject.GetComponent<Animator>().SetFloat("x", 1);
            if (currentIndex == 0)
            {
                //gameObject.GetComponent<Animator>().SetFloat("x", 1);
                gameObject.GetComponent<Animator>().SetBool("right", false);
            }
            else
            {

                gameObject.GetComponent<Animator>().SetBool("right", true);
            }
            transform.position = Vector3.MoveTowards(transform.position, currentWaypoint, Speed * Time.deltaTime);
        }
        else
        {
            //StartCoroutine(TakeSkulls());
            //StartCoroutine(ThrowSkulls());
            StartCoroutine(WaitTime());
            gameObject.GetComponent<Animator>().SetFloat("x", 0);
            StartCoroutine(WaitTime());
            gameObject.GetComponent<Animator>().SetFloat("x", -1);
            Invoke("GetNextWaypoint", wait);
            loop = true;
        }
    }

    void GetNextWaypoint()
    {
        if (currentIndex >= 1)
        {

            if (loop)
            {
                currentIndex = 0;
                currentWaypoint = Waypoints[currentIndex].transform.position;
                loop = false;
            }
        }

        if (currentIndex < 1)
        {
            if (loop)
            {
                currentIndex = 1;
                currentWaypoint = Waypoints[currentIndex].transform.position;
                loop = false;
            }
        }

    }

    //IEnumerator TakeSkulls()
    //{
    //    gameObject.GetComponent<Animator>().SetFloat("x", 0);
    //    yield return new WaitForSeconds(0.3f);
    //}
    IEnumerator WaitTime()
    {
        yield return new WaitForSeconds(0.3f);
    }
    //IEnumerator ThrowSkulls()
    //{
    //    gameObject.GetComponent<Animator>().SetFloat("x", -1);
    //    yield return new WaitForSeconds(0.3f);
    //}


    void AggressivePatrol()
    {

    }

    void SummonSequence()
    {

    }

}

As you can see in this section:

else
        {
            //StartCoroutine(TakeSkulls());
            //StartCoroutine(ThrowSkulls());
            StartCoroutine(WaitTime());
            gameObject.GetComponent<Animator>().SetFloat("x", 0);
            StartCoroutine(WaitTime());
            gameObject.GetComponent<Animator>().SetFloat("x", -1);
            Invoke("GetNextWaypoint", wait);
            loop = true;
        }

The patrolling part of the above script works just fine, my problem is with the above section (side note, I am using a Blend tree for this).

The commented parts indicate the previous method I used; I created Coroutines to start the “TakeSkull” animation ending with a wait time and then starting the “ThrowSkull” animation ending another wait time and then using the Invoke method. It didn’t work and when playing in Unity it ignored it completely and behaved all weird with the animation doing weird stuff.

So I changed it to Invoke(“TakeSkulls”, 0.3f); instead and changed the IEnumerator to a normal function. Didn’t help much.
After too much weird stuff, I did what you see now above. Now the below part:

gameObject.GetComponent<Animator>().SetFloat("x", 0);

is being ignored completely and the below part:

gameObject.GetComponent<Animator>().SetFloat("x", -1);

runs twice.

The animation looks flawless but it isn’t what I want, I want it to run the x = 0 animation and then the x = -1 animation.

I am seriously at a loss what is going on and have been at this for 2.5 days now. Any help would be really appreciated and thank you very much in advance for your time and assistance.

That’s not how coroutines work, the Update call won’t bother and simply continue once the Couroutine returns for the first time (either returning completely or with a yield instruction). It won’t stop the Update function for 0.3 seconds, but the coroutine.
Afterwards, if it hasn’t finished already, it’ll continue running. In your case, it’ll “pause” execution for roughly 0.3 seconds and once 0.3 seconds have elapsed, it’ll continue to execute what comes next within the coroutine. But there’s nothing in your code to execute so it’ll simply wait and then return.

So you have to put the code you want to be delayed into the coroutine, after yielding whatever you want to yield there.

Also, you should have an indicator in order to determine whether the coroutine has finished or not, otherwise you’ll start additional couroutines. In the worst case in your code: every frame a new coroutine.

Start this coroutine once (do not start it every frame):

IEnumerator MyCoroutine()
{
    Debug.Log("Coroutine has started. This will be executed immediately.");
    yield return new WaitForSeconds(1);
    Debug.Log("This will be executed one second later");
}

You’ll get the idea.

1 Like

Hey Soddoha,

Thanks for your reply. That is exactly the setup I had the very first time I made that code (you see the 5th version and try of that code).

 //IEnumerator TakeSkulls()
    //{
    //    gameObject.GetComponent<Animator>().SetFloat("x", 0);
    //    yield return new WaitForSeconds(0.3f);
    //}

This is the 3rd version I used (commented now) and it had the same setup you mentioned above and it didn’t work.

I tried it again now as below:

 IEnumerator TakeSkulls()
    {
       gameObject.GetComponent<Animator>().SetFloat("x", 0);
       yield return new WaitForSeconds(0.3f);
       gameObject.GetComponent<Animator>().SetFloat("x", -1);
    }

I know that the x=0 track is 0.33 seconds long and the x = -1 is 0.53 seconds long. The “wait” variable in the Invoke function is 1 second long. Yet, it still messy somehow. What I see is the x=0 plays perfectly. Then the x=-1 plays but seems like it is playing quicker than expected.

I actually tried triggering the events using Animation Events from the Animation window but it never triggers.

Would you recommend perhaps another way to setup the animation that is easier and less hassle free that I could use? Perhaps a way that resembles the old way where I can just Play animation 1 wait 0.33f seconds, stop and play animation 2 wait for 0.5 seconds then so on?

I can’t really help with all the animation stuff.

Have you made sure to only run the Coroutine once while it is running to prevent unexpected behaviour?
What exactly do you mean with “it runs quicker”? Does it run faster? Then it’s probably not related to the code. If it runs earlier than expected, you may either play around with the waiting time or have to dig a little more into the animation system. There’s also a section for animation specific stuff.

Perhaps there are callbacks or triggers or other things that let you chain the animations, I’m really the wrong person to ask about animation. :smile: Sorry for that.

Coroutines can quickly turn into a hell when used to writing AI or any complex logic. It produces code that is difficult to understand and therefore hard to debug.

An excellent alternative is to use Behaviour Tree. It simplifies your code and makes your high-level logic easier to understand and to define.

Have a look at Panda BT:
http://www.pandabehaviour.com

@Suddoha

Thank you very much. Seriously you confirmed my understanding of coroutines. However, perhaps someone else can shed some light on how to handle this kind of situation as fixing it the way coroutines are supposed to be used doesn’t help much.

@ericbegue

I haven’t heard of Pandabehavior before and I am guessing you’re the creator from the signature. It looks interesting, would you describe it like Playmaker for example or uScript?
I am willing to give it a try but would you say this is easy to get into for a beginner at coding?

@ Panda BT is a rather new package. I am indeed the author, I released a free version in mid February.

Playmaker is a visual Finite State Machine editor and uScript is a generic visual scripting editor. Panda BT is not a visual editor, it’s text-based, so it’s more programmer friendly. It allows to create behaviour trees as scripts. Yet, the execution of the BTs can be visualized as they are running providing insight about why and what the AI is currently doing (This is an online demo showing what a running BT looks like).

About comparing these tools to Panda BT. I’ve used Playmaker, however I haven’t use uScript, but I optimistically assume it allows you do do anything you can do in C# but in a visual way. So, I assume it has the same functionality as writing plain C# scripts.

Behaviour Tree is a technique coming from AI that exists roughly since 10 years, it has been used in games such as Halo. It’s also a technique used in Robotics. Compared to FSM, it seems to be a less popular technique, but BT is much more flexible. FSM is simpler to understand and to master, however it is really resistant to modifications and lacks re-usability and maintainability. Whereas BT requires a bit more effort to understand the fundamentals, but it’s really worth it. BT it’s far more flexible and allows to easily describe complex logics.

I believe Panda BT would be easier for a beginner for writing AI code than using exclusively C# code. It requires knowledge about Behaviour Tree, but behaviours described as BTs are more intuitive than plain C# only. For example, let’s say you want to write the AI of a ghost in Pacman, then the BT script would be:

// Top  behaviours
// do the first successful action
tree "Root"
    fallback
        tree "ReturnToBase"
        tree "Escape"
        tree "ChasePacman"
        tree "WanderTheMaze"

// Move to the base if has been eaten by pacman.
tree "ReturnToBase"
    while
        sequence
            IsEaten
            HasNotReachedBase
        sequence
            MoveToBase
            Recover

// Avoid pacman if he has eaten a power pellet.
tree "Escape"
    while IsPacmanPoweredUp
        AvoidPacman

// Chase pacman while he is visible and has not eaten a power pellet.
tree "ChasePacman"
    while
        sequence
            IsPacmanVisible
            not IsPacmanPoweredUp
        MoveToPacmanLocation

// Just wander the maze randomly.
tree "WanderTheMaze"
    while not IsPacmanVisible
        MoveToRandomLocation

It might looks simple, but this is a the complete and functional Behaviour Tree describing such AI. The tasks (such as MoveToPacmanLocation, IsPacmanPoweredUp, MoveToBase, IsEaten, …) are implemented as simple C# methods. It does not require an in-deep knowledge about OOP programming to implement theses tasks, just to know how to write a method, which is accessible to a beginner in coding. And you’ve might guess it should be trivial to implement them.

I hope this gives you some idea about the advantages of using Panda BT. Let me know if you have further question about this tool.

@Vallar The following post gives further arguments why BT are suitable than plain C# only:

I found this thread interesting because I’m an electrical engineer who has worked with industrial controls design for many years.

FSM works really well for situations in which the problem domain can be characterized by a modest number of inputs, states, and outputs that need to inter-relate predictably. If you are trying to solve a large and complex problem with FSM, the best approach is to break it down into isolated sub-problems with their own FSM. In your example, managing the patrol logic with an FSM is straightforward. I agree with @ericbegue that FSM doesn’t lend itself well to problems with huge numbers of possible states, unless you can segment the problem. How large is too large for an FSM depends on how good your visualization and test tooling are. Some of the Unity FSM assets are pretty good. In the 1980s I was using FSM to design microcode logic for bit-slice computers, and my “tooling” was mostly graph paper and some fairly primitive simulators. The world has changed a bit. :slight_smile:

I’m also working on a project with complex behavioral logic, and I’m considering several behavior tree alternatives. I hadn’t heard of Panda BT but will give it a look. I’m also considering Behavior Designer from Opsive, which has prebuilt integrations with several other assets I am already using. There is also Behaviour Machine from Anderson Cardoso.

It is definite that I will buy on of the behavior tree assets for my AI behavior design. I am still considering whether I also want Playmaker (or something like it) for low-level FSM such as controlling doors, elevators, traps, and visual effects. It’s possible to do those sorts of things with behavior trees or (of course) with plain old C# scripts. The tradeoff is how many tools I want to learn versus how many lines of C# I want to maintain. :slight_smile: I have done AI programming in another game engine, and I can tell you it’s pretty hard to scale up complex AI when you’re doing it in raw script code without some sort of BT or FSM tooling. It’s not impossible (I did it), but it’s hard.

I don’t have any business relationship with asset providers, and I have no reason to recommend one solution over another for you. I just wanted to amplify @ericbegue 's point that FSMs and BTs are both excellent tools but in my opinion best used for different things.

1 Like

@ericbegue and @syscrusher

Thank you very much for your explanations and examples. I now have a better understanding of what is a BT and what is a FSM.

PandaBT sounds interesting specially now that I am learning to code my games rather do the whole thing using FSMs and battling with Playmaker or uScript quirks (not that I am saying they are bad tools, on the contrary). I did a couple of AIs (basic stuff though) 3 years ago with Playmaker and it was easy.

However the way you both describe it, I think it is much better to use BT in general. I am not using complex AI at the moment (as you can see from the code). But I believe the way PandaBT works (and it has been described), I think it makes things easy. Write a function, handle the timing and what not from Panda itself. Sounds cool to me.

Just a quick question with PandaBT though, is my understanding correct when I say that I code up functions and then let Panda handle it?
In the example you mentioned with pacman; all these trees I am going to code in scripts (for example) and load them into Panda component, correct?

On another note; I fixed my problem finally. Here is the solution (just in case someone else fell in the same trap):

Since the “Take Skull” animation and the “Throw Skull” animation are happening after each other and not separated I made them into one animation clip rather than two. Then in the Animator I had a transition with Exit time left to the same time as the clip flowing into the walk cycle. That way when I trigger “TakeSkull” both would play and then flow when done into the walking cycle. Now I only have to mess with one number only and that is the “wait” in the Invokation part.

@syscrusher It’s great to have feedback from someone like you who is experienced with FSMs and knows their boundaries and not only in the domain of video games.

Thank you for giving a try to Panda BT. I hope you will like it.

Behaviour Tree is an excellent alternative to FSM or plain C# script, even for very simple and obvious logics. I’ve discovered Behaviour Tree since less than two years, and I know that I won’t touch an FSM anymore (if I got the choice). Once you’ve got a grip on BT, it becomes so natural to describe a process in term of BT, that you won’t even bother trying to make your life harder with other more classical techniques. Sometimes it seems you are almost doing a literal translation from a plain English descriptions to a BT scripts.

It’s great that you have a good grasp about the potential of this tool. The whole point of Panda BT is to make coding easier and more structured. Also it offers a way to describe a long running process in a time-independent manner, and that description seems pretty natural and intuitive.

Yes, that’s correct. The workflow to use Panda BT is first you implement some tasks as C# functions in any MonoBehaviour, then you use these tasks as building blocks from BT scripts.

That’s again correct. A BT script is simple plain text file that exists in your Asset folder as a TextAsset. The Panda Behaviour component is attached to a GameObject you wish to drive with a Behaviour Tree. The Panda Behaviour is a container that receives the BT scripts, its through this component that the BT scripts are executed (and visualized directly in the Inspector in Play Mode).

LOL! I guess it depends on your individual perspective. To me, FSM is intuitive because I’ve been doing it so long. BT is something I am still learning. :slight_smile:

I also noticed that Node Canvas is an Asset Store alternative that claims to do BT and FSM in one tool. As before, my disclaimer is that I haven’t used Node Canvas personally, and I’m only mentioning its existence, not endorsing it. :slight_smile:

Thanks for the sample. If I had to characterize Panda BT from this sample, I would refer to it as a declarative rules language, but this is one of those areas where the terminology gets a little grey and fuzzy.

@syscrusher I would also classify Panda BT as a declarative language rather than a procedural one. Behaviour Trees are goal oriented, that is you describe what do to instead of how to do it. I believe that’s why the description of the behaviour in plain English seems to be very close to the BT script implementing the behaviour. Which is good from a designer perspective since you would have few mental steps from your original idea to its implementation.