【问题标题】:How to execute multiple times a couroutine without stopping it?如何在不停止协程的情况下多次执行协程?
【发布时间】:2019-06-06 19:54:12
【问题描述】:

我有一组灯光游戏对象,我试图随着时间的推移增加和减少点光源的范围大小,问题是灯光有时不会随着时间的推移而减少,它们只是立即进入范围 0。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour
{
    public GameObject[] stars;

    private void Start()
    {
        StartCoroutine(ChooseStar());
    }

    public void PlayGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }

    public void QuitGame()
    {
        Application.Quit();
    }


    IEnumerator IncreaseRadius(GameObject star, float duration)
    {
        Debug.Log("Increasing: "+star.name + " radius: " + star.GetComponent<Light>().range);
        float counter = 0;

        while (counter < duration)
        {
            counter += Time.deltaTime;
            star.GetComponent<Light>().range = counter;
            yield return null;
        }
        StartCoroutine(DecreaseRadius(star));
    }

    IEnumerator DecreaseRadius(GameObject star)
    {
        Debug.Log("Decreasing: "+star.name+" radius: "+ star.GetComponent<Light>().range);
        float counter = star.GetComponent<Light>().range;

        while (star.GetComponent<Light>().range >= 0f)
        {
            counter -= Time.deltaTime;
            star.GetComponent<Light>().range = counter;
            yield return null;
        }
        star.GetComponent<Light>().range = 0f;

    }

    IEnumerator ChooseStar()
    {
        float duration = Random.Range(3, 8);
        float waitTime = 2f;

        GameObject choosenStar = stars[Random.Range(0, stars.Length)];

        if (choosenStar.GetComponent<Light>().range <= 0f)
        {
            StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
        }
        else
        {
            waitTime = 0f;
        }

        yield return new WaitForSeconds(waitTime);
        StartCoroutine(ChooseStar());

    }
}

预期的结果应该是这个序列:

1 - 从游戏对象数组中选择随机星 2 - 检查星星是否正在增加范围,如果是,则重新开始搜索新的,如果没有开始增加。 3 - 灯光开始增加直到持续时间,然后调用减少功能 4 - 星开始减少,当功能结束时,将范围重置为 0

【问题讨论】:

    标签: c# unity3d ienumerator


    【解决方案1】:

    一般来说回答这个问题:你可以简单地放一个

    while (true)
    {
       ... 
    
       yield return ...
    }
    

    围绕您的代码。只要你在其中的某个地方yield 对协程完全有效。


    我的猜测是你会得到并发的协程,因为你没有等到IncreaseRadius 完成后才选择下一个随机星......这可能和以前一样。

    if (chosenStar.range <= 0f)
    {
        StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
    }
    else
    {
        waitTime = 0f;
    }
    

    虽然你之前已经选择了另一颗星,但你又在这里做了一个 Random.Range,这是故意的吗?


    首先一般不要一遍又一遍地使用GetComponent&lt;Light&gt;,而只是简单地制作

    public Light[] stars;
    

    以与以前相同的方式引用对象,但现在您直接处理 Light 引用而不是 GameObject

    那你就知道了

    float duration = Random.Range(3, 8);
    

    实际上返回37 之间的随机完整int 值。如果您希望 float 值也在 38 之间,那么也包括例如3.253453f 那么你应该使用

    var duration = Random.Range(3.0f, 8.0f);
    

    解决方案 1 - 一次只有一颗星

    作为简单的替代方案,您始终可以一次只为一颗星星设置动画。您可以通过yield return 另一个IEnumerator 来实现这一点。这使得另一个 IEnumerator 执行并同时等待它完成。类似的东西

    public Light[] stars;
    
    private void Start()
    {
        StartCoroutine(ChooseStar());
    }
    
    private IEnumerator IncreaseRadius(Light star, float duration)
    {
        Debug.Log("Increasing: " + star.name + " radius: " + star.range);
        float counter = 0;
    
        while (counter < duration)
        {
            counter += Time.deltaTime;
            star.range = counter;
            yield return null;
        }
    
        // again do the decreasing and at the same time wait for it to finish
        yield return DecreaseRadius(star);
    }
    
    private static IEnumerator DecreaseRadius(Light star)
    {
        Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
        var counter = star.range;
    
        while (star.range >= 0f)
        {
            counter -= Time.deltaTime;
            star.range = counter;
            yield return null;
        }
        star.range = 0f;
    }
    
    IEnumerator ChooseStar()
    {
        // Looks scary but is totally fine in Coroutines as long as you yield somewhere
        // instead of starting a new Coroutine simple continue the one you already have
        while (true)
        {
            var duration = Random.Range(3.0f, 8.0f);
    
            var choosenStar = stars[Random.Range(0, stars.Length)];
    
            // This starts the Increase routine on that star
            // and at the same time waits for it to finish!
            //
            // since we also wait until DecreaseRadius is done this means 
            // at any time only exactly 1 star is animated at the same time
            yield return IncreaseRadius(choosenStar, duration);
        }
    }
    

    解决方案 2 - 过滤随机数

    或者,如果您想要允许星星的并行动画,我会简单地过滤掉可用星星列表(当前没有动画的星星)以获得随机范围。类似的东西

    public Light[] stars;
    
    // Use a list for dynamically adding and removing items
    private List<Light> availableStars = new List<Light>();
    
    private void Start()
    {
        // initialize the available list
        // copy the references from stars
        availableStars.AddRange(stars);
    
        StartCoroutine(ChooseStar());
    }
    
    private IEnumerator IncreaseRadius(Light star, float duration)
    {
        Debug.Log("Increasing: " + star.name + " radius: " + star.range);
    
        // As soon as you start animating this star
        // remove it from the list of availables
        availableStars.Remove(star);
    
        float counter = 0;
    
        while (counter < duration)
        {
            counter += Time.deltaTime;
            star.range = counter;
            yield return null;
        }
    
        // Decreasing and at the same time wait for it to finish
        yield return DecreaseRadius(star);
    
        // when finished add the star again to the availables
        availableStars.Add(star);
    }
    
    private static IEnumerator DecreaseRadius(Light star)
    {
        Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
        var counter = star.range;
    
        while (star.range >= 0f)
        {
            counter -= Time.deltaTime;
            star.range = counter;
            yield return null;
        }
        star.range = 0f;
    }
    
    IEnumerator ChooseStar()
    {
        // Looks scary but is totally fine in Coroutines as long as you yield somewhere
        while (true)
        {
            var duration = Random.Range(3.0f, 8.0f);
    
            // in case that currently all stars are being animated
            // simply wait until the next one becomes available again
            yield return new WaitUntil(() => availableStars.Count > 0);
    
            // Pick a random star from the availables instead
            var chosenStar = availableStars[Random.Range(0, availableStars.Count)];
    
            // this check becomes then actually redundant
            //if (chosenStar.range <= 0f)
            //{
            StartCoroutine(IncreaseRadius(chosenStar, duration));
            yield return new WaitForSeconds(2f);
            //}
        }
    }
    

    【讨论】:

    • yield return IncreaseRadius(); 是否与yield return StartCoroutine(IncreaseRadius()); 一样?
    • @trollingchar 老实说我不知道​​.. 以前从未使用过那个... 我认为不同之处在于yield return StartCoroutine(IncreaseRadius()) 启动的例程可以由StopCoroutine(IncreaseRadius()) 停止,而@987654347 @run by yield return IncreaseRadius() 无法从外部阻止。但是是的,尽管如此,我猜它也是如此
    • 也就是说,如果我不想提前停止协程,那么StartCoroutine调用是多余的?
    • @trollingchar 如前所述.. 我不知道 ^^ 但由于yield return IncreaseRadius(); 到目前为止总是完全按照我应该说的去做。
    • 经过一些测试,我确认你是对的。谢谢,我不知道这个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    • 2018-04-18
    • 2023-02-20
    • 1970-01-01
    • 1970-01-01
    • 2021-10-24
    相关资源
    最近更新 更多