Spawner bool

I’ve noticed that in my HandleWinCondition method it gets all of the AttackerSpawner objects and calls StopSpawn on them, this then changes the bool spawn from true to false.
However if this happens whilst the while loop is being processed then Spawn will still get called and you’ll end up with one more enemy from each of your attacker spawners.

Here’s the code that I hope shows this and my little fix

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class AttackerSpawner : MonoBehaviour
{
    private bool spawn = true;
    [SerializeField] float minTimeToSpawn = 1f;
    [SerializeField] float maxTimeToSpawn = 5f;
    [SerializeField] Attacker[] attackerPrefabs = null;
    [SerializeField] int health = 50;

    // Start is called before the first frame update
    IEnumerator Start()
    {
        while (spawn == true)
        {
            yield return new WaitForSeconds(Random.Range(minTimeToSpawn, maxTimeToSpawn));
            SpawnAttacker();
        }
    }

    private void Spawn(Attacker attacker)
    {
        Debug.Log("Spawned at: " + (int)Time.time + " seconds.");
        Attacker newAttacker = Instantiate(attacker, transform.position, transform.rotation) as Attacker;
        newAttacker.transform.parent = transform;
        Health healthComponent = attacker.GetComponent<Health>();
        if (healthComponent == null)
        {
            Debug.LogError("Health component is null on " + attacker.name);
        }
        else
        {
            healthComponent.SetHealth(health);
        }
    }

    private void SpawnAttacker()
    {
        //One last check, as it's possible spawn was set to false during the while loop, meaning this can still be called even if spawn is false
        if (spawn)
        {
            int attackerPrefabsCount = attackerPrefabs.Length;
            int attackerIndex = Random.Range(0, attackerPrefabsCount);
            Spawn(attackerPrefabs[attackerIndex]);
        }        
    }

    public void StopSpawning()
    {
        spawn = false;
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class LevelController : MonoBehaviour
{
    [SerializeField]
    [Tooltip("In seconds")]
    public float loadNextLevelTimer = 5f;

    [SerializeField]
    [Tooltip("Name of the level to load automatically after the Load Next Level Timer has finished")]
    public String level = "";

    private int numberOfAttackers = 0;
    private GameTimer gameTimer = null;
    private GameObject levelCompleteCanvas = null;
    private LevelLoader levelLoader = null;
    private bool levelComplete = false;
    // Start is called before the first frame update
    void Start()
    {
        gameTimer = FindObjectOfType<GameTimer>();

        levelCompleteCanvas = GameObject.FindGameObjectWithTag("LevelCompleteCanvas");
        if (levelCompleteCanvas != null)
        {
            levelCompleteCanvas.SetActive(false);
        }
        else
        {
            Debug.Log("levelCompleteCanves == null");
        }
        
        levelLoader = GetComponent<LevelLoader>();
        if (level != "") //level name provided
        {
            Debug.Log("Level to load automatically is " + level + " in " + loadNextLevelTimer.ToString() + " seconds");
            StartCoroutine(levelLoader.LoadLevel(level, loadNextLevelTimer));
        }
        else
        {
            Debug.Log("No level name provided to load automatically");
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (gameTimer != null) //Does the game timer exist?
        {
            Debug.Log("gameTimer is not null");
            if ((gameTimer.HasTimerFinished()) && (levelComplete == false)) //If the game timer has finished, and the level isn't yet marked as complete then handle the win condition
            {
                Debug.Log("gameTimer has finished and level is not complete");
                HandleWinCondition();                
            }
        }              
    }

    private void HandleWinCondition()
    {
        Debug.Log("HandleWinCondition() called");
        // Tell the spawners to stop spawning attackers
        AttackerSpawner[] attackerSpawners = FindObjectsOfType<AttackerSpawner>();
        foreach (AttackerSpawner spawner in attackerSpawners)
        {
            spawner.StopSpawning();
            Debug.Log("spawner.StopSpawning() called");
        }

        int numberOfAttackers = FindObjectsOfType<Attacker>().Length;
        if (numberOfAttackers == 0)
        {
            Debug.Log("There are 0 attackers left and the game timer has finished, the level is now complete.");
            levelComplete = true;
            levelCompleteCanvas.SetActive(true);
            GetComponent<AudioSource>().Play();
            StartCoroutine(levelLoader.LoadNextLevel(loadNextLevelTimer));
        }
    }

    public void Quit()
    {
        Application.Quit();
    }
}

1 Like

The suggested spawning while loop, by Rick, has timer before it will execute spawning method.
Instead of having a second condition check, the simplest fix is just making the spawning method instantaneous then wait for timer.

In my solution, the order within the loop is swapped and first yield is mandatory before the loop to keep things unchanged. This way, the first yield is already extractable for level designing choices.

    [SerializeField] private Enemy[] enemyPrefab;
    [SerializeField] private float maxSpawnTime, minSpawnTime;
    [SerializeField] private bool spawnAtStart = false;

    private bool doSpawn = true;

    private void Start()
    {
        //optional instant first wave for debug. toggle in inspector
        if (spawnAtStart)
        {
            Spawn();
        }

        StartCoroutine(SpawnEnemyLoop());
    }

    private IEnumerator SpawnEnemyLoop()
    {
        yield return new WaitForSeconds(Random.Range(minSpawnTime, maxSpawnTime));
        while (doSpawn)
        {
            Spawn();
            yield return new WaitForSeconds(Random.Range(minSpawnTime, maxSpawnTime));
        }
    }

    private void Spawn()
    {
        int enemyIndex = Random.Range(0, enemyPrefab.Length);
        Instantiate(
            enemyPrefab[enemyIndex],
            transform.position,
            Quaternion.identity,
            transform);
    }

    public void StopSpawning()
    {
        doSpawn = false;
    }
1 Like

Thanks to both of you for posting these solutions, @Vergil, @Jamie_Sandell. Very useful! I, too, found it a bit awkward that even after the spawn bool was set to false, you’d still get enemies spawning (because of their random range of spawn delays). Good ideas in there!

1 Like

Privacy & Terms