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
- This topic has 0 replies, 1 voice, and was last updated 3 months, 1 week ago by Terence.
-
AuthorPosts
-
August 15, 2024 at 3:25 pm #15591::
Hi folks, there is an issue
SpawnManager
script in Part 21 that makes it unable to processWaveData
scriptable objects with the Must Kill All attribute checked when we also have anEventManager
spawning mobs into the game, so we have to make a few modifications to fix it.The problem lies with the
HasWaveEnded()
function in theSpawnManager
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 theSpawnManager
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 theSpawnManager
. We add every spawned enemy into theexistingSpawns
array, and when the condition to progress to the next wave is met, instead of checking againstEnemyStats.count
, we are checking against the value ofexistingSpawns.Count
. This way, we exclude counting the enemies that are spawned by theEventManager
(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 inexistingSpawns
: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 theexistingSpawns
array, so we have to remove them ourselves. Otherwise, when we countexistingSpawns
, they will still contribute to the total count. -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: