You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何在循环内中断等待协程?游戏场景实现求助

解决游戏循环中中断等待协程的问题

嘿,我之前做过类似的反应测试小游戏,完全懂你碰到的这个循环里中断等待的痛点!核心问题在于普通的for/while循环和协程的异步特性不兼容,得换个思路用协程来主导整个流程,同时用标记或者取消信号来中断等待。下面给你两种实用的解决方案,以Unity为例(其他游戏引擎思路通用):

核心思路

我们不用普通循环遍历随机数数组,而是用协程来驱动整个游戏流程:逐个显示数字 → 等待用户输入/超时 → 处理结果 → 进入下一个数字。同时设置“用户已作答”的标记,或者用取消令牌,来随时中断当前的等待计时。


方案一:布尔标记+帧循环等待(简单易上手)

这种方式适合新手,用一个布尔变量标记用户是否已经作答,在等待的帧循环里实时检查这个标记,一旦触发就立刻跳出等待。

关键代码实现

首先定义必要的变量:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ReactionGame : MonoBehaviour
{
    [SerializeField] private Text numberText; // 显示数字的UI文本
    [SerializeField] private int totalNumbers = 10; // 总题数
    
    private List<int> randomNumbers;
    private int currentIndex = 0;
    private bool isUserAnswered = false;
    private bool isCurrentAnswerCorrect = false;
    private List<bool> userAnswerRecords = new List<bool>(); // 保存用户答案的数组
}

生成随机数数组的方法:

private void GenerateRandomNumberArray()
{
    randomNumbers = new List<int>();
    System.Random rng = new System.Random();
    for (int i = 0; i < totalNumbers; i++)
    {
        randomNumbers.Add(rng.Next(1, 100)); // 生成1-99的随机整数
    }
}

主导游戏流程的主协程:

public void StartGame()
{
    GenerateRandomNumberArray();
    StartCoroutine(GameFlowCoroutine());
}

private IEnumerator GameFlowCoroutine()
{
    currentIndex = 0;
    userAnswerRecords.Clear();
    
    while (currentIndex < randomNumbers.Count)
    {
        isUserAnswered = false;
        int currentNumber = randomNumbers[currentIndex];
        
        // 显示当前数字到屏幕
        numberText.text = currentNumber.ToString();
        
        // 等待2秒或用户作答
        float waitTime = 2f;
        float elapsedTime = 0f;
        while (elapsedTime < waitTime && !isUserAnswered)
        {
            elapsedTime += Time.deltaTime;
            yield return null; // 等待一帧
        }
        
        // 处理结果
        if (!isUserAnswered)
        {
            // 用户超时未作答,记录错误
            userAnswerRecords.Add(false);
            Debug.Log("超时!");
        }
        else
        {
            // 记录用户答案的正确性
            userAnswerRecords.Add(isCurrentAnswerCorrect);
            Debug.Log(isCurrentAnswerCorrect ? "回答正确!" : "回答错误!");
        }
        
        currentIndex++;
        // 切换数字前的短暂间隔(可选)
        yield return new WaitForSeconds(0.5f);
    }
    
    // 游戏结束,处理最终结果
    numberText.text = "游戏结束!";
    ShowFinalResult();
}

用户点击按钮的响应方法:

// 左键点击(对应偶数)
public void OnLeftButtonClick()
{
    if (currentIndex >= randomNumbers.Count) return; // 游戏结束后不响应
    
    int currentNumber = randomNumbers[currentIndex];
    isCurrentAnswerCorrect = currentNumber % 2 == 0;
    isUserAnswered = true;
    numberText.text = ""; // 立刻隐藏数字
}

// 右键点击(对应奇数)
public void OnRightButtonClick()
{
    if (currentIndex >= randomNumbers.Count) return;
    
    int currentNumber = randomNumbers[currentIndex];
    isCurrentAnswerCorrect = currentNumber % 2 != 0;
    isUserAnswered = true;
    numberText.text = "";
}

方案二:CancellationToken取消令牌(更优雅的方式)

如果使用Unity 2020.1及以上版本,可以用CancellationTokenSource来创建取消信号,比布尔标记更规范,适合复杂场景。

关键代码调整

新增取消令牌相关变量:

private CancellationTokenSource _cts;

修改主协程为使用取消令牌:

private IEnumerator GameFlowCoroutine()
{
    currentIndex = 0;
    userAnswerRecords.Clear();
    
    while (currentIndex < randomNumbers.Count)
    {
        _cts = new CancellationTokenSource();
        int currentNumber = randomNumbers[currentIndex];
        numberText.text = currentNumber.ToString();
        
        bool isTimedOut = true;
        // 等待2秒,同时监听取消信号
        yield return WaitForSecondsWithCancel(2f, _cts.Token, () => isTimedOut = false);
        
        // 处理结果
        if (isTimedOut)
        {
            userAnswerRecords.Add(false);
            Debug.Log("超时!");
        }
        else
        {
            userAnswerRecords.Add(isCurrentAnswerCorrect);
            Debug.Log(isCurrentAnswerCorrect ? "回答正确!" : "回答错误!");
        }
        
        currentIndex++;
        yield return new WaitForSeconds(0.5f);
        _cts.Dispose(); // 释放资源
    }
    
    numberText.text = "游戏结束!";
    ShowFinalResult();
}

// 自定义带取消功能的等待协程
private IEnumerator WaitForSecondsWithCancel(float seconds, CancellationToken token, Action onCancel)
{
    float elapsedTime = 0f;
    while (elapsedTime < seconds)
    {
        if (token.IsCancellationRequested)
        {
            onCancel?.Invoke();
            yield break; // 中断协程
        }
        elapsedTime += Time.deltaTime;
        yield return null;
    }
}

修改按钮点击方法,触发取消信号:

public void OnLeftButtonClick()
{
    if (_cts == null || currentIndex >= randomNumbers.Count) return;
    
    int currentNumber = randomNumbers[currentIndex];
    isCurrentAnswerCorrect = currentNumber % 2 == 0;
    _cts.Cancel(); // 中断等待协程
    numberText.text = "";
}

public void OnRightButtonClick()
{
    if (_cts == null || currentIndex >= randomNumbers.Count) return;
    
    int currentNumber = randomNumbers[currentIndex];
    isCurrentAnswerCorrect = currentNumber % 2 != 0;
    _cts.Cancel();
    numberText.text = "";
}

注意事项

  • 不要用普通的for循环直接嵌套协程,因为协程是异步执行的,普通循环会直接跑完所有迭代,导致所有数字同时触发等待。
  • 确保按钮点击事件在游戏结束后不再响应,避免空引用或逻辑错误。
  • 如果是其他引擎(比如Godot),思路类似:用信号(Signal)替代布尔标记或取消令牌,在用户点击时发送信号中断等待计时器。

内容的提问来源于stack exchange,提问作者baroooo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:19:23