阶段状态机 + 攻击模式注册表(环/扇/瞄准/旋转 + 冲刺/蓄力)+ 新建 EnemyBulletManager 敌方子弹 + 保留召唤援军 + 事件 29/30 + bosses.json 与设计器 Boss 分页。范围外:场地机制/通关演出/无敌帧/.tres。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
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.godotautoload。 - 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.godotautoload。 - 运行态(每个活跃 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 —_check_phase_transition:读EnemyManager.get_hp_percent(id),按phase_defs(hp_threshold降序)检查是否跨入更深阶段;跨入 → 进阶(重置pattern_index/cooldown、可选召唤援军、发BOSS_PHASE_CHANGED)。_tick_movement:无进行中位移招式时,Boss 朝玩家缓慢漂移(move_speed * phase.move_speed_mult),经EnemyManager.set_entity_pos写回 SoA。_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
{
"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 步」:
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())。designer_panel.gd的_init()里_add_tab("Boss", ...)注册。- 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 # 更新实现现状注记(改文档追认)