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
相关产品推荐
相关产品推荐

