【问题标题】:Why does this enemy spawn method cause Unity to crash?为什么这个敌人生成方法会导致 Unity 崩溃?
【发布时间】:2025-11-24 21:10:02
【问题描述】:

我已经在我的 Unity 2D 项目的主要 Gameplay.cs 脚本中处理这部分代码很长一段时间了,无论我以哪种方式更改它并改变它,它都会导致 Unity 锁定在 Play 上,或者经过一些调整后,会立即从 Wave 0 转到 Wave 9。这是我最初的尝试,它导致 Unity 在 Play 上冻结。我不明白为什么会发生这种情况,并想了解我的逻辑哪里出了问题。

我正在努力实现以下目标:

  • 在 Play 中,游戏等待 startWait 秒。
  • startWait 后,游戏进入 SpawnWaves() 方法并等待 waveWait 每波之间的秒数,并增加波数 每一波都有一个。
  • 每一波都会产生zombieCount 数量的敌人并等待 spawnWait 秒,在每个僵尸产生之间。


private float startWait = 3f;   //Amount of time to wait before first wave
private float waveWait = 2f;    //Amount of time to wait between spawning each wave
private float spawnWait = 0.5f; //Amount of time to wait between spawning each enemy

private float startTimer = 0f; //Keep track of time that has elapsed since game launch
private float waveTimer = 0f;  //Keep track of time that has elapsed since last wave
private float spawnTimer = 0f; //Keep track of time that has elapsed since last spawn

private List<GameObject> zombies = new List<GameObject>();

[SerializeField]
private Transform spawnPoints; //The parent object containing all of the spawn point objects


void Start()
{
    deathUI.SetActive(false);

    //Set the default number of zombies to spawn per wave
    zombieCount = 5;

    PopulateZombies();
}

void Update()
{
    startTimer += Time.deltaTime;
    if (startTimer >= startWait)
    {
        waveTimer += Time.deltaTime;
        spawnTimer += Time.deltaTime;

        SpawnWaves();
    }


    //When the player dies "pause" the game and bring up the Death screen
    if (Player.isDead == true)
    {
        Time.timeScale = 0;
        deathUI.SetActive(true);
    }

}

void SpawnWaves()
{
    while (!Player.isDead && ScoreCntl.wave < 9)
    {
        if (waveTimer >= waveWait)
        {
            IncrementWave();
            for (int i = 0; i < zombieCount; i++)
            {
                if (spawnTimer >= spawnWait)
                {
                    Vector3 spawnPosition = spawnPoints.GetChild(Random.Range(0, spawnPoints.childCount)).position;
                    Quaternion spawnRotation = Quaternion.identity;
                    GameObject created = Instantiate(zombies[0], spawnPosition, spawnRotation);
                    TransformCreated(created);

                    spawnTimer = 0f;
                }
            }

            waveTimer = 0f;
        }
    }
}

我是初学者,我理解我的代码可能不遵循最佳实践。我还有一个使用 Coroutine 和 yield return 的正常工作的敌人生成器,但我想让这个版本的敌人生成器工作。

【问题讨论】:

  • 更改时间表需要格外小心。在第一次更新脚本之前确保玩家没有死
  • 你也可以在yield return new WaitForSecondsRealtime 中使用协程,以便在将时间刻度更改为零后游戏继续运行
  • 在我的 Player.cs 脚本中,我正在通过 Update() 中调用的方法检查玩家是否已死亡,如果他们已死亡,我设置 isDead == true,然后由 Gameplay.cs 使用.在 Player.cs isDead == false on Awake()。目前游戏将一直运行到 4 秒,也就是第一波应该出现的时间。然后它就冻结了。

标签: c# loops unity3d spawning


【解决方案1】:

我对 Unity 没有任何经验,但对于 XNA,我认为它们的主要游戏处理与使用 Update() 和 Draw() 函数相似。

Update() 函数通常每秒调用几次,在您的代码中 SpawnWaves()应该在每次 Update 调用后执行一次 startTimer >= startWait。

让我们看看 SpawnWaves()

void SpawnWaves()
{
    while (!Player.isDead && ScoreCntl.wave < 9)
    {
        if (waveTimer >= waveWait)
        {
            IncrementWave();
            for (int i = 0; i < zombieCount; i++)
            {
                if (spawnTimer >= spawnWait)
                {
                    [..]
                    spawnTimer = 0f;
                }
            }
            waveTimer = 0f;
        }
    }
}

这个while循环是个问题,因为它是游戏循环中的一种游戏循环。它只会在玩家死亡或您的波数超过 9 时退出。

调用 SpawnWaves() 后有两种可能,取决于 Time.deltaTime,它在程序启动后或多或少是随机的,waveTimer 可以大于或小于 waveWait。

  • 案例:waveTimer >= waveWait

    里面的所有代码都会立即执行。 因此,如果它可以使用 IncrementWave(),它将在毫秒内完成九次。然后while为假,里面的代码再也不会执行了。

  • 案例:waveTimer

    在这种情况下,IncrementWave() 将永远不会被调用,并且您的游戏可以在这个 while 循环中无限循环

所以你要做的基本上就是用 if 语句替换这个 while 循环,让程序继续运行。

If(!Player.isDead && ScoreCntl.wave < 9)

从那时起,您的计数器增量将在 Update() 上多次调用。

waveTimer += Time.deltaTime;
spawnTimer += Time.deltaTime;

随着时间的推移,你的波浪变化和产卵条件会变得真实。

if (waveTimer >= waveWait),
if (spawnTimer >= spawnWait)

我希望这可以指导您解决问题。

更新:

对于这种行为,最好将 IncrementWave 和生成条件分开。为此,您需要一个额外的 bool 变量,在本例中称为 haveToSpawn。

    if (waveTimer >= waveWait)
    {
      IncrementWave();
      waveTimer = 0f;
      haveToSpawn = true; //new variable
    }

    if (haveToSpawn && spawnTimer >= spawnWait)
    {
        for (int i = 0; i < zombieCount; i++)
        {
            [..] //spawn each zonbie
        }
        spawnTimer = 0f; //reset spawn delay
        haveToSpawn = false; //disallow spawing
    }

【讨论】:

  • 哇,这应该非常明显。我怀疑 Update() 是问题所在,并尝试将 SpawnWaves() 移到 Start() 中,但并没有想到简单地切换 if 的时间。谢谢!但是,我的后续代码一定有问题,因为无论我做什么,它都会忽略 (??) for 循环并产生一个僵尸,增加 wave 并重复。当它应该每波产生更多的僵尸时。我什至尝试将 i++ 移动到循环本身,在僵尸肯定产生之后,但这只会让 Unity 冻结在僵尸应该产生的点。
  • 我写了一个小更新,应该会产生所有的僵尸。希望它有效。
  • 搞笑的是,它一个接一个地催生了大量的僵尸。波浪增量工作得很好,但是 spawnWait 应该发生在给定波浪内的每个僵尸生成之间。在这种情况下,它一次产生了所有的zombieCount,而不是一个一个地产生,然后等待waveWait 秒,然后再次释放所有的zombieCount。这对于一次生成非常有效。
  • 在这种情况下,使用一个变量来保存生成的僵尸的数量,并将 for(int i...) 替换为类似 if(zombieSpawned
【解决方案2】:

使用协程调用 spawn waves 方法:

private float startWait = 3f;   //Amount of time to wait before first wave
private float waveWait = 2f;    //Amount of time to wait between spawning each wave
private float spawnWait = 0.5f; //Amount of time to wait between spawning each enemy


private float startTimer = 0f; //Keep track of time that has elapsed since game launch
private float waveTimer = 0f;  //Keep track of time that has elapsed since last wave
private float spawnTimer = 0f; //Keep track of time that has elapsed since last spawn

private List<GameObject> zombies = new List<GameObject>();

[SerializeField]
private Transform spawnPoints; //The parent object containing all of the spawn point objects


void Start()
{
    deathUI.SetActive(false);

    //Set the default number of zombies to spawn per wave
    zombieCount = 5;

    PopulateZombies();

    StartCoroutine(SpawnWaves());
}

void Update()
{
    startTimer += Time.deltaTime;
    if (startTimer >= startWait)
    {
        waveTimer += Time.deltaTime;
        spawnTimer += Time.deltaTime;
    }


    //When the player dies "pause" the game and bring up the Death screen
    if (Player.isDead == true)
    {
        Time.timeScale = 0;
        deathUI.SetActive(true);
    }

}

IEnumerator SpawnWaves()
{

    //wait 3 seconds
    yield return new WaitForSeconds(startWait);



    //then:

    while (!Player.isDead && ScoreCntl.wave < 9)
    {
        if (waveTimer >= waveWait)
        {
            IncrementWave();
            for (int i = 0; i < zombieCount; i++)
            {
                if (spawnTimer >= spawnWait)
                {
                    Vector3 spawnPosition = spawnPoints.GetChild(Random.Range(0, spawnPoints.childCount)).position;
                    Quaternion spawnRotation = Quaternion.identity;
                    GameObject created = Instantiate(zombies[0], spawnPosition, spawnRotation);
                    TransformCreated(created);

                    spawnTimer = 0f;
                }
            }

            waveTimer = 0f;
        }

        //wait until the end of frame
        yield return null;
    }
}

为了更好地理解统一协程的工作原理:

协程是返回类型为 IEnumerator 的方法,其行为类似于异步执行的代码块的集合。 yield 指令分隔代码块并指定在下一个代码块开始执行之前要等待的时间帧数。

yield 指令有几种类型:

  • null : 等待帧结束(渲染前)
  • WaitForEndOfFrame : 等待帧结束(渲染后)
  • WaitForSeconds :等待指定的秒数(包括时间刻度)
  • WaitForSecondsRealtime : 等待指定的秒数(忽略时间刻度)

你可以想到

  • 作为协程更新,yield return null
  • LateUpdate 作为协程,带有 yield return new WaitForEndOfFrame()
  • FixedUpdate 作为协程与 yield return new WaitForSeconds(.2f)

【讨论】:

  • 谢谢!我避免使用协程和多个收益返回——因为我已经走了那条路——但我不认为将协程与计时器结合使用(而不是所有收益返回)。这确实奏效了,除了我随后的代码很不稳定——这完全是我将要解决的另一个问题。感谢您提供有关协程和更新的更多信息。
最近更新 更多