# Boss 阶段 + 弹幕/位移攻击模式系统 — 设计

> **日期**：2026-07-22
> **切片**：S6（Boss 深度）
> **前置状态**：Mana 系统 + 元进展已完成；Boss 目前只是 EnemyManager SoA 中 `type=4`（Mini/W8）/`type=5`（Final/W20）的厚血实体，唯一"阶段行为"是 66%/33% 跨阈值召唤 FAST 援军（`wave_manager.gd`）。本设计将其升级为数据驱动的多阶段状态机 + 攻击模式调度 + 敌方子弹子系统。
> **依赖**：EnemyManager、PlayerManager、PlayerStats、EventBus、VFXManager、SettingsManager、SpatialGrid、game_designer 编辑器插件。

---

## 0. 范围

**范围内**：
- 数据驱动的 Boss 多阶段状态机（血量阈值切换）。
- 攻击模式注册表：瞬时远程弹幕（环/扇/瞄准/旋转）+ 位移/蓄力/近战招式（冲刺/蓄力重击/近身劈砍），带三段式生命周期与前摇预警。
- 新增 `EnemyBulletManager` 敌方子弹子系统（伤害玩家）。
- 保留现有"进阶召唤援军"手感（作为每阶段可选字段）。
- 补齐专用事件 `BOSS_PHASE_CHANGED` / `BOSS_KILLED`。
- `data/bosses.json` + 设计器新增「Boss」分页（字段化、双语、行编辑器）。
- 敌方子弹独立 MultiMesh 渲染层。

**范围外（延后）**：
- 场地机制（冰墙障碍、场地收缩）。
- 通关演出 / `GAME_CLEARED` 事件 / Boss 死亡序列演出。
- 玩家无敌帧系统（本轮敌方子弹单次命中即销毁，靠数值调低保证公平）。
- `.tres` 资源（本工程纯 JSON，不引入）。
- C# 热路径（沿用 GDScript，参照 R-08）。

---

## 1. 新增自动加载 `EnemyBulletManager`（敌方子弹）

平行于 `BulletManager` 的**独立 SoA 池**，不污染玩家子弹热路径（玩家子弹检测 vs 敌人 SpatialGrid；敌方子弹检测 vs 玩家）。

- 文件：`scripts/autoloads/enemy_bullet_manager.gd`（+ `.uid`），注册进 `project.godot` autoload。
- SoA `PackedFloat32Array`，命名常量 `ENEMY_BULLET_STRIDE = 7`：
  `[ x, y, vx, vy, lifetime, radius, damage ]`（索引 0..6）。
- `const MAX_ENEMY_BULLETS: int = 1024`；`_ready` 中 `resize(MAX_ENEMY_BULLETS * ENEMY_BULLET_STRIDE)` + `fill(0.0)`（P6-N3：禁止热循环内 `new()`）。
- `PLAYER_HIT_RADIUS`：玩家碰撞半径常量（估算 12px，与玩家标记视觉一致）。
- `_physics_process(delta)`：
  - 逐弹积分：`x += vx*dt`、`y += vy*dt`、`lifetime -= dt`。
  - `lifetime <= 0` → `_swap_and_pop`。
  - 否则对玩家碰撞：`d² ≤ (radius + PLAYER_HIT_RADIUS)²`（读 `PlayerManager.get_position()`）→ `PlayerStats.take_damage(damage)`（走难度减伤）+ `VFXManager.play("hit_spark", pos)` + `_swap_and_pop`（单次命中即销毁）。
- 接口：
  - `spawn_bullet(pos: Vector2, vel: Vector2, lifetime: float, radius: float, damage: float) -> int`（满容返回 -1）。
  - `sync_multimesh(mm: MultiMesh)`（SoA 位置批量写入，`visible_instance_count = active`，与 BulletManager 同法）。
  - `get_active_count() -> int`、`reset()`。
- `combat_manager` 在 `start_game` / `close_shop_and_next_wave` / `_on_player_died` 中 `EnemyBulletManager.reset()`（与 bullet/zone/minion 重置一致）。

---

## 2. 新增自动加载 `BossManager`（阶段 + 招式侧挂控制器）

Boss 实体仍留在 EnemyManager SoA（渲染/受击/HP 不变）；`BossManager` 用侧 `Dictionary` 按 `boss_id` 存运行态，**并接管 Boss 的移动**。

- 文件：`scripts/autoloads/boss_manager.gd`（+ `.uid`），注册进 `project.godot` autoload。
- 运行态（每个活跃 Boss 一条）：
  ```
  {
    boss_type: int,
    max_hp: float,
    phase: int,                 # 当前阶段（0 起）
    phase_defs: Array,          # 该 boss_type 的阶段配置（来自 bosses.json）
    pattern_index: int,         # 当前阶段招式轮询索引
    cooldown: float,            # 距下次起手的冷却
    action: Dictionary,         # 当前进行中的招式状态（见 §3），空 = 无
  }
  ```
- `register_boss(boss_id, boss_type, max_hp)`：`wave_manager._spawn_boss` 生成 SoA Boss 后调用；初始化运行态、置 `phase=0` 并触发 phase 0 进入。
- `_ready` 载入 `data/bosses.json`（缺失/格式错 `push_error`，纯数据驱动无回退）；订阅 `EventID.ENEMY_KILLED`。
- `_physics_process(delta)`（有活跃 Boss 才进入循环体）：对每个 Boss —
  1. `_check_phase_transition`：读 `EnemyManager.get_hp_percent(id)`，按 `phase_defs`（`hp_threshold` 降序）检查是否跨入更深阶段；跨入 → 进阶（重置 `pattern_index/cooldown`、可选召唤援军、发 `BOSS_PHASE_CHANGED`）。
  2. `_tick_movement`：无进行中位移招式时，Boss 朝玩家缓慢漂移（`move_speed * phase.move_speed_mult`），经 `EnemyManager.set_entity_pos` 写回 SoA。
  3. `_tick_action`：推进当前招式（§3）；无招式且 `cooldown <= 0` → 选下一招式并启动。
- `_on_enemy_killed`：`killed_id` 是注册 Boss → 发 `BOSS_KILLED {boss_id, boss_type, wave}` 并注销运行态。
- `reset()`：清空运行态；由 `combat_manager` 在 run 重置点调用。

**EnemyManager 改动**：
- 新增 `set_entity_pos(id, pos)`（写 SoA x/y）。
- `_gd_update_movement` **跳过已注册 Boss 的 slot**（BossManager 全权驱动其移动）——通过 EnemyManager 侧的一个「外部驱动」id 集合（`register_external_mover(id)` / `unregister_external_mover(id)`，类比精英寻路跳过），零额外开销（无 Boss 时集合空）。

**wave_manager 改动**：
- `_spawn_boss` 末尾调用 `BossManager.register_boss(...)` + `EnemyManager.register_external_mover(_boss_id)`。
- **移除** `_update_boss_phase` / `_on_boss_phase_enter`（阶段与召唤逻辑迁入 BossManager）。
- 保留 `_boss_id` / `_boss_wave` 及 `killed_id == _boss_id` 的**波次完成判定**（不变）。

---

## 3. 招式模型：带生命周期的动作

每阶段 `attack_patterns` 是招式规格列表，BossManager 轮询启动。招式分两类，均含通用字段 `kind` 与 `cooldown`（招式结束后进入的冷却秒数）。

### 3.A 瞬时远程弹幕（发一波即结束，Boss 期间继续漂移）

经 `EnemyBulletManager.spawn_bullet` 从 Boss 位置发射：

| kind | 行为 | 主要参数 |
| :--- | :--- | :--- |
| `ring` | 绕 360° 均匀 N 弹（开花/环） | `count, speed, bullet_damage, bullet_radius` |
| `spread` | 朝玩家方向 ±`half_angle` 的 N 弹扇形 | `count, half_angle, speed, bullet_damage, bullet_radius` |
| `aimed` | 瞄准玩家的单发/连发 | `count, speed, bullet_damage, bullet_radius`（连发用 burst 间隔可后续扩展） |
| `spiral` | 每次起手角度递增的旋转弹 | `count, speed, angle_step, bullet_damage, bullet_radius` |

瞬时招式的 `action` 生命周期只占一帧（发射后立即进冷却）。

### 3.B 位移 / 蓄力招式（跨多帧，三段式，接管 Boss 移动）

三段式：`windup 前摇 → active 生效 → recover 后摇`。`action` 状态含 `{ kind, stage, timer, ... }`，逐帧推进。

| kind | 行为 | 主要参数 |
| :--- | :--- | :--- |
| `dash`（冲刺） | 前摇：静立 + 播**预警 VFX**（给玩家闪避窗口）；生效：以 `dash_speed` 冲向前摇锁定的玩家位置，冲刺中与玩家重叠 → `PlayerStats.take_damage(contact_damage)`（带内部命中冷却 `contact_hit_cd` 防每帧多段）；后摇：静立恢复。 | `windup, dash_speed, dash_time, contact_damage, contact_hit_cd, recover, telegraph_vfx` |
| `charge_up`（蓄力） | 前摇：静立蓄力 + 播预警 VFX；生效：释放一次**重击**（大 `ring` 或多重 `spread`，由 `release_kind` + 弹幕参数决定）；后摇：静立恢复。 | `windup, release_kind, count, speed, half_angle, bullet_damage, bullet_radius, recover, telegraph_vfx` |
| `melee`（近战/劈砍） | **原地近身范围打击**（区别于 `dash` 的横穿冲撞）。前摇：静立蓄势 + 播预警 VFX（预示打击范围）；生效：判定玩家是否在 `melee_radius` 内 → `PlayerStats.take_damage(melee_damage)`（该次生效仅判定一次）；后摇：静立恢复。 | `windup, melee_radius, melee_damage, recover, telegraph_vfx` |

**公平性约束**：所有位移/蓄力招式**必须**有 `windup` 前摇与 `telegraph_vfx` 预警，否则冲刺不可闪避。

### 招式注册表实现

`BossManager` 内置 `pattern_id/kind → 处理逻辑`（行为在代码，调参在 JSON，类比 status/spell）。瞬时招式为「发射函数」；位移/蓄力招式为「阶段推进器」。不引入独立 Resource / 注册 autoload，逻辑内聚在 `BossManager`（招式数量少，MVP 阶段）。

---

## 4. 数据与设计器

### 4.1 `data/bosses.json`

```json
{
  "4": {
    "move_speed": 120.0,
    "phases": [
      {
        "hp_threshold": 1.0,
        "move_speed_mult": 1.0,
        "summon_adds": 0,
        "attack_patterns": [
          { "kind": "spread", "count": 5, "half_angle": 40.0, "speed": 220.0, "bullet_damage": 8.0, "bullet_radius": 8.0, "cooldown": 2.5 },
          { "kind": "dash", "windup": 0.8, "dash_speed": 600.0, "dash_time": 0.5, "contact_damage": 15.0, "contact_hit_cd": 0.4, "recover": 0.8, "telegraph_vfx": "boss_telegraph", "cooldown": 3.0 }
        ]
      },
      {
        "hp_threshold": 0.66,
        "move_speed_mult": 1.2,
        "summon_adds": 6,
        "attack_patterns": [ ... ]
      },
      {
        "hp_threshold": 0.33,
        "move_speed_mult": 1.5,
        "summon_adds": 8,
        "attack_patterns": [ ... ]
      }
    ]
  },
  "5": { "move_speed": 80.0, "phases": [ ... ] }
}
```

- 顶层键为 `boss_type`（"4"=Mini、"5"=Final），与 `waves.json` 的 `boss` 字段、`balance.json` 的 `boss_hp` 键一致。
- `phases` 按 `hp_threshold` 降序（1.0 → …）。
- Boss 基础血量仍取 `balance.json` 的 `boss_hp`（不迁移，避免破坏现有平衡数据）；`bosses.json` 只负责阶段/招式/移动。
- `SettingsManager` 加 getter（如 `get_boss_phase_config(boss_type) -> Array`）；`BossManager._ready` 经其读取。缺失/格式错 `push_error`，无静默回退。

### 4.2 设计器「Boss」分页

按 CLAUDE.md「新增分页 3 步」：
1. `addons/game_designer/boss_tab.gd`（`@tool extends VBoxContainer`，复用 `designer_ui.gd` 的 `spin/line/opt/btn/header/set_status/load_json/save_json`；在 `_ready()` 构建 UI，非 `_init()`）。
2. `designer_panel.gd` 的 `_init()` 里 `_add_tab("Boss", ...)` 注册。
3. Manager `_load_json_bosses()` 于 `_ready()` 载入。

字段化要求（遵守插件规范）：
- 所有枚举/选项**中英双语、中文在前**（`kind`：`环 ring` / `扇形 spread` / `瞄准 aimed` / `旋转 spiral` / `冲刺 dash` / `蓄力 charge_up` / `近战 melee`）。
- `OptionButton` 存 key（如 `ring`），**不**把双语显示串写进 JSON（显示标签数组与存储值数组分离，index 对齐）。
- 数值用 `SpinBox`，撑满宽度（`SIZE_EXPAND_FILL`，工厂已内置）。
- 可变长的阶段列表 / 招式列表用增删行 UI（➕加 / 🗑删）；如结构过深，MVP 可先做「按 boss_type 选择 → 阶段列表 → 每阶段招式行编辑」的分层表单。**禁止手写 JSON 文本框**。
- 数据往返一致、防丢键；可选字段为默认值时可省略。

---

## 5. 事件

`event_ids.gd` 从 29 起新增（同步更新 `docs/technical/implementation_plan.md §2.1` 事件目录）：

| ID | 常量 | Payload | 订阅方 |
| :-- | :-- | :-- | :-- |
| 29 | `BOSS_PHASE_CHANGED` | `{ boss_id, boss_type, phase }` | HUD（阶段提示）、AudioManager（可选 BGM）|
| 30 | `BOSS_KILLED` | `{ boss_id, boss_type, wave }` | WaveManager/成就（可选）|

`GAME_CLEARED` 延后（范围外）。沿用现有 `BOSS_SPAWNED = 19`。

---

## 6. 渲染

`combat_s2.gd`：
- `_setup_world_view` 增第三个 `MultiMeshInstance2D` + `_enemy_bullet_mm`（`instance_count = EnemyBulletManager.MAX_ENEMY_BULLETS`），modulate 用**红/紫**区分玩家子弹（黄）。
- `_process` 增 `EnemyBulletManager.sync_multimesh(_enemy_bullet_mm)`。

---

## 7. 跨切片约束（P6-N）遵守

- `ENEMY_BULLET_STRIDE` 命名常量，禁裸整数。
- 热循环（integrate/collision）不 `new()`；SoA 预分配；`_swap_and_pop` 就地搬运。
- VFX `one_shot`（P6-N72）。
- EventID 从常量取，禁硬编码整数；顺序递增（29/30）。
- CoreFeatureTag / StatusID 等仍用枚举，不引入裸串。
- 纯数据驱动：`bosses.json` 缺失/格式错 `push_error`，无硬编码回退副本。

---

## 8. 验证

- godot-mcp-pro `validate_script` 校验新脚本语法。
- `play_scene` 跳到 W8/W20（或直接 `WaveManager` 起 boss 波），`execute_game_script` 断言：
  - 敌方子弹生成并使 `PlayerStats.hp` 下降（`ring`/`spread`/`aimed`/`spiral` 各验一次）。
  - `dash` 前摇静立→冲刺位移→接触扣血（带命中冷却，不每帧多段）；`charge_up` 前摇→释放弹幕；`melee` 前摇→近身范围内扣血、范围外不扣。
  - HP 跨 66%/33%（或配置阈值）发 `BOSS_PHASE_CHANGED`；进阶召唤援军数正确。
  - Boss 死亡发 `BOSS_KILLED`，波次照常完成。
- 设计器 Boss 分页对真实 `bosses.json` 跑回读往返（`execute_editor_script` 断言 `FAILS=0`）；改 dock GDScript 后需 `EditorInterface.restart_editor(true)` 清缓存再测。

---

## 9. 目录 / 文件清单

```
scripts/autoloads/
  enemy_bullet_manager.gd (+ .uid)   # 新增 autoload
  boss_manager.gd         (+ .uid)   # 新增 autoload
scripts/autoloads/enemy_manager.gd   # +set_entity_pos / +register_external_mover / _gd_update_movement 跳过
scripts/autoloads/wave_manager.gd    # _spawn_boss 注册 BossManager；移除旧 _update_boss_phase/_on_boss_phase_enter
scripts/autoloads/event_ids.gd       # +BOSS_PHASE_CHANGED=29 / +BOSS_KILLED=30
scripts/autoloads/settings_manager.gd# +get_boss_phase_config 等 getter
scripts/domain/combat/combat_manager.gd # reset EnemyBulletManager/BossManager
scenes/main/combat_s2.gd             # 敌方子弹 MMI + sync
data/bosses.json                     # 新增数据
addons/game_designer/boss_tab.gd (+ .uid)  # 新增设计器分页
addons/game_designer/designer_panel.gd     # _add_tab 注册
project.godot                        # 注册两个 autoload + 分页无关
docs/technical/implementation_plan.md# §2.1 事件目录 +29/+30
docs/technical/boss_design.md        # 更新实现现状注记（改文档追认）
```
