274 lines
20 KiB
Markdown
274 lines
20 KiB
Markdown
# 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. `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 | 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.
|