Forum begins after the advertisement:
[Part 21] The Mob Event doesn’t work
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 21] The Mob Event doesn’t work
- This topic has 1 reply, 2 voices, and was last updated 1 week, 3 days ago by
Alp Apustaja.
-
AuthorPosts
-
August 19, 2025 at 3:53 am #18700::
Hello,
I’m following the part 21 and I try to implement the mob event but it doesn’t work. Actually the enemies are not charging each 10″. The charging script work.
Here is my code :
using System.Collections; using System.Collections.Generic; using UnityEngine; public class VMP_EventManager : MonoBehaviour { public static VMP_EventManager instance; float currentEventCooldown = 0; public VMP_EventData[] events; [Tooltip("How long to wait before this becomes active.")] public float firstTriggerDelay = 180f; [Tooltip("How long to wait between each event. ")] public float triggerInterval = 30f; [System.Serializable] public class Event { public VMP_EventData data; public float duration, cooldown = 0; } List<Event> runningEvents = new(); // These are events that have been activated, and are running. VMP_PlayerStats[] allPlayers; void Start() { if (instance) Debug.LogWarning("There is more than 1 Spawn Manager in the Scene! Please remove the extras."); instance = this; currentEventCooldown = firstTriggerDelay > 0 ? firstTriggerDelay : triggerInterval; allPlayers = FindObjectsByType<VMP_PlayerStats>(FindObjectsSortMode.None); } void Update() { // Cooldown for adding another event to the slate. currentEventCooldown -= Time.deltaTime; if (currentEventCooldown <= 0) { // Get an event and try to execute it. VMP_EventData e = GetRandomEvent(); if (e && e.CanHappen(allPlayers[Random.Range(0, allPlayers.Length)])) runningEvents.Add(new Event { data = e, duration = e.duration }); // Set the cooldown for the event. currentEventCooldown = triggerInterval; } // Events that we want to remove. List<Event> toRemove = new(); // Cooldown for existing event to see if they should continue running. foreach (Event e in runningEvents) { // Reduce the current duration. e.duration -= Time.deltaTime; if (e.duration <= 0) { toRemove.Add(e); continue; } // Reduce the current cooldown. e.cooldown -= Time.deltaTime; if (e.cooldown <= 0) { // Pick a random player to sic this mob on, // then reset the cooldown. e.data.Activate(allPlayers[Random.Range(0, allPlayers.Length)]); e.cooldown = e.data.GetSpawnInterval(); } } // Remove all the events that have expired. foreach (Event e in toRemove) runningEvents.Remove(e); } public VMP_EventData GetRandomEvent() { // If no events are assigned, don't return anything. if (events.Length <= 0) return null; // Get a list of all possible events. List<VMP_EventData> possibleEvents = new(events); // Add the events in event to the possible events only if the event is active foreach (VMP_EventData e in events) if (e.IsActive()) possibleEvents.Add(e); // Randomly pick an event from the possible events to play if (possibleEvents.Count > 0) { VMP_EventData result = possibleEvents[Random.Range(0, possibleEvents.Count)]; return result; } return null; } }
using UnityEngine; [CreateAssetMenu(menuName = "Vampire Survivor/Spawn System/Events/new Mob Event", fileName = "MobEvent_00")] public class VMP_MobEventData : VMP_EventData { [Header("Mob Data")] [Range(0f, 360f)] public float possibleAngles = 360f; [Min(0)] public float spawnRadius = 2f, spawnDistance = 20f; public override bool Activate(VMP_PlayerStats player = null, bool alwaysFires = false) { // Only activate this if the player is present. if (player) { // We spawn a mob outside of the screen and move it towards the player. float randomAngle = Random.Range(0, possibleAngles) * Mathf.Deg2Rad; Vector2 direction = new Vector2(Mathf.Cos(randomAngle), Mathf.Sin(randomAngle)); foreach (GameObject o in GetSpawns(VMP_EnemyStats.count)) { // Calculate a random distance for each enemy within the defined radius. float currentSpawnDistance = spawnDistance + Random.Range(-spawnRadius, spawnRadius); // Calculate the final spawn position. Vector3 spawnPosition = player.transform.position + ((Vector3)direction * currentSpawnDistance); Instantiate(o, spawnPosition, Quaternion.identity); } return true; } return false; } }
using UnityEngine; using System.Collections.Generic; public class VMP_SpawnManager : Singleton<VMP_SpawnManager> { 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 VMP_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; 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()) { spawnTimer += data[currentWaveIndex].GetSpawnInterval(); return; } // Get the array of enemies that we are spawning for this tick. GameObject[] spawns = data[currentWaveIndex].GetSpawns(VMP_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++; } // Regenerates the spawn timer. spawnTimer += data[currentWaveIndex].GetSpawnInterval(); } } // 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].maximum_Count) 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 (VMP_EnemyStats.count > Instance.maximumEnemyCount) return true; return false; } public bool HasWaveEnded() { VMP_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.exit_Conditions & VMP_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.exit_Conditions & VMP_WaveData.ExitCondition.reachedTotalSpawns) > 0) if (currentWaveSpawnCount < currentWave.maximum_Count) 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 && existingSpawns.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; } }
using UnityEngine; public abstract class VMP_EventData : VMP_SpawnData { [Header("Event Data")] [Tooltip("The probability is connected to luck factor. 1 = always happend, inferior of 1 = depends on luck.\nA short amount here and a large amount luck = almost never")] [Range(0f, 1f)] public float probability = 1f; // Whether this event occurs. [Tooltip("Connected to the probability. If the luck is 1 = the event is fully depend of the luck (1 it doesn't active, 0, it always active)")] [Range(0f, 1f)] public float luckFactor = 1f; // How much luck affects the probability of this event. [Tooltip("Only activate the event after X amount of time in seconds (the counter start from the begining of the level)")] public float activeAfter = 0; public abstract bool Activate(VMP_PlayerStats player = null, bool alwaysFires = false); // Checks whether this event is currently active. public bool IsActive() { if (!VMP_GameManager.Instance) return false; if (VMP_GameManager.Instance.GetElapsedTime() > activeAfter) return true; return false; } // Calculates a random probability of this event happening. public bool CanHappen(VMP_PlayerStats stats) { // Probability of 1 means it always happens. if (probability >= 1) return true; // Otherwise, get a random number and see if we pass the probability test. if (probability / Mathf.Max(1, (stats.Stats.luck * luckFactor)) >= Random.Range(0f, 1f)) return true; return false; } }
This code work :
using UnityEngine; public class VMP_ChargingEnemy : VMP_EnemyMovement { Vector2 chargeDirection; // We calculate the direction where the enemy charges towards first, // i.e. where the player is when the enemy spawns. protected override void Start() { base.Start(); chargeDirection = (player.transform.position - transform.position).normalized; } // Instead of moving towards the player, we just move towards // the direction we are charging towards. public override void Move() { transform.position += (Vector3)chargeDirection * enemy.currentMoveSpeed * Time.deltaTime; } }
I tried to fix that with Gemini but it doesn’t work.
Thank you
August 19, 2025 at 4:00 am #18701::Hi Boby,
It looks like you’re trying to implement a mob event in your game, but the enemies are not charging every 10 seconds as expected. Let’s troubleshoot your code step by step to identify potential issues.
1. Check Event Activation
The mob event is triggered if
e.CanHappen(allPlayers[Random.Range(0, allPlayers.Length)])
returns true. Ensure that theCanHappen
function in yourVMP_EventData
class is correctly calculating whether the event can occur. If theprobability
orluckFactor
is set in a way that makes it unlikely to trigger, the event won’t activate.2. Debugging the Event Manager
Add some debug logs to your
Update
method inVMP_EventManager
to monitor the event activation:void Update() { // Cooldown for adding another event to the slate. currentEventCooldown -= Time.deltaTime; if (currentEventCooldown <= 0) { // Get an event and try to execute it. VMP_EventData e = GetRandomEvent(); if (e && e.CanHappen(allPlayers[Random.Range(0, allPlayers.Length)])) { Debug.Log("Activating Event: " + e.name); // Log the activated event runningEvents.Add(new Event { data = e, duration = e.duration }); } else { Debug.Log("Event not activated"); // Log if the event was not activated } // Set the cooldown for the event. currentEventCooldown = triggerInterval; } ... }
3. Event Cooldown and Duration
Make sure that the
duration
andcooldown
values of your events are being set correctly. If theduration
is very short, the event may be removed quickly without spawning anything.4. Spawning Logic
In your
VMP_MobEventData
, ensure thatGetSpawns(VMP_EnemyStats.count)
is returning the correct enemies to spawn. You might want to log the count of enemies being spawned:public override bool Activate(VMP_PlayerStats player = null, bool alwaysFires = false) { if (player) { float randomAngle = Random.Range(0, possibleAngles) * Mathf.Deg2Rad; Vector2 direction = new Vector2(Mathf.Cos(randomAngle), Mathf.Sin(randomAngle); GameObject[] spawns = GetSpawns(VMP_EnemyStats.count); Debug.Log("Spawning " + spawns.Length + " enemies"); // Log the count of enemies being spawned foreach (GameObject o in spawns) { float currentSpawnDistance = spawnDistance + Random.Range(-spawnRadius, spawnRadius); Vector3 spawnPosition = player.transform.position + ((Vector3)direction * currentSpawnDistance); Instantiate(o, spawnPosition, Quaternion.identity); } return true; } return false; }
5. Check Enemy Movement
Ensure that your
VMP_ChargingEnemy
class is correctly moving the enemies towards the player. TheMove
method should be called in your enemy's update cycle.6. Confirm Event Triggering
Finally, confirm that your mob event is actually being added to the
runningEvents
list and that the cooldown for existing events is being managed correctly. If thecooldown
is not resetting or the event is removed too quickly, it may not trigger again.Conclusion
By adding debug logs and checking the flow of your event management, you should be able to identify where things are going wrong. If you still encounter issues, please share the output of your debug logs for further assistance.
Let me know if you need any more help!
has upvoted this post. August 19, 2025 at 4:26 am #18704::Hello,
I finaly found the solution (with Gemini, honnestly I didn’t read the read his response)
I change the Active After to 0 and I change my Event Manager.
Here is the new Event Manager :
using System.Collections; using System.Collections.Generic; using UnityEngine; public class VMP_EventManager : MonoBehaviour { public static VMP_EventManager instance; float currentEventCooldown = 0; public VMP_EventData[] events; [Tooltip("How long to wait before this becomes active.")] public float firstTriggerDelay = 180f; [Tooltip("How long to wait between each event. ")] public float triggerInterval = 30f; [System.Serializable] public class Event { public VMP_EventData data; public float duration, cooldown = 0; } List<Event> runningEvents = new(); // These are events that have been activated, and are running. VMP_PlayerStats[] allPlayers; void Start() { if (instance) Debug.LogWarning("There is more than 1 Event Manager in the Scene! Please remove the extras."); instance = this; currentEventCooldown = firstTriggerDelay > 0 ? firstTriggerDelay : triggerInterval; allPlayers = FindObjectsByType<VMP_PlayerStats>(FindObjectsSortMode.None); } void Update() { // Cooldown for adding another event to the slate. currentEventCooldown -= Time.deltaTime; if (currentEventCooldown <= 0) { // Get an event and try to execute it. VMP_EventData e = GetRandomEvent(); if (e && e.CanHappen(allPlayers[Random.Range(0, allPlayers.Length)])) runningEvents.Add(new Event { data = e, duration = e.duration }); // Set the cooldown for the event. currentEventCooldown = triggerInterval; } // Events that we want to remove. List<Event> toRemove = new(); // Cooldown for existing event to see if they should continue running. foreach (Event e in runningEvents) { // Reduce the current duration. e.duration -= Time.deltaTime; if (e.duration <= 0) { toRemove.Add(e); continue; } // Reduce the current cooldown. e.cooldown -= Time.deltaTime; if (e.cooldown <= 0) { // Pick a random player to sic this mob on, // then reset the cooldown. e.data.Activate(allPlayers[Random.Range(0, allPlayers.Length)]); e.cooldown = e.data.GetSpawnInterval(); } } // Remove all the events that have expired. foreach (Event e in toRemove) runningEvents.Remove(e); } public VMP_EventData GetRandomEvent() { // If no events are assigned, don't return anything. if (events.Length <= 0) return null; // Create a new list to hold only the events that are currently active. List<VMP_EventData> possibleEvents = new(); // Populate the list with active events. foreach (VMP_EventData e in events) { if (e.IsActive()) { possibleEvents.Add(e); } } // If there are any active events, pick one at random. if (possibleEvents.Count > 0) { return possibleEvents[Random.Range(0, possibleEvents.Count)]; } // If no events are active, return null. return null; } }
has upvoted this post. August 19, 2025 at 4:30 am #18705::The key changes in this updated Event Manager are:
-
Setting
activeAfter
to 0 in the event data, which means events can start immediately. -
Modifying the
GetRandomEvent()
method to:- Create a new empty list
possibleEvents
- Populate it only with events that are currently active via
IsActive()
- Randomly select from this list of active events
- Create a new empty list
These changes ensure that:
- Events can start right from the beginning of the game
- Only currently active events are considered for random selection
- The event system has a more robust method of event selection
The solution looks correct and should resolve the previous issues with event triggering. The code follows the same logic as the original implementation but with improved event selection logic.
Would you like me to elaborate on any specific part of the implementation?
-
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: