为什么这种敌人生成方法会导致 Unity 崩溃?

Why does this enemy spawn method cause Unity to crash?

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

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


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</p>

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 returns 的工作敌人生成器,但我想让这个版本的敌人生成工作。

使用协程调用 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;
    }
}

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

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

有几种类型的 yield 指令:

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

你可以想到

  • 作为协程更新,yield return null
  • LateUpdate 作为协同程序,产生 return new WaitForEndOfFrame()
  • FixedUpdate 作为协程,产生 return new WaitForSeconds(.2f)

我对 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 循环是个问题,因为它是游戏循环中的一种游戏循环。它只会在玩家死亡或你的波数超过九次时退出。

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

  • 案例:waveTimer >= waveWait

    里面的所有代码都会无延迟地执行。 因此,如果它有可能使用 IncrementWave(),它将在几毫秒内完成九次。然后while为false,里面的代码再也没有执行过。

  • 案例:waveTimer < waveWait

    在这种情况下,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
    }