Unity等待所有协程

huangapple go评论62阅读模式
英文:

Unity wait for all coroutines

问题

以下是您要翻译的代码部分:

private IEnumerator WaitForAllAnimations(List<ShiftedGem> shiftedGems)
{
    float duration = 3f;
    isDuringAnimation = true;

    List<Coroutine> animationCoroutines = new List<Coroutine>();

    for (int i = 0; i < shiftedGems.Count(); i++)
    {
        ShiftedGem shiftedGem = shiftedGems[i];
        Debug.Log("Add animation");
        ShiftAnimation shiftAnimation = shiftedGem.GameObject.AddComponent<ShiftAnimation>();
        yield return StartCoroutine(shiftAnimation.AnimateShift(shiftedGem.GameObject, shiftedGem.ShiftAmount, duration));

        animationCoroutines.Add(animationCoroutine);
    }

    foreach (Coroutine coroutine in animationCoroutines)
    {
        Debug.Log("Wait for coroutine");
        yield return coroutine;
    }

    Debug.Log("Animation end");
    isDuringAnimation = false;
}

请注意,这只是代码的翻译部分,不包括回答问题。如果您需要进一步的解释或帮助,请随时提问。

英文:

I'm going to wait for several coroutines in another coroutine. Example code

private IEnumerator WaitForAllAnimations(List<ShiftedGem> shiftedGems)
{
    float duration = 3f;
    isDuringAnimation = true;

    List<Coroutine> animationCoroutines = new List<Coroutine>();

    for (int i = 0; i < shiftedGems.Count(); i++)
    {
        ShiftedGem shiftedGem = shiftedGems[i];
        Debug.Log("Add animation");
        ShiftAnimation shiftAnimation = shiftedGem.GameObject.AddComponent<ShiftAnimation>();
        yield return StartCoroutine(shiftAnimation.AnimateShift(shiftedGem.GameObject, shiftedGem.ShiftAmount, duration));

        animationCoroutines.Add(animationCoroutine);
    }

    foreach (Coroutine coroutine in animationCoroutines)
    {
        Debug.Log("Wait for coroutine");
        yield return coroutine;
    }

    Debug.Log("Animation end");
    isDuringAnimation = false;
}

However, it does not work as expected - the Debug.Log("Animation end") is called before AnimateShift is finished.

I've tried a simpler example:

IEnumerator CoroutineA(List<ShiftedGem> shiftedGems)
{
    Debug.Log("Start Coroutine A");

    yield return StartCoroutine(CoroutineB());
    Debug.Log("End Coroutine A");
}

IEnumerator CoroutineB()
{
    Debug.Log("Start Coroutine B");
    yield return new WaitForSeconds(1f);
    Debug.Log("End Coroutine B");
}

Which works fine ("End Coroutine A" is logged after "End Coroutine B").

But if I move StartCoroutine(CoroutineB()) into a loop:

IEnumerator CoroutineA(List<ShiftedGem> shiftedGems)
{
    Debug.Log("Start Coroutine A");
    foreach (ShiftedGem shiftedGem in shiftedGems)
    {
        yield return StartCoroutine(CoroutineB());
    }
    Debug.Log("End Coroutine A");
}

IEnumerator CoroutineB()
{
    Debug.Log("Start Coroutine B");
    yield return new WaitForSeconds(10f);
    Debug.Log("End Coroutine B");
}

Then once again - Debug.Log("End Coroutine A") is called before Debug.Log("End Coroutine B").

How should I organize my code, to be able to wait for all animationCoroutines inside the WaitForAllAnimations Coroutine?

答案1

得分: 1

First of all, it seems strange a bit that you are adding an object to the list of coroutines that I don't see being assigned anywhere. Anyway, about the options:

首先,有一点让人感到奇怪,你向协程列表中添加了一个对象,但我没有看到它在任何地方被分配。不管怎样,关于选项:

You can use some kind of counter to count the current amount of coroutines run. Based on your last piece of code:

你可以使用某种计数器来计算当前运行的协程数量。根据你最后的代码片段:

int _someCounter = 0;

IEnumerator CoroutineA(List<ShiftedGem> shiftedGems)
{
    Debug.Log("Start Coroutine A");
    _someCounter = 0;
    foreach (ShiftedGem shiftedGem in shiftedGems)
    {
        StartCoroutine(CoroutineB());
    }
    while(_someCounter > 0)
    {
        yield return null;
    }
    Debug.Log("End Coroutine A");
}

IEnumerator CoroutineB()
{
    Debug.Log("Start Coroutine B");
    _someCounter++;
    yield return new WaitForSeconds(10f);
    _someCounter--;
    Debug.Log("End Coroutine B");
}

However, be careful that the code above will not be stable in case running the second time until the previous call is not finished yet.

不过,要小心上面的代码在第二次运行时可能不稳定,直到上一个调用完成为止。

I think the best option with minimum changes to your existing variant will be to add a flag to your ShiftAnimation component to check if animation is still in progress. Based on your initial example:

我认为对你现有变体进行最少更改的最佳选项是向你的 ShiftAnimation 组件添加一个标志,以检查动画是否仍在进行中。根据你的初始示例:

private IEnumerator WaitForAllAnimations(List<ShiftedGem> shiftedGems)
{
    float duration = 3f;
    isDuringAnimation = true;

    List<ShiftAnimations> animations = new List<ShiftAnimations>();

    for (int i = 0; i < shiftedGems.Count(); i++)
    {
        ShiftedGem shiftedGem = shiftedGems[i];
        Debug.Log("Add animation");
        ShiftAnimation shiftAnimation = shiftedGem.GameObject.AddComponent<ShiftAnimation>();
        animations.Add(shiftAnimation);
        shiftAnimation.AnimateShift(shiftedGem.GameObject, shiftedGem.ShiftAmount, duration);

    }

    bool isAnyAnimationActive;
    do
    {
        isAnyAnimationActive = false;
        foreach (var animationIt in animations)
        {
            if(animationIt.IsAnimationActive == true)
            {
                isAnyAnimationActive = true;
                break;
            }
        }        

        yield return null;
    }
    while(isAnyAnimationActive == true);

    Debug.Log("Animation end");
    isDuringAnimation = false;
}

And in your ShiftAnimation component something like this:

...
bool _isAnimationActive = false;

public IsAnimationActive => _isAnimationActive;

public void AnimateShift(your parameters here)
{
    StartCoroutine(CoAnimateShift(your parameters here));
}

IEnumerator CoAnimateShift(your parameters here)
{
    _isAnimationActive = true;

    // your code here

    _isAnimationActive = false;
}
...

并且在你的 ShiftAnimation 组件中,类似于以下内容:

英文:

First of all, it seems strange a bit that you are adding an object to the list of coroutines that I don't see being assigned anywhere. Anyway, about the options:

You can use some kind of counter to count the current amount of coroutines run. Based on your last piece of code:

int _someCounter = 0;

IEnumerator CoroutineA(List&lt;ShiftedGem&gt; shiftedGems)
{
    Debug.Log(&quot;Start Coroutine A&quot;);
    _someCounter = 0;
    foreach (ShiftedGem shiftedGem in shiftedGems)
    {
        StartCoroutine(CoroutineB());
    }
    while(_someCounter &gt; 0)
    {
        yield return null;
    }
    Debug.Log(&quot;End Coroutine A&quot;);
}

IEnumerator CoroutineB()
{
    Debug.Log(&quot;Start Coroutine B&quot;);
    _someCounter++;
    yield return new WaitForSeconds(10f);
    _someCounter--;
    Debug.Log(&quot;End Coroutine B&quot;);
}

However, be careful that the code above will not be stable in case running the second time until the previous call is not finished yet.


I think the best option with minimum changes to your existing variant will be to add a flag to your ShiftAnimation component to check if animation is still in progress. Based on your initial example:

private IEnumerator WaitForAllAnimations(List&lt;ShiftedGem&gt; shiftedGems)
{
    float duration = 3f;
    isDuringAnimation = true;

    List&lt;ShiftAnimations&gt; animations = new List&lt;ShiftAnimations&gt;();

    for (int i = 0; i &lt; shiftedGems.Count(); i++)
    {
        ShiftedGem shiftedGem = shiftedGems[i];
        Debug.Log(&quot;Add animation&quot;);
        ShiftAnimation shiftAnimation = shiftedGem.GameObject.AddComponent&lt;ShiftAnimation&gt;();
        animations.Add(shiftAnimation);
        shiftAnimation.AnimateShift(shiftedGem.GameObject, shiftedGem.ShiftAmount, duration);

    }
    
    bool isAnyAnimationActive;
    do
    {
        isAnyAnimationActive = false;
        foreach (var animationIt in animations)
        {
            if(animationIt.IsAnimationActive == true)
            {
                isAnyAnimationActive = true;
                break;
            }
        }        
        
        yield return null;
    }
    while(isAnyAnimationActive == true);

    Debug.Log(&quot;Animation end&quot;);
    isDuringAnimation = false;
}

And in your ShiftAnimation component something like this:

...
bool _isAnimationActive = false;

public IsAnimationActive =&gt; _isAnimationActive;

public void AnimateShift(your parameters here)
{
    StartCoroutine(CoAnimateShift(your parameters here));
}

IEnumerator CoAnimateShift(your parameters here)
{
    _isAnimationActive = true;

    // your code here

    _isAnimationActive = false;
}
...

答案2

得分: 1

如果你正在寻找一种更好的方式来处理Unity中的异步任务,我建议查看UniTask插件。这个插件提供了一个易于使用的替代传统的C# async/await和Unity的协程,后者可能难以处理。

使用UniTask,你可以使用WhenAll()方法来等待多个任务完成。这种方法只需要将你现有的协程重构为异步方法,可以简化你的代码并提高可读性。

英文:

If you're looking for a better way to handle asynchronous tasks in Unity, I recommend checking out the UniTask plugin. This plugin offers an easy-to-use alternative to traditional C# async/await, as well as Unity's coroutines which can be difficult to work with.

With UniTask, you can use the WhenAll() method to wait for multiple tasks to complete. This approach only requires that you refactor your existing coroutines as async methods, which can simplify your code and improve its readability.

huangapple
  • 本文由 发表于 2023年4月11日 15:50:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/75983580.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定