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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:09:58