20 KiB
Wave Manager Logic
Status: In Design Author: SepComet + Claude Last Updated: 2026-04-27 Implements Pillar: 战场即信息 (The Field is Information), 形态即战术 (Form is Tactics)
Overview
Wave Manager Logic 是夜裔的遭遇战编排系统——控制每一波敌人的生成时机、类型构成、数量和波次推进节奏。它不是一个简单的"定时刷怪"计时器,而是战斗节奏的作曲家:它决定什么时候让玩家感到压力(混合编制波次)、什么时候给玩家喘息(波次间 10-20s 间隔)、以及什么时候考验玩家的形态切换决策(同一波内投放多种需要不同形态克制的敌人)。
玩家直接感受 Wave Manager 的输出——但不是通过 UI 数字,而是通过战场节奏。一波 Swarm 海从四面八方涌来就是"切雾形"的信号。一波 Brute+Stalker 混合编制就是在问玩家:"你先处理哪个?Brute 的弹反窗口还是 Stalker 的冲刺?"波次清空后的短暂沉默不是空白——那是玩家重新评估血能、调整位置、预判下一波构成的战略时刻。Wave Manager Logic 是"战场即信息"和"形态即战术"的编排引擎——它通过敌人构成设计来创造有意义的形态切换压力,而不是靠随机数。
Player Fantasy
Wave Manager Logic 服务于一个任何动作游戏玩家都熟悉但很少被命名的幻想:掌控节奏。当你清空一波敌人、战场突然安静的那 10 秒钟,你的心跳从"战斗模式"的高频切回战术思考——"我还有多少血能?下一波会是什么?我该站在哪里?"——这就是 Wave Manager 在发挥作用。它不是制造敌人,而是制造期待。
好的波次编排像一场 DJ 演出:它知道什么时候给你密集的低音(Swarm 海)、什么时候切入一个重拍让你展示技巧(Brute 弹反)、什么时候让所有声音同时响起制造混乱(混合编制)、以及什么时候全部静默给你喘息(波次间隔)。玩家不会说"这波次设计得真好"——他们会说"再来一波,我能打"。
参考 Vampire Survivors 的波次节奏——敌人密度波浪式增长,偶尔给一个"安全窗口"让你有空间操作——和 Hades 的遭遇战房间——每个房间是一场独立的微型战斗拼图。夜裔的 Wave Manager 将两者融合:波浪式密度 + 拼图式构成。每一波不只是"更多敌人",而是"不同的问题"。第一波是热身(纯 Swarm,教你切雾形),第三波是期中考试(Brute + Swarm 混合,考验你什么时候放弃 AOE 去做弹反),最后一波是终极考验(三种类型同时在场,要求完整的形态切换循环)。
Detailed Design
Core Rules
1. Wave Lifecycle
InterWave ──(delay elapsed)──→ WaveActive ──(all enemies dead)──→ InterWave
↑ │
└──────────────────── (next wave exists) ────────────────────────────┘
│
└── (no more waves) → LevelComplete
2. Spawning Rules
- 敌人从竞技场边界(perimeter)生成:以竞技场中心为圆心、固定半径 R 的圆周上随机取点
- 生成时敌人面向竞技场中心(朝向玩家方向)
- 同一
SpawnGroup内的敌人同时生成(同一帧),不同 SpawnGroup 之间有SpawnDelay间隔 - 生成后立即进入 Enemy AI 的 Idle 状态(玩家进入 DetectionRange 时进入 Chase)
3. Wave Tracking
- Wave Manager 订阅
EnemyAI.OnEnemyDied(enemyId, enemyType, killerForm)事件 - 维护
aliveEnemyCount— 当前波次中存活敌人数量 - 当
aliveEnemyCount == 0且所有 SpawnGroup 已生成完毕 → 波次清空 - 仅追踪由当前波次生成的敌人。不在波次中的敌人不计入
4. Wave Advancement
- 波次清空后立即进入 InterWave 状态,启动
InterWaveDelay计时器(10-20s,由 LevelData 配置) - 计时器结束 → 自动推进到下一波
- 无需玩家交互 — 节奏完全由设计者控制
- 如果是最后一波 → 发射
OnLevelComplete,不再进入 InterWave
5. Level Completion
- 最后一波的所有敌人死亡 →
OnLevelComplete发射 - 此时所有敌人已清除,关卡结束
- 后续流程(结算、菜单、下一关)由 L1/L2 处理
Wave Structure
WaveData 定义:
WaveData {
int WaveIndex; // 0-based wave number
string WaveName; // Designer label (e.g. "Swarm Intro")
List<WaveSpawnGroup> SpawnGroups; // Groups spawned this wave
float PreWaveDelay; // Additional delay before first spawn (default 0)
bool IsFinalWave; // Marks level completion
}
WaveSpawnGroup 定义:
WaveSpawnGroup {
EnemyType Type; // Swarm(1) / Brute(2) / Stalker(3)
int Count; // How many enemies of this type
float SpawnDelay; // Seconds after wave start to spawn this group
SpawnPattern Pattern; // Perimeter / Clustered / Opposite / Random
}
SpawnPattern 枚举:
| Pattern | Behavior | When to Use |
|---|---|---|
Perimeter |
随机分布在竞技场边界圆周上 | 标准包围感,适合 Swarm |
Clustered |
集中在圆周的一个 60° 弧段内 | 紧凑编队,适合 Brute 群 |
Opposite |
分布在两个相对的 60° 弧段 | 夹击压力,适合 Stalker 编队 |
Random |
完全随机分布在整个边界上 | 混合编制默认 |
MVP Level: "First Night" 波次设计
7 波,教学曲线从单一类型引入到全类型混合:
| Wave | Name | Composition | Design Intent |
|---|---|---|---|
| 1 | Awakening | 8× Swarm (Perimeter) | 雾形教学 — 圆形 AOE 清屏。玩家学会"看到很多小敌人→切雾形"。 |
| 2 | The Pack | 12× Swarm (Perimeter, 2 groups staggered 1s) | 雾形巩固 — 更多数量,分两批生成创造持续压力。 |
| 3 | Heavy | 2× Brute (Clustered) | 弹反教学 — 两个 Brute 先后攻击,玩家学会"看到大块头→切人形弹反"。SpawnDelay=2s 确保不会同时攻击。 |
| 4 | Swarm + Brute | 8× Swarm (Perimeter) + 2× Brute (Opposite) | 首次混合 — 玩家被迫在 AOE 清小怪和弹反大怪之间切换。Swarm 先刷(0s),Brute 延迟 3s 刷。 |
| 5 | Hunters | 3× Stalker (Opposite, 2 groups staggered 0.5s) | 冲刺教学 — 首次引入 Stalker,玩家学会"看到高速冲刺→切狼形对撞"。 |
| 6 | Full House | 6× Swarm (Perimeter) + 1× Brute (Clustered) + 2× Stalker (Opposite) | 全类型混合 — 三种形态循环的完整考验。生成顺序:Swarm(0s) → Stalker(2s) → Brute(4s)。 |
| 7 | Final Stand | 10× Swarm (Perimeter) + 3× Brute (Random) + 3× Stalker (Random) | 最终波 — 高密度全类型。所有 SpawnGroup 同时生成(SpawnDelay=0),制造最大混乱和最大切换压力。 |
设计原则:
- 每种敌人类型的前两次出现都是"纯编制"(只有该类型),给予玩家学习空间
- 混合编制在玩家掌握基础后引入(Wave 4+)
- 生成顺序创造"先处理A再处理B"的引导:先刷的敌人先到达玩家
- SpawnDelay 控制敌人到达玩家的时间差,防止所有敌人同时压上
Interactions with Other Systems
| System | Direction | Interface |
|---|---|---|
| Enemy AI Logic | Downstream | 生成敌人实例并注入 EnemyTypeConfig。调用 Enemy AI 的 SpawnEnemy(typeConfig, position, facingAngle) 工厂方法。 |
| Enemy AI Logic | Upstream | 订阅 OnEnemyDied(enemyId, enemyType, killerForm) — 追踪存活敌人数量,判断波次是否结束。 |
| Combat Logic | — | 无直接依赖。Wave Manager 不参与伤害判定。通过 Enemy AI 间接关联。 |
| UI/HUD (L2) | Downstream | 发射 OnWaveChanged(waveIndex, waveName) — HUD 显示当前波次信息。发射 OnInterWaveStarted(duration) — HUD 显示倒计时或"准备"提示。 |
| VFX Spawner (L1) | Downstream | 发射 OnWaveStarted(波次开始 VFX)、OnWaveCleared(波次清空 VFX)、OnEnemySpawned(敌人出现 VFX) |
| Audio Player (L1) | Downstream | 发射 OnWaveStarted(波次音效)、OnInterWaveStarted(喘气/心跳音效) |
| Level Loader (L1) | Upstream | Level Loader 加载关卡时提供 LevelData(包含所有 WaveData)。可选:提供竞技场边界参数(中心点、半径)用于生成位置计算。 |
| Score Calculator | Downstream | 发射 OnWaveCleared + 波次数据 — Score Calculator 可根据波次清空速度计算评分。 |
Formulas
Spawn Position Calculation
The spawn position formula is defined as:
spawnPos = arenaCenter + (cos(angle), 0, sin(angle)) × arenaRadius
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Arena center | arenaCenter |
Vector3 | any | Arena center point (from Level Loader) |
| Arena radius | arenaRadius |
float | 20–40 | Spawn perimeter radius (LevelData config) |
| Angle | angle |
float | 0–360° | Random angle for perimeter spawns. Clustered: 60° range from base angle. Opposite: two 60° ranges 180° apart. |
Output Range: Any point on the perimeter circle at arenaRadius from center.
For Clustered pattern: angle = baseAngle + random(-30°, +30°) where baseAngle is randomized per wave.
For Opposite pattern: half at baseAngle ± 30°, half at baseAngle + 180° ± 30°.
For Random pattern: angle = random(0, 360°).
Alive Enemy Count
The wave tracking formula is defined as:
aliveCount = spawnedThisWave - killedThisWave
Variables:
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Spawned this wave | spawnedThisWave |
int | 0–totalWaveEnemies | Cumulative count of enemies spawned in current wave |
| Killed this wave | killedThisWave |
int | 0–spawnedThisWave | Incremented on each OnEnemyDied for enemies belonging to this wave |
Output: 0 to totalWaveEnemies. Wave clears when aliveCount == 0 && allSpawnGroupsCompleted.
Wave Completion
The wave completion formula is defined as:
isWaveCleared = (aliveEnemyCount == 0) AND (allSpawnGroups have been spawned)
Output: Boolean. Once true, triggers transition to InterWave or LevelComplete.
Edge Cases
- If all enemies in a wave are killed before all SpawnGroups have spawned: Wave remains active.
isWaveClearedrequires both conditions — all enemies dead AND all groups spawned. Remaining SpawnGroups continue spawning on schedule. - If player dies during WaveActive: Wave state freezes. Death/Respawn Rules manages respawn. On respawn, Wave Manager resets the current wave to its initial state (all enemies despawned, wave restarts from InterWave → WaveActive).
- If deltaTime = 0 (game paused): All Wave Manager timers pause (InterWaveDelay, SpawnDelay). No spawning occurs. No state transitions.
- If a wave has 0 enemies (all SpawnGroups have Count=0): Wave immediately completes (aliveCount=0, all groups "spawned"). Transition directly to InterWave. Logged as a warning — likely a data error.
- If a wave has only one SpawnGroup: SpawnDelay is irrelevant — all enemies spawn simultaneously at wave start. No timer needed between groups.
- If
OnEnemyDiedfires for an enemy not spawned by this wave: Ignore the event. Wave Manager maintains a set of tracked enemy IDs. Unknown IDs are silently skipped. - If arenaRadius <= 0 (invalid LevelData): Log error. Use fallback radius = 20.0. Spawning continues with fallback.
- If LevelData has an empty Waves list: Log error, emit
OnLevelCompleteimmediately. No enemies, no gameplay — treat as degenerate level. - If
InterWaveDelay = 0: Wave advancement is instant — no breather between waves. Valid but not recommended for MVP. Minimum enforced by design: 5s (clamped at runtime). - If
PreWaveDelay + SpawnDelayexceeds practical limit (>30s): Log warning. Enemies not spawning for too long creates dead air. Designer should review timing. - If spawned position would place enemy inside arena geometry (e.g., pillar): MVP does not handle this — arenas are assumed to be clear circles. Enemy collision with geometry deferred to vertical slice.
- If
OnLevelCompletefires but game state is already transitioning: Guard with_levelCompleteFiredflag. Emit event exactly once. Duplicate calls are ignored.
Dependencies
| System | Layer | Type | Hard/Soft | Interface |
|---|---|---|---|---|
| Enemy AI Logic | L0 | Upstream/Downstream | Hard | Downstream: calls SpawnEnemy(typeConfig, position, facingAngle) to create enemies. Upstream: subscribes to OnEnemyDied(enemyId, enemyType, killerForm) to track alive count. Cannot function without Enemy AI. |
| Level Loader (L1) | L1 | Upstream | Hard | Provides LevelData (wave list, arena params, InterWaveDelay) on level start. Without it, Wave Manager has nothing to manage. |
| UI/HUD (L2) | L2 | Downstream | Soft | Emits OnWaveChanged, OnInterWaveStarted for HUD display. Gameplay works without HUD. |
| VFX Spawner (L1) | L1 | Downstream | Soft | Emits OnWaveStarted, OnWaveCleared, OnEnemySpawned for visual feedback. |
| Audio Player (L1) | L1 | Downstream | Soft | Emits OnWaveStarted, OnInterWaveStarted for audio cues. |
| Score Calculator | L0 | Downstream | Soft | Emits OnWaveCleared with wave index and clear time for scoring. |
| Death/Respawn Rules | L0 | Upstream | Soft | Receives OnPlayerDied to freeze/reset wave state. Soft because wave system could function without death handling (just loses state on death). |
Hard dependencies (system cannot function without): Enemy AI Logic, Level Loader.
Soft dependencies (enhanced by but works without): UI/HUD, VFX Spawner, Audio Player, Score Calculator, Death/Respawn Rules.
Tuning Knobs
| Parameter | Default | Safe Range | Too Low | Too High |
|---|---|---|---|---|
InterWaveDelay |
15s | 8–25s | No time to reassess — waves blur together | Dead air — player gets bored waiting |
ArenaRadius |
30.0 | 20–40 | Enemies spawn on top of player — no reaction time | Enemies take too long to reach player — no pressure |
PreWaveDelay (per wave) |
0s | 0–3s | — | Delays wave start, creates dead air before fights |
SpawnDelay (between groups) |
2s | 0.5–5s | All enemies arrive simultaneously — no staggered pressure | Groups arrive so far apart wave loses cohesion |
| Swarm count per wave | 8–12 | 4–20 | Not enough to justify AOE form switch | Frame rate and readability suffer |
| Brute count per wave | 1–3 | 1–5 | Brute not threatening — can be ignored | Multiple simultaneous heavy attacks unreadable |
| Stalker count per wave | 2–3 | 1–5 | Stalker not creating urgency | Stalker rush overlaps unreactable |
| Total enemies per wave | 8–16 | 4–30 | Wave feels empty — no density | 200+ total active enemies — performance risk |
Key tuning groups:
- Pacing rhythm:
InterWaveDelay+PreWaveDelay+SpawnDelay— the sum of delays determines the feel of "combat density vs breathing room" - Form pressure balance: Swarm count vs Brute count vs Stalker count per wave — if any type dominates, the wave only tests one form
- Performance ceiling: Total enemies alive simultaneously — Combat Logic budget is 200 enemies at <1ms. Total active across all waves must stay under this limit. At default counts (Wave 7: 16 total), performance is not a concern. Future tuning should profile before increasing.
Visual/Audio Requirements
Combat is a visual system category — Visual/Audio is REQUIRED.
| Event | VFX | Audio |
|---|---|---|
OnWaveStarted |
Arena perimeter pulse — brief geometric ring flash at spawn boundary. Color: white → fade. | Rising tone / alarm — pitch increases with wave number |
OnWaveCleared |
Brief screen flash + all remaining enemy fragments dissolve simultaneously | Bass drop + short silence before InterWave heartbeat |
OnEnemySpawned |
Small geometric "materialize" effect at spawn point — shape matches enemy type | Subtle whoosh — pitch varies by enemy size |
OnInterWaveStarted |
Screen edges dim slightly (vignette increase) — signals "safe moment" | Heartbeat / breathing SFX — tempo slows as InterWaveDelay nears end |
OnLevelComplete |
Full screen geometric pattern (like shattered glass reforming) — gold/white | Victory fanfare — short, geometric, not orchestral |
UI Requirements
| Element | Content | Trigger |
|---|---|---|
| Wave Banner | "Wave [N]: [WaveName]" — appears at wave start, fades after 2s | OnWaveChanged |
| InterWave Countdown | "[N]s until next wave" — centered, subtle | OnInterWaveStarted(duration) |
| Enemy Remaining Counter | "[N] enemies remaining" — small HUD element | Alive count update each time OnEnemyDied fires |
| Wave Clear Text | "Cleared!" — brief centered text | OnWaveCleared |
Acceptance Criteria
| # | GIVEN | WHEN | THEN |
|---|---|---|---|
| 1 | LevelData with 7 waves loaded. Wave 1 has 8× Swarm (Perimeter). InterWaveDelay=15s. | Level starts | Wave 1 spawns 8 Swarm at arena perimeter. OnWaveChanged fires with waveIndex=0. |
| 2 | Wave 1 active. 8 enemies alive. | Enemy AI emits OnEnemyDied for one Swarm | aliveCount=7. Wave remains active. |
| 3 | Wave 1 active. 8 enemies alive. All 8 OnEnemyDied events fire. | Last enemy dies | Wave clears. OnWaveCleared fires. Transition to InterWave. InterWaveDelay timer starts. |
| 4 | InterWave state. 15s elapsed. Next wave exists (Wave 2). | Timer expires | Transition to WaveActive. Wave 2 begins spawning. OnWaveChanged fires with waveIndex=1. |
| 5 | Wave 7 (IsFinalWave=true) active. All enemies dead. | Last enemy dies | OnWaveCleared fires. OnLevelComplete fires. No InterWave transition. |
| 6 | Wave with 2 SpawnGroups: Group1(Delay=0s), Group2(Delay=3s). Wave starts. | Wave becomes active | Group1 spawns immediately. 3s later, Group2 spawns. Both before any died events. |
| 7 | SpawnPattern=Clustered, baseAngle=90°. Count=4. ArenaRadius=30. ArenaCenter=(0,0,0). | Spawn executed | 4 enemies spawned within 60° arc centered on 90° (±30°). All at distance 30 from center. |
| 8 | deltaTime=0.0. InterWave state with 10s remaining on timer. | Update called | Timer does not advance. Still 10s remaining. |
| 9 | Player dies during WaveActive (Wave 3). Death/Respawn emits OnPlayerRespawned. | OnPlayerRespawned received | Wave 3 resets: all enemies despawned, wave restarts from InterWave → re-spawn all Wave 3 enemies. |
| 10 | OnEnemyDied fires for enemy ID not in tracked set (not spawned by this wave). | Event received | Ignored. aliveCount unchanged. |
| 11 | LevelData.Waves is empty list. | Level starts | Log error. OnLevelComplete fires immediately. |
| 12 | Wave 3 "Heavy": 2× Brute (Clustered, SpawnDelay=2s between them). | Wave becomes active | First Brute spawns at 0s. Second Brute spawns at 2s. Two separate spawn events. |
Open Questions
- Player death wave reset: Should the player restart the entire level, the current wave, or from a checkpoint (e.g., every 3 waves)? Defer to Death/Respawn Rules GDD.
- Wave difficulty scaling: Should enemy stats increase per wave (health/damage scaling) or is composition change alone sufficient for difficulty? MVP assumes composition-only scaling.
- Boss wave integration: How does Boss AI Logic GDD's boss encounter integrate into the wave system? Is the boss a special "Wave 8" or a separate encounter after Wave 7?
- Wave order randomization: Should the 7-wave structure be fixed (same every playthrough) or allow designer to tag waves as "random pool" for replayability? MVP uses fixed order.
- Spawn visual transition: Should enemies "phase in" with a brief materialize animation, or pop into existence? Enemy AI GDD says Idle state has no wake-up animation — spawning FX is a Wave Manager concern.