【问题标题】:Calling a Coroutine from UI button gets unpredictable behavior Unity从 UI 按钮调用协程会出现不可预知的行为 Unity
【发布时间】:2017-11-18 23:33:57
【问题描述】:

已解决:将 Unity 更新至 2017.2 版

当在 Click() 管理器的 UI 按钮的执行列表中添加超过 1 个任务时,这些任务的执行会出现不可预知的行为。当该列表中添加了多个任务时,我的倒计时脚本的计数速度是原来的两倍(参见附图)。当我删除一项任务时,代码会正常运行(每秒将倒计时打印到屏幕上 5..4..3..2..1 等)。统一漏洞?

public IEnumerator Countdown()
{
    while (timeLeft > 0)
    {
        yield return new WaitForSeconds(1.0f);
        countText.text = timeLeft.ToString("f0");
        timeLeft--;          
    }
}

public void StartCountDown()
{
    StartCoroutine("Countdown");
}

【问题讨论】:

  • 那么协程是同时运行两次吗?
  • 是的。因此,当我在 On Click() 列表中再添加一项任务(总共 3 个任务)时,它会运行 3 次,依此类推。为什么会这样?

标签: c# unity3d uibutton coroutine


【解决方案1】:

每次调用协程函数时,都会修改timeLeft 变量。如果您在第一个协程函数正在运行时再次调用该协程函数,那么多个协程函数将同时修改 timeLeft 变量,从而导致您当前的问题。

一种解决方案是使用布尔变量来检测协程何时已经运行并再次启动,但您提到要同时创建多个计时器。你有两个选择:

1.将timeLeft 变量移到Countdown 函数中,以便每个函数调用都有一个timeLeft 变量。也对countText 变量执行相同操作,以便在每个函数调用中使用不同的Text 组件来修改Text

public IEnumerator Countdown()
{
    float timeLeft = 5f;
    Text countText = GameObject.Find("TextForThisCounter").GetComponent<Text>();

    while (timeLeft > 0)
    {
        yield return new WaitForSeconds(1.0f);
        countText.text = timeLeft.ToString("f0");
        timeLeft--;
    }
}

2.将整个协程函数移动到另一个类,然后使用回调Action 来通知您每秒有一个计时器滴答声以及计时器何时完成。我推荐这种方法,因为它更便携和可重用。您还可以实现一个 ID 来确定它完成运行时是哪个计时器。

定时器移到另一个脚本:

public struct CountDownTimer
{
    private static int sTimerID = 0;
    private MonoBehaviour monoBehaviour;

    public int timer { get { return localTimer; } }
    private int localTimer;

    public int timerID { get { return localID; } }
    private int localID;

    public CountDownTimer(MonoBehaviour monoBehaviour)
    {
        this.monoBehaviour = monoBehaviour;
        localTimer = 0;

        //Assign timer ID
        sTimerID++;
        localID = sTimerID;
    }

    public void Start(int interval, Action<int> tickCallBack, Action<int> finshedCallBack)
    {
        localTimer = interval;
        monoBehaviour.StartCoroutine(beginCountDown(tickCallBack, finshedCallBack));
    }

    private IEnumerator beginCountDown(Action<int> tickCallBack, Action<int> finshedCallBack)
    {
        while (localTimer > 0)
        {
            yield return new WaitForSeconds(1.0f);
            localTimer--;
            //Notify tickCallBack in each clock tick
            tickCallBack(localTimer);
        }

        //Notify finshedCallBack after timer is done
        finshedCallBack(localID);
    }
}

用法:

启动计时器 4 次,每次使用不同的 ID。

void Start()
{
    createAndStartNewTimer();

    createAndStartNewTimer();

    createAndStartNewTimer();

    createAndStartNewTimer();
}


public void createAndStartNewTimer()
{
    //Create new Timer
    CountDownTimer timer = new CountDownTimer(this);

    //What to do each second time tick in the timer
    Action<int> tickCallBack = (timeLeft) =>
    {
        Debug.Log(timeLeft.ToString("f0"));
    };


    //What to do each second time tick in the timer
    Action<int> finshedCallBack = (timeriD) =>
       {
           Debug.Log("Count Down Timer Done! ID: " + timeriD);
       };

    //Start Countdown Timer from 5
    timer.Start(5, tickCallBack, finshedCallBack);
}

【讨论】:

  • “每次调用协程函数时,都会修改 timeLeft 变量。”——这很明显,但这里的问题是,当协程只有一个任务时,它会被调用 ONCE。为什么当我向 On click 事件添加更多任务时它会运行两次?它不应该这样。 “但你提到你想同时创建多个计时器。” ——不,我没有那样说。我只需要它运行一次。
  • 从您的问题 “当在 Click() 管理器上的 UI 按钮的执行列表中添加超过 1 个任务时,这些任务的执行会出现不可预知的行为” 如果添加`StartCountDown()` 到 onClick 插槽多次或超过 1 次,`StartCountDown()` 函数将被调用该函数添加的时间量。这不是一个错误,应该表现得像那样。
  • 好吧,如果你仔细看附图,我不会两次添加相同的任务,我正在添加另一个与我的倒计时脚本无关的任务,然后统一疯狂并运行我的协程两次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 2018-07-29
  • 2012-12-28
  • 1970-01-01
  • 2014-06-12
  • 2013-05-25
相关资源
最近更新 更多