The Modular Wave System is a pair of wave managers with a clean C# API and a matching Game Creator 2 integration layer.
- Default Wave System (KingEdward.WaveSystem.Default): classic wave/stage progression, optional boss waves.
- Survivors‑Like Wave Syste (KingEdward.WaveSystem.SurvivorsLike): continuous spawning loop in a given duration (difficulty over time, elites, bosses).
- Core utilities (KingEdward.WaveSystem.Core): shared pieces (WaveCharacter, ObjectPool, SpawnArea, WaveSystemEffects).
- GC2 integration (KingEdward.WaveSystem.GC2Integration): Property Gets, Conditions, Triggers and one Instruction, under a separate namespace. Install via .unitypackage on Integrations folder.
You can drive everything directly from code, from GC2, or mix both.
Folder Structure
Core/
WaveCharacter.cs – generic character marker + death event.ObjectPool.cs – generic prefab pool, used by both managers.SpawnArea.cs – 3D spawn volume (sphere/box) for spawn positions.WaveSystemEffects.cs – shared VFX spawn/pool logic used by both managers.Default/
WaveManagerDefault.cs – classic wave manager.WaveData.cs – ScriptableObject describing each wave.SurvivorsLike/
WaveManagerSurvivors.cs – Survivors‑like manager.GameConfiguration.cs – global settings & difficulty curve.EnemySpawnData.cs – enemy entries for Survivors.BossData.cs – boss entries for Survivors.UI/
WaveDefaultUI.cs – HUD for Default manager.WaveSurvivorsUI.cs – HUD for Survivors manager.Editor/
WaveData and GameConfiguration.Gizmos/ – icons for all components and data assets.GC2Integration/Runtime/Visual Scripting/
Instructions/ – InstructionWaveCharacterDie.Conditions/ – default + survivors state checkers.Properties/ – Property Gets for both systems.Triggers/ – Events for all wave/game/boss/enemy signals.Icons/ – GC2 icon classes generated from the gizmos.Core Concepts
WaveCharacter (Core)
Namespace: KingEdward.WaveSystem.Core
Attach this to any GameObject that should participate in the wave systems (player or enemy).
IsPlayer / IsEnemy (read‑only).IsDead (read‑only).MarkAsPlayer() – marks this character as the player.MarkAsEnemy() – marks this character as an enemy.Die() – sets IsDead = true and fires OnDeath once.event Action<WaveCharacter> OnDeathHow to use from your own health/damage code:
using KingEdward.WaveSystem.Core;
public class EnemyHealth : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int currentHealth;
private WaveCharacter waveCharacter;
private void Awake()
{
currentHealth = maxHealth;
waveCharacter = GetComponent<WaveCharacter>();
if (waveCharacter == null)
{
waveCharacter = gameObject.AddComponent<WaveCharacter>();
waveCharacter.MarkAsEnemy();
}
}
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0 && waveCharacter != null)
{
waveCharacter.Die();
}
}
}
Both wave managers subscribe to
WaveCharacter.OnDeathon spawned enemies, so your only job is to callDie()when the enemy actually dies.
ObjectPool (Core)
Simple prefab pooling used by both managers to avoid excessive Instantiate/Destroy.
Main methods:
CreatePool(GameObject prefab, int initialSize, int maxSize) – create or ensure a pool exists (e.g. at prewarm).GameObject GetFromPool(GameObject prefab) – get from pool; creates pool with default sizes (5, 50) if missing.GameObject GetFromPool(GameObject prefab, int initialSize, int maxSize) – get from pool; if the pool does not exist, creates it with the given sizes (used when not prewarming).ReturnToPool(GameObject instance) – return an instance to its pool.Both managers handle pooling automatically; you typically do not need to call these yourself unless you want manual control elsewhere in your project.
SpawnArea (Core)
Namespace: KingEdward.WaveSystem.Core
Component that defines a 3D spawn volume for enemies. Can be a sphere or box, with visual gizmos similar to Unity's Colliders.
- Volume Types:
Sphere: spawns within a spherical volume with configurable radius.Box: spawns within a box volume with configurable size (X, Y, Z).- Methods:
GetRandomPosition() – returns a random position inside the 3D volume.GetRandomPositionInCircle(float minRadius) – returns a random position in a circle within the volume (for spawn patterns).GetRandomPositionInLine(float lineOffsetRange) – returns a random position along a line within the volume (for spawn patterns).Contains(Vector3 position) – checks if a position is inside the volume.- Gizmos: The volume is visualized in the Scene view with colored wireframe (semi-transparent when not selected, more opaque when selected). Uses Unity's Handles for better visual quality.
- Settings:
Gizmo Color: customize the gizmo color.Show Gizmo: toggle gizmo visibility.How to use:
SpawnArea component.Sphere or Box volume type.Radius (for sphere) or Size (for box - X, Y, Z dimensions).SpawnArea to the manager's Spawn Areas array.You can have multiple SpawnArea components in your scene; the manager will randomly pick one for each spawn. The volumes respect the GameObject's transform (position, rotation, scale).
Default Wave System
Components & Data
WaveManagerDefault (MonoBehaviour)
KingEdward.WaveSystem.DefaultWaveData (ScriptableObject)
KingEdward.WaveSystem.DefaultKingEdward/Wave System/Wave DataWaveData – configuring waves
Each WaveData asset defines a single wave:
- Fields
waveName (string) – used by UI.waveIcon (Sprite) – optional icon for UI.wavePattern (SpawnPattern) – default pattern for the wave:
Random – random spawn points, respecting min distance to player.Circle – around a spawn point, in a ring.CircleAroundPlayer – around the player, at a ring distance.Line – along a line given by spawn point right vector.Grid, Star, VFormation – formation patterns (use formationSpacing on enemy groups).isBossWave (bool) – if true, this wave uses boss spawn points.enemyGroups (list of EnemyGroup):
enemyPrefab: GameObject to spawn.count: number of enemies.spawnDelay: delay between each spawn in the group.spawnPattern: optional override (per group) of wavePattern.For boss waves, typically you add a single
EnemyGroupwith the boss prefab andcount = 1, and markisBossWave = true.
WaveManagerDefault – scene setup
1. Create a GameObject in your scene, e.g. Wave Manager Default.
2. Add the WaveManagerDefault component.
3. Referencing the manager: Prefer assigning the manager by reference in the inspector (e.g. on WaveDefaultUI, or on your own scripts that need to read state or subscribe to events). You can have multiple managers in the same scene (e.g. one per arena); each UI or script should reference the manager it uses. A static Instance is still set to the first active manager at runtime for backward compatibility and simple single-manager setups; when in doubt, use an explicit reference.
4. Assign fields:
Waves: list of WaveData assets in the order you want.Time Before First Wave: delay before first wave starts.Time Between Waves: delay between waves.Delay After Boss Defeat: delay before continuing after a boss.Spawn Areas: array of SpawnArea components used for normal enemy spawns.Boss Spawn Areas (optional): if set, boss waves will prefer these areas.WaveCharacter in the scene with IsPlayer = true.tag (“Player” by default).Use Object Pooling: enable to use ObjectPool automatically.Prewarm Pools: if true, pools are created at startup; if false, created on first spawn.Pool Initial Size, Pool Max Size – used for prefabs that appear only in normal waves.Boss Pool Initial Size, Boss Pool Max Size – used for prefabs that appear in waves marked as boss waves (isBossWave). Each prefab gets a single pool; if it appears in both normal and boss waves, boss sizes are used.The custom inspector groups these settings under Setup / Player / Performance with foldouts and warnings for missing waves or spawn areas.
Runtime API (code)
Get the manager by reference (assign in the inspector on your MonoBehaviour) or, in code-only cases, via the static Instance (first active manager). Prefer reference when you have multiple managers or want clear wiring.
using KingEdward.WaveSystem.Default;
// Option A: reference assigned in inspector (recommended)
[SerializeField] private WaveManagerDefault waveManager;
void SomeMethod()
{
if (waveManager != null && waveManager.IsGameActive)
Debug.Log($"Wave {waveManager.CurrentWave}/{waveManager.TotalWaves}, enemies left: {waveManager.EnemiesRemaining}");
}
// Option B: single-manager scene, no reference
var mgr = WaveManagerDefault.Instance;
if (mgr != null && mgr.IsGameActive)
Debug.Log($"Wave {mgr.CurrentWave}/{mgr.TotalWaves}");
- Read‑only properties
CurrentWave (1‑based index)EnemiesRemainingIsWaveActive, IsGameActive, IsBossWaveRemainingWaves, TotalWavesPlayer (GameObject)CurrentWaveName, CurrentWaveIcon- Control methods
StartGame() – starts the wave sequence.StartNextWave() – forces the next wave immediately.PauseGame() / ResumeGame() – toggles Time.timeScale and IsGameActive.- Events
event WaveEvent OnWaveStart – int waveNumberevent WaveEvent OnWaveClearevent WaveEvent OnStageClearevent WaveEvent OnBossDefeatedevent EnemyDeathEvent OnEnemyDeath – GameObject enemyExample: custom reward on boss death
using KingEdward.WaveSystem.Default;
public class BossReward : MonoBehaviour
{
[SerializeField] private WaveManagerDefault waveManager;
private void OnEnable()
{
if (waveManager == null) waveManager = WaveManagerDefault.Instance;
if (waveManager != null)
waveManager.OnBossDefeated += HandleBossDefeated;
}
private void OnDisable()
{
if (waveManager != null)
waveManager.OnBossDefeated -= HandleBossDefeated;
}
private void HandleBossDefeated(int waveIndex)
{
// Grant loot, XP, play cutscene, etc.
}
}
Default UI
WaveDefaultUI is a simple HUD component:
WaveManagerDefault.Instance at runtime (single-manager scenes).Text and Image references (wave count, enemies remaining, wave name, icon).Survivors‑Like Wave System
Components & Data
WaveManagerSurvivors (MonoBehaviour)
KingEdward.WaveSystem.SurvivorsLikeGameConfiguration (ScriptableObject)
KingEdward/Wave System/Game Configuration.EnemySpawnData (Serializable class)
BossData (Serializable class)
GameConfiguration
- Game Settings
gameNamegameDuration – total game time in seconds.timeToFirstSpawn – delay before first enemy appears.
- DifficultydifficultyCurve – controls overall difficulty \(0–1 progression\).baseSpawnRate, maxSpawnRate – enemies per second range.eliteSpawnChanceBase, eliteSpawnChanceMax, eliteSpawnCurve – chance of elites over progress.
- Enemies & BossesenemyTypes – list of EnemySpawnData.bosses – list of BossData (spawned periodically).
- Spawn SettingsminDistanceFromPlayer / maxDistanceFromPlayer – ring region around player.bossSpawnMode (FixedInterval, ByProgress, ByBossSpawnTime), bossInterval; each BossData can set spawnAtProgress or spawnAtTime when using progress/time modes.Helper methods (used internally but also safe to call):
GetDifficultyAtTime(float progress)GetSpawnRateAtTime(float progress)GetEliteSpawnChance(float progress)EnemySpawnData
enemyPrefab (GameObject)enemyName (string) – for your own UI/logs.spawnWeight – weighted random selection based on difficulty.minDifficultyToSpawn – minimum difficulty before this enemy is included.isElite – marks this entry as elite.usePooling – if true, this enemy uses the manager’s enemy pool (when Use Object Pooling is on).spawnEffect, deathEffect (GameObject) – optional VFX.spawnEffectUsePooling, deathEffectUsePooling – pool VFX when enabled.spawnSound, deathSound (AudioClip) – optional SFX.BossData
bossPrefab (GameObject)bossName (string)bossIcon (Sprite) – used by UI.spawnOnce – if true, boss is only spawned once in the sequence.spawnEffect, deathEffectspawnSound, deathSoundWaveManagerSurvivors – scene setup
1. Create a GameObject Wave Manager Survivors.
2. Add WaveManagerSurvivors.
3. Referencing the manager: Assign the manager by reference on WaveSurvivorsUI or your scripts when possible. If left empty, the UI uses WaveManagerSurvivors.Instance (first active manager). Prefer explicit reference if you have multiple managers.
4. Assign:
Game Config: a GameConfiguration asset.Spawn Areas: optional array of SpawnArea components. If set, enemies spawn within these areas. If not set, spawns in a circle around the player.Max Enemies Alive: cap for simultaneous enemies.Enemy Cleanup Interval: how often the manager cleans far/dead enemies.Use Object Pooling is on):
Prewarm Pools: create pools at startup (true) or on first use (false).Pool Initial Size, Pool Max Size – per enemy prefab (only for enemies with pooling enabled in EnemySpawnData).VFX Pool Initial Size, VFX Pool Max Size – for spawn/death effects that use pooling.Boss Pool Initial Size, Boss Pool Max Size – separate pool sizes for boss prefabs.WaveCharacter or falls back to tag "Player".The manager automatically:
isElite).currentBossIndex.maxDistanceFromPlayer) without counting as kills.Runtime API (code)
Get the manager by reference (assign in inspector) or via WaveManagerSurvivors.Instance in single-manager setups:
using KingEdward.WaveSystem.SurvivorsLike;
[SerializeField] private WaveManagerSurvivors waveManager;
void SomeMethod()
{
if (waveManager == null) waveManager = WaveManagerSurvivors.Instance;
if (waveManager != null && waveManager.IsGameActive)
Debug.Log($"Time: {waveManager.GameTime:0.0}s | Difficulty: {waveManager.CurrentDifficulty:0.00} | Enemies: {waveManager.EnemiesAlive}");
}
- Read‑only properties
GameTimeGameProgress (0–1)CurrentDifficultyEnemiesAliveIsGameActiveIsBossActiveTimeRemainingPlayerCurrentBossName, CurrentBossIcon- Control methods
StartGame() – starts the run.PauseGame(), ResumeGame()- Events
event GameEvent OnGameStartevent GameEvent OnGameEndevent GameEvent OnBossSpawnevent KillEvent OnEnemyKilledevent KillEvent OnEliteKilledevent KillEvent OnBossKilledExample: custom power‑up logic
using KingEdward.WaveSystem.SurvivorsLike;
public class SurvivorsPowerUpController : MonoBehaviour
{
[SerializeField] private WaveManagerSurvivors waveManager;
private void OnEnable()
{
if (waveManager == null) waveManager = WaveManagerSurvivors.Instance;
if (waveManager == null) return;
waveManager.OnEnemyKilled += OnEnemyKilled;
waveManager.OnEliteKilled += OnEliteKilled;
waveManager.OnBossKilled += OnBossKilled;
}
private void OnDisable()
{
if (waveManager == null) return;
waveManager.OnEnemyKilled -= OnEnemyKilled;
waveManager.OnEliteKilled -= OnEliteKilled;
waveManager.OnBossKilled -= OnBossKilled;
}
private void OnEnemyKilled(GameObject enemy) { /* base XP, coins, etc. */ }
private void OnEliteKilled(GameObject enemy) { /* stronger rewards */ }
private void OnBossKilled(GameObject boss) { /* big chest, relics, etc. */ }
}
WaveSurvivorsUI provides a minimal HUD:
WaveManagerSurvivors.Instance.timeDisplayMode), enemies alive, current difficulty, and a boss panel (name + icon) while a boss is active.Attach it to a Canvas and assign the Text/Image references.
Game Creator 2 Integration
All GC2 integration lives under:
KingEdward/WaveSystem/GC2Integration/Runtime/Visual Scripting
Instruction
InstructionWaveCharacterDie
Category: KingEdward/Wave System/Kill Wave Character
Purpose: marks any WaveCharacter as dead (calls Die()).
Usage:
Target to:
WaveCharacter.OnDeath event, which both managers interpret as that enemy dying.Conditions
Two condition types, each with Default and Survivors flavours:
ConditionWaveSystemStateDefault (Wave System State (Default))
Category: Wave System/Conditions
States:
IsWaveActiveIsGameActiveIsBossWaveHasEnemiesRemainingConditionWaveSystemStateSurvivorsLike (Wave System State (SurvivorsLike))
Category: Wave System/Conditions
States:
IsGameActiveIsBossActiveHasEnemiesAliveExample: Only allow a door to open if the current wave is cleared:
On InteractWave System State (Default) → HasEnemiesRemaining = falseProperty Gets
All under KingEdward/Wave System/ categories.
Default
GetBoolWavePropertyDefault – Boolean Values (Default)
IsWaveActiveIsGameActiveIsBossWaveGetDecimalWavePropertyDefault – Numeric Values (Default)
CurrentWaveEnemiesRemainingRemainingWavesTotalWavesSurvivors‑Like
GetBoolWavePropertySurvivorsLike – Boolean Values (SurvivorsLike)
IsGameActiveIsBossActiveGetDecimalWavePropertySurvivorsLike – Numeric Values (SurvivorsLike)
GameTimeGameProgressCurrentDifficultyEnemiesAliveTimeRemainingExample: show wave number in a GC2 UI Text
Wave System/Numeric Values (Default) → CurrentWave.Triggers (Events)
All triggers derive from GameCreator.Runtime.VisualScripting.Event and are in the Triggers folder.
Default events
On Wave Start (Default)On Wave Clear (Default)On Stage Clear (Default)On Boss Killed (Default) (boss defeat)On Enemy Killed (Default) (any enemy death)All listen to the corresponding WaveManagerDefault C# events.
Survivors‑Like events
On Game Start (SurvivorsLike)On Game End (SurvivorsLike)On Boss Spawn (SurvivorsLike)On Enemy Killed (SurvivorsLike)On Elite Killed (SurvivorsLike)On Boss Killed (SurvivorsLike)All listen to WaveManagerSurvivors events.
Example: GC2 power‑up drop on elite kill
Wave System/On Elite Killed (SurvivorsLike).Args with the killed elite as the Trigger’s target (depending on your GC2 wiring).Best Practices & Notes
Manager access: Prefer assigning the wave manager by reference in the inspector (on UI components and your own scripts). Use the static Instance only when you have a single manager and no convenient reference (e.g. from a prefab or another scene). This keeps multi-manager and multi-arena setups clear.
Always make sure your player either:
WaveCharacter marked as player; orWaveCharacter automatically.For enemies & bosses, your health scripts should be the only place that calls WaveCharacter.Die().
HandleEnemyDeath / HandleBossDeath manually.Use the custom inspectors under Editor/ to configure assets; they include warnings for common misconfigurations (no waves, no spawn points, invalid distances, etc.).
If you need to extend the system (new spawn patterns, custom difficulty, additional GC2 properties), prefer:
Extending WaveData.SpawnPattern or the internal spawn methods, preserving the existing enum values.
Adding new methods/properties to the managers and then exposing them via new GC2 Property Types, following the existing naming and icon pattern.
📞 Support
🔄 Changelog
Version 1.0.0