Forum begins after the advertisement:


[Part 21] Must Kill All attribute in new Spawn Manager script does not work

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [Part 21] Must Kill All attribute in new Spawn Manager script does not work

Viewing 1 post (of 1 total)
  • Author
    Posts
  • #15591
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi folks, there is an issue SpawnManager script in Part 21 that makes it unable to process WaveData scriptable objects with the Must Kill All attribute checked when we also have an EventManager spawning mobs into the game, so we have to make a few modifications to fix it.

    null

    The problem lies with the HasWaveEnded() function in the SpawnManager script. One of the things it checks for before advancing to the next wave of enemies is whether the Must Kill All flag is checked; and if it is, whether there are any enemies left on the map. It only advances to the next wave when there are no more enemies on the map (see yellow highlighted section below).

    public bool HasWaveEnded()
    {
        WaveData currentWave = data[currentWaveIndex];
    
        // If waveDuration is one of the exit conditions, check how long the wave has been running.
        // If current wave duration is not greater than wave duration, do not exit yet.
        if ((currentWave.exitConditions & WaveData.ExitCondition.waveDuration) > 0)
            if (currentWaveDuration < currentWave.duration) return false;
    
        // If reachedTotalSpawns is one of the exit conditions, check if we have spawned enough
        // enemies. If not, return false.
        if ((currentWave.exitConditions & WaveData.ExitCondition.reachedTotalSpawns) > 0)
            if (currentWaveSpawnCount < currentWave.totalSpawns) return false;
    
        // Otherwise, if kill all is checked, we have to make sure there are no more enemies first.
        if (currentWave.mustKillAll && EnemyStats.count > 0)
            return false;
    
        return true;
    }

    However, that can be a condition that is impossible to achieve if you also have other scripts that are spawning enemies as well, such as the EventManager.

    Hence, we will need to change the way that SpawnManager keeps track of its own enemies on the map. This is done by making a few changes to the SpawnManager script.

    using UnityEngine;
    using System.Collections.Generic;
    
    public class SpawnManager : MonoBehaviour
    {
    
        int currentWaveIndex; //The index of the current wave [Remember, a list starts from 0]
        int currentWaveSpawnCount = 0; // Tracks how many enemies current wave has spawned.
        List<GameObject> existingSpawns = new List<GameObject>();
    
        public WaveData[] data;
        public Camera referenceCamera;
    
        [Tooltip("If there are more than this number of enemies, stop spawning any more. For performance.")]
        public int maximumEnemyCount = 300;
        float spawnTimer; // Timer used to determine when to spawn the next group of enemy.
        float currentWaveDuration = 0f;
        public bool boostedByCurse = true;
    
        public static SpawnManager instance;
    
        void Start()
        {
            if (instance) Debug.LogWarning("There is more than 1 Spawn Manager in the Scene! Please remove the extras.");
            instance = this;
        }
    
        void Update()
        {
            // Updates the spawn timer at every frame.
            spawnTimer -= Time.deltaTime;
            currentWaveDuration += Time.deltaTime;
    
            if (spawnTimer <= 0)
            {
                // Check if we are ready to move on to the new wave.
                if (HasWaveEnded())
                {
                    currentWaveIndex++;
                    currentWaveDuration = currentWaveSpawnCount = 0;
    
                    // If we have gone through all the waves, disable this component.
                    if (currentWaveIndex >= data.Length)
                    {
                        Debug.Log("All waves have been spawned! Shutting down.", this);
                        enabled = false;
                    }
    
                    return;
                }
    
                // Do not spawn enemies if we do not meet the conditions to do so.
                if (!CanSpawn())
                {
                    ActivateCooldown();
                    return;
                }
    
                // Get the array of enemies that we are spawning for this tick.
                GameObject[] spawns = data[currentWaveIndex].GetSpawns(EnemyStats.count);
    
                // Loop through and spawn all the prefabs.
                foreach (GameObject prefab in spawns)
                {
                    // Stop spawning enemies if we exceed the limit.
                    if (!CanSpawn()) continue;
    
                    // Spawn the enemy.
                    existingSpawns.Add( Instantiate(prefab, GeneratePosition(), Quaternion.identity) );
                    currentWaveSpawnCount++;
                }
    
                ActivateCooldown();
            }
        }
    
        // Resets the spawn interval.
        public void ActivateCooldown()
        {
            float curseBoost = boostedByCurse ? GameManager.GetCumulativeCurse() : 1;
            spawnTimer += data[currentWaveIndex].GetSpawnInterval() / curseBoost;
        }
    
        // Do we meet the conditions to be able to continue spawning?
        public bool CanSpawn()
        {
            // Don't spawn anymore if we exceed the max limit.
            if (HasExceededMaxEnemies()) return false;
    
            // Don't spawn if we exceeded the max spawns for the wave.
            if (instance.currentWaveSpawnCount > instance.data[instance.currentWaveIndex].totalSpawns) return false;
    
            // Don't spawn if we exceeded the wave's duration.
            if (instance.currentWaveDuration > instance.data[instance.currentWaveIndex].duration) return false;
            return true;
        }
    
        // Allows other scripts to check if we have exceeded the maximum number of enemies.
        public static bool HasExceededMaxEnemies()
        {
            if (!instance) return false; // If there is no spawn manager, don't limit max enemies.
            if (EnemyStats.count > instance.maximumEnemyCount) return true;
            return false;
        }
    
        public bool HasWaveEnded()
        {
            WaveData currentWave = data[currentWaveIndex];
    
            // If waveDuration is one of the exit conditions, check how long the wave has been running.
            // If current wave duration is not greater than wave duration, do not exit yet.
            if ((currentWave.exitConditions & WaveData.ExitCondition.waveDuration) > 0)
                if (currentWaveDuration < currentWave.duration) return false;
    
            // If reachedTotalSpawns is one of the exit conditions, check if we have spawned enough
            // enemies. If not, return false.
            if ((currentWave.exitConditions & WaveData.ExitCondition.reachedTotalSpawns) > 0)
                if (currentWaveSpawnCount < currentWave.totalSpawns) return false;
    
            // Otherwise, if kill all is checked, we have to make sure there are no more enemies first.
            existingSpawns.RemoveAll(item => item == null);
            if (currentWave.mustKillAll && EnemyStats.countexistingSpawns.Count > 0)
                return false;
    
            return true;
        }
    
        void Reset()
        {
            referenceCamera = Camera.main;
        }
    
        // Creates a new location where we can place the enemy at.
        public static Vector3 GeneratePosition()
        {
            // If there is no reference camera, then get one.
            if (!instance.referenceCamera) instance.referenceCamera = Camera.main;
    
            // Give a warning if the camera is not orthographic.
            if (!instance.referenceCamera.orthographic)
                Debug.LogWarning("The reference camera is not orthographic! This will cause enemy spawns to sometimes appear within camera boundaries!");
    
            // Generate a position outside of camera boundaries using 2 random numbers.
            float x = Random.Range(0f, 1f), y = Random.Range(0f, 1f);
    
            // Then, randomly choose whether we want to round the x or the y value.
            switch (Random.Range(0, 2))
            {
                case 0:
                default:
                    return instance.referenceCamera.ViewportToWorldPoint(new Vector3(Mathf.Round(x), y));
                case 1:
                    return instance.referenceCamera.ViewportToWorldPoint(new Vector3(x, Mathf.Round(y)));
            }
        }
    
        // Checking if the enemy is within the camera's boundaries.
        public static bool IsWithinBoundaries(Transform checkedObject)
        {
            // Get the camera to check if we are within boundaries.
            Camera c = instance && instance.referenceCamera ? instance.referenceCamera : Camera.main;
    
            Vector2 viewport = c.WorldToViewportPoint(checkedObject.position);
            if (viewport.x < 0f || viewport.x > 1f) return false;
            if (viewport.y < 0f || viewport.y > 1f) return false;
            return true;
        }
    }

    Essentially, what is happening above is that we are adding a new private list called existingSpawns that is used to track enemies that have been spawned by the SpawnManager. We add every spawned enemy into the existingSpawns array, and when the condition to progress to the next wave is met, instead of checking against EnemyStats.count, we are checking against the value of existingSpawns.Count. This way, we exclude counting the enemies that are spawned by the EventManager (or any other other script that we may have).

    One thing to note is that, in the updated HasWaveEnded() function, we also have a line that removes all null enemies in existingSpawns:

    existingSpawns.RemoveAll(item => item == null);

    What this line of code does is remove all objects that are null from the existingSpawns array. The enemies in the list become null after they get killed or removed from the game some other way. When the game deletes them, they are not automatically removed from the existingSpawns array, so we have to remove them ourselves. Otherwise, when we count existingSpawns, they will still contribute to the total count.

    View post on imgur.com

Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: