docs(spec): Boss 阶段+弹幕/位移攻击模式系统设计

阶段状态机 + 攻击模式注册表(环/扇/瞄准/旋转 + 冲刺/蓄力)+ 新建
EnemyBulletManager 敌方子弹 + 保留召唤援军 + 事件 29/30 + bosses.json
与设计器 Boss 分页。范围外:场地机制/通关演出/无敌帧/.tres。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 09:17:40 +08:00
co-authored by Claude Opus 4.8
parent ef65cba162
commit 7989d8d0fd
@@ -0,0 +1,241 @@
# Boss 阶段 + 弹幕/位移攻击模式系统 — 设计
> **日期**2026-07-22
> **切片**S6Boss 深度)
> **前置状态**: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` |
**公平性约束**:所有位移/蓄力招式**必须**有 `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`)。
- `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` 前摇→释放弹幕。
- 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 # 更新实现现状注记(改文档追认)
```