Coroutine for animation's end and bool parameter

Hi, here is my problem. I am using coroutine to know when an animation ends.

IEnumerator OnAnimating(bool condition, Action callback)
{
    yield return new WaitForEndOfFrame();

    while (condition)
        yield return null;

    callback?.Invoke();
}

public bool IsAnimated(string animationName, int layerIndex = 0)
{
    return animator.GetCurrentAnimatorStateInfo(layerIndex).IsName(animationName);
}

Then I call the coroutine:

StartCoroutine(OnAnimating(IsAnimated("UseItem"), () => {
    humanBody.weaponPart.Equip(weapon);
    humanBody.shieldPart.Equip(shield);
    itemUsing = null;
}));

The problem is, when I do this, I don’t know why but it is not waiting for the animation’s end, instead, if I write this, it works:

StartCoroutine(OnAnimating(() => {
    humanBody.weaponPart.Equip(weapon);
    humanBody.shieldPart.Equip(shield);
    itemUsing = null;
}));

IEnumerator OnAnimating(Action callback)
{
    yield return new WaitForEndOfFrame();

    while (IsAnimated("UseItem"))
        yield return null;

    callback?.Invoke();
}

I don’t understand why, if I put the condition before in the parameters, it doesn’t work since the IsAnimated function should be call the same way as before.

When you do this:

StartCoroutine(OnAnimating(IsAnimated("UseItem"), () =>

You actually “call” your IsAnimated method once. This method returns a boolean of the current state. This state is passed to the coroutine and will not ever change.

What you can do is replace the boolean parameter with a delegate that returns a boolean. That way you can pass it any method that returns a boolean. This method would be called every frame until it returns false. So it could look like this:

    IEnumerator OnAnimating(Func<bool> condition, Action callback)
    {
        yield return new WaitForEndOfFrame();
    
        while (condition())
            yield return null;
    
        callback?.Invoke();
    }

Now you can start the coroutine like this:

    StartCoroutine(OnAnimating(()=>IsAnimated("UseItem"), () => {
        humanBody.weaponPart.Equip(weapon);
        humanBody.shieldPart.Equip(shield);
        itemUsing = null;
    }));

Though if everything is abstracted that strong, the name of your coroutine becomes misleading since the coroutine itself has nothing to do with animations at all. It just takes two delegates, the first is called every frame until it returns false at which point the second callback is called. So this is just a “WaitWhile” coroutine that could be used for anything. Maybe instead of passing a boolean of delegate you want to just pass it the animation name to check on?
Something like:

    IEnumerator OnAnimating(string animationName, Action callback)
    {
        yield return new WaitForEndOfFrame();
     
        while (IsAnimated(animationName))
            yield return null;
     
        callback?.Invoke();
    }

Of course using a predicate delegate is more flexible, but as I said, the coroutine has no connection to animations anymore and should get a more meaningful name.

1 Like

After some tries, it appears that if you put a a variable into a coroutine, this one will be copies and couldn’t be change from a other script. The only way is to use a Func as here:

StartCoroutine(OnAnimating(
    () => { return isUsingItem; },
    () => {
    humanBody.weaponPart.Equip(weapon);
    humanBody.shieldPart.Equip(shield);
    itemUsing = null;
}));

IEnumerator OnAnimating(Func<bool> condition, Action callback)
{
    yield return new WaitForEndOfFrame();

    while (condition.Invoke())
        yield return null;

    callback?.Invoke();
}

And it’s works, moreover thanks to this method, as it is a function, you can put some conditions inside if you want to.

1 Like

I didn’t see your answer before and I get to the same conclusion, thanks a lot for responding and your explanations.

If I don’t just put a string for the animation name, it’s because I use a bool which checks for two animation:

public bool isCastingSpell { get { return IsAnimated("CastSpell"); } }
public bool isUsingItem { get { return IsAnimated("UseItem") || isCastingSpell; } }

:slight_smile:
That’s simply because we posted pretty much at the same time (I think under a minute difference).

And yes, using a predicate is of course much more versatile, but as I said in that case the coroutine has no relation to anything related to animations. This coroutine could be used to wait for any predicate to execute any callback when the condition changes. So it could be called WaitWhile since the coroutine runs as long as the predicate is true.