Unity中while检测时间槽致程序卡死的原因及解决建议
鼓机开发中的Unity卡死问题:原因与解决方案
我正在Unity中制作一个鼓机,将时间划分为16个槽位,用户可在槽位中设置1-6的变量(对应6种不同音效),当运行到对应槽位时播放指定音效。
我发现用Mathf.FloorToInt(Time.time / 0.6f) % 16可以追踪当前槽位,在Update里直接赋值打印的代码能正常工作:
void Update() { slot = Mathf.FloorToInt(Time.time / 0.6f) % 16; Debug.Log("The time is" + Time.time + " and it is in " + slot); }
这段代码能正确返回时间,槽位也会按0、1、2…循环切换。但用while循环时,比如:
while (Mathf.FloorToInt(Time.time / 0.6f) % 16 == 0) { Debug.Log(Time.time); }
或者:
while (slot == 0) { slot = Mathf.FloorToInt(Time.time / 0.6f) % 16; }
Unity启动就卡死,提示“等待Unity代码执行完成”(application.enterplaymode阶段)。我试过精简代码、新建项目,问题依旧,想知道原因和可行的替代方案。
完整参考代码如下:
using System.Collections; using UnityEngine; public class Lógica : MonoBehaviour { public int slotAtual; //was going to use this variables for visual control, but didn't got to this yet public GameObject MII; public GameObject MI; public GameObject MKICK; public GameObject SNARE; public GameObject MTOM; public GameObject MTOM2; public GameObject MOOH; public GameObject MCHH; public GameObject M2SNARE; public GameObject M2TOM; public GameObject M2TOM2; public GameObject M2OHH; public GameObject M2CHH; public int MIIi = 0; public int MIi = 0; public int MKICKi = 0; public int SNAREi = 0; public int MTOMi = 0; public int MTOM2i = 0; public int MOOHi = 0; public int MCHHi = 0; public int M2SNAREi = 0; public int M2TOMi = 0; public int M2TOM2i = 0; public int M2OHHi = 0; public int M2CHHi = 0; public int f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16; public int b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16; public AudioSource soundPlayer; private float tempoDecorrido = 0f; private float tempoInicial; public AudioClip[] sons; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { while (slotAtual == 0) { slotAtual = Mathf.FloorToInt(Time.time / 0.6f) % 16; if (Input.GetKeyDown(KeyCode.Z)) { b1 = 1; } if (Input.GetKeyDown(KeyCode.X)) { b1 = 2; } if (Input.GetKeyDown(KeyCode.C)) { b1 = 3; } if (Input.GetKeyDown(KeyCode.B)) { b1 = 4; } if (Input.GetKeyDown(KeyCode.N)) { b1 = 5; } if (Input.GetKeyDown(KeyCode.M)) { b1 = 6; } if (b1 != 0) { AudioSource.PlayClipAtPoint(sons[b1 - 1], transform.position); } } } }
卡死原因
Unity的Update方法是每一帧同步执行的,而while循环是阻塞式的:只要循环条件满足,它会一直重复执行循环体内的代码,完全占用当前帧的执行线程,根本不给Unity更新Time.time的机会。
比如游戏刚启动时,slotAtual初始值为0,进入while循环后,Time.time还没到0.6,计算结果始终为0,slotAtual被重新赋值为0,循环条件永远成立,直接陷入死循环,导致Unity主线程卡死,无法进入下一帧或完成playmode加载。
替代实现方案
鼓机的核心是检测槽位切换事件,而非用循环阻塞线程,推荐两种可靠实现方式:
方案1:Update中检测槽位变化
通过记录上一帧的槽位,对比当前帧的槽位,仅当槽位变化时执行对应逻辑:
public class Lógica : MonoBehaviour { public int slotAtual; private int lastSlot = -1; // 初始值设为不存在的槽位 public float slotDuration = 0.6f; // 每个槽位的持续时间 // 其他变量保持不变... void Update() { // 计算当前槽位 slotAtual = Mathf.FloorToInt(Time.time / slotDuration) % 16; // 仅当槽位发生变化时执行逻辑 if (slotAtual != lastSlot) { lastSlot = slotAtual; PlaySlotSound(slotAtual); HandleInputForSlot(slotAtual); } } // 根据当前槽位播放对应音效 void PlaySlotSound(int slot) { int soundIndex = slot switch { 0 => b1, 1 => b2, 2 => b3, 3 => b4, 4 => b5, 5 => b6, 6 => b7, 7 => b8, 8 => b9, 9 => b10, 10 => b11, 11 => b12, 12 => b13, 13 => b14, 14 => b15, 15 => b16, _ => 0 }; if (soundIndex != 0 && soundIndex <= sons.Length) { AudioSource.PlayClipAtPoint(sons[soundIndex - 1], transform.position); } } // 处理当前槽位的输入(示例为槽位0,可扩展到其他槽位) void HandleInputForSlot(int slot) { if (slot == 0) { if (Input.GetKeyDown(KeyCode.Z)) b1 = 1; if (Input.GetKeyDown(KeyCode.X)) b1 = 2; if (Input.GetKeyDown(KeyCode.C)) b1 = 3; if (Input.GetKeyDown(KeyCode.B)) b1 = 4; if (Input.GetKeyDown(KeyCode.N)) b1 = 5; if (Input.GetKeyDown(KeyCode.M)) b1 = 6; } // 可添加其他槽位的输入逻辑 } }
方案2:使用协程(精准时间控制)
协程通过WaitForSeconds精准控制每个槽位的间隔,避免依赖Time.time的帧更新误差:
public class Lógica : MonoBehaviour { public int slotAtual; public float slotDuration = 0.6f; private Coroutine drumLoop; // 其他变量保持不变... void Start() { // 启动鼓机循环协程 drumLoop = StartCoroutine(DrumMachineLoop()); } IEnumerator DrumMachineLoop() { while (true) { // 计算当前槽位 slotAtual = Mathf.FloorToInt(Time.time / slotDuration) % 16; // 执行当前槽位的逻辑 PlaySlotSound(slotAtual); HandleInputForSlot(slotAtual); // 等待到下一个槽位 yield return new WaitForSeconds(slotDuration); } } // PlaySlotSound和HandleInputForSlot方法同方案1... }
关键注意点
- 永远不要在Update、Start等主线程方法中使用阻塞式循环(while/for死循环),Unity主线程负责渲染、输入、逻辑更新,一旦阻塞就会卡死。
- 鼓机核心是事件触发(槽位切换时执行逻辑),而非持续轮询。
- 协程方式的时间控制更精准,适合音乐类游戏的节奏要求。
内容的提问来源于stack exchange,提问作者Lúcio Mortari Cavalheiro
相关产品推荐
相关产品推荐

