Vampire-Act-Base/design/gdd/wave-manager-logic.md

274 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 先刷0sBrute 延迟 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 | 2040 | Spawn perimeter radius (LevelData config) |
| Angle | `angle` | float | 0360° | 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 | 0totalWaveEnemies | Cumulative count of enemies spawned in current wave |
| Killed this wave | `killedThisWave` | int | 0spawnedThisWave | 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. `isWaveCleared` requires 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 `OnEnemyDied` fires 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 `OnLevelComplete` immediately. 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 + SpawnDelay` exceeds 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 `OnLevelComplete` fires but game state is already transitioning**: Guard with `_levelCompleteFired` flag. 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 | 825s | No time to reassess — waves blur together | Dead air — player gets bored waiting |
| `ArenaRadius` | 30.0 | 2040 | Enemies spawn on top of player — no reaction time | Enemies take too long to reach player — no pressure |
| `PreWaveDelay` (per wave) | 0s | 03s | — | Delays wave start, creates dead air before fights |
| `SpawnDelay` (between groups) | 2s | 0.55s | All enemies arrive simultaneously — no staggered pressure | Groups arrive so far apart wave loses cohesion |
| **Swarm count per wave** | 812 | 420 | Not enough to justify AOE form switch | Frame rate and readability suffer |
| **Brute count per wave** | 13 | 15 | Brute not threatening — can be ignored | Multiple simultaneous heavy attacks unreadable |
| **Stalker count per wave** | 23 | 15 | Stalker not creating urgency | Stalker rush overlaps unreactable |
| **Total enemies per wave** | 816 | 430 | 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.