feat: add BossPhaseSystem with phase switch logic
This commit is contained in:
125
assets/script/game/hero/BossPhaseSystem.ts
Normal file
125
assets/script/game/hero/BossPhaseSystem.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* BossPhaseSystem — Boss 多阶段战斗状态机。
|
||||
*
|
||||
* 检测混合触发条件(血量阈值 OR 在场时长),命中后执行阶段切换:
|
||||
* removes 移除旧技能 → adds 注入新技能(cd 兜底 + 资源预热)→ attr_mod 属性修正
|
||||
* → 空钩子 onPhaseExit/onPhaseEnter(演出后续迭代)→ 广播 GameEvent.BossPhaseChange
|
||||
*
|
||||
* Why: 独立系统符合 M/V/S 分层;filter 按 BossPhaseComp 过滤,每局 Boss 唯一,开销可忽略。
|
||||
* 计时在组件 elapsed(随 reset 归零),避免系统级计数在 per-entity update 中重复累加。
|
||||
*/
|
||||
|
||||
import { Prefab } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { BossPhaseSet, PhaseSwitchDef } from "../common/config/BossPhaseSet";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { BossPhaseComp } from "./BossPhaseComp";
|
||||
|
||||
/** 阶段模块调试日志开关 */
|
||||
const DEBUG = true;
|
||||
|
||||
/** 新注入技能缺省 CD(秒),与 Mon.ts 大招注入兜底一致 */
|
||||
const DEFAULT_ADD_CD = 8;
|
||||
|
||||
@ecs.register('BossPhaseSystem')
|
||||
export class BossPhaseSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(BossPhaseComp, HeroAttrsComp);
|
||||
}
|
||||
|
||||
update(e: ecs.Entity): void {
|
||||
const phase = e.get(BossPhaseComp);
|
||||
const model = e.get(HeroAttrsComp);
|
||||
if (!phase || !model) return;
|
||||
|
||||
// 战斗状态守卫(与 MoveComp/SCastSystem 同款模式):暂停/未开战不计时
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
// 死亡同帧守卫:防死尸换阶段
|
||||
if (model.is_dead) return;
|
||||
// P3 后不再检测(固定 3 阶段)
|
||||
if (phase.index >= 2) return;
|
||||
|
||||
const plan = BossPhaseSet[phase.plan_id];
|
||||
if (!plan) return;
|
||||
|
||||
phase.elapsed += this.dt;
|
||||
const def = plan.switches[phase.index];
|
||||
|
||||
// 混合触发:任一条件满足即切换(血量实时计算,禁止缓存绝对值——hp_max 在 load 后被外部覆盖)
|
||||
const hpPct = model.hp / model.hp_max * 100;
|
||||
const hpHit = def.trigger.hp_pct !== undefined && hpPct <= def.trigger.hp_pct;
|
||||
const timeHit = def.trigger.time !== undefined && phase.elapsed >= def.trigger.time;
|
||||
if (hpHit || timeHit) {
|
||||
this.executePhaseSwitch(e, def);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行阶段切换:技能增量改写 + 属性修正 + 钩子与事件广播
|
||||
* Why 改 skills 而非施法系统过滤:与 _testSkills/RogueConfig.ults 动态注入同模式,
|
||||
* SCastSystem 零改动;施法闭包捕获 SkillSet 静态配置,进行中施法不受影响
|
||||
*/
|
||||
private executePhaseSwitch(e: ecs.Entity, def: PhaseSwitchDef): void {
|
||||
const phase = e.get(BossPhaseComp)!;
|
||||
const model = e.get(HeroAttrsComp)!;
|
||||
const from = phase.index;
|
||||
|
||||
this.onPhaseExit(e);
|
||||
|
||||
// 1. 移除旧技能(仅大招键;普攻首键受键序契约保护,见 BossPhaseSet 文件头注释)
|
||||
if (def.removes) {
|
||||
for (const s_uuid of def.removes) {
|
||||
delete model.skills[s_uuid];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 注入新技能(cd 强制兜底,防 undefined → Math.min 结果 NaN 导致大招永不释放)
|
||||
if (def.adds) {
|
||||
for (const add of def.adds) {
|
||||
if (!add || !add.s_uuid || !SkillSet[add.s_uuid]) continue;
|
||||
model.skills[add.s_uuid] = {
|
||||
uuid: add.s_uuid,
|
||||
lv: 1,
|
||||
cd: add.cd ?? DEFAULT_ADD_CD,
|
||||
ccd: 0,
|
||||
overrides: add.overrides,
|
||||
};
|
||||
// 资源预热:新技能 prefab 首次异步加载会丢一发,切换时预载(路径与 Skill.load 对齐)
|
||||
oops.res.load(`game/skill/atk/${SkillSet[add.s_uuid].sp_name}`, Prefab);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 属性修正:基于当前值链式乘算(P3 基于 P2 修正后的值,配置按此口径)
|
||||
const mod = def.attr_mod;
|
||||
if (mod) {
|
||||
if (mod.ap_pct) model.ap = Math.round(model.ap * (1 + mod.ap_pct / 100));
|
||||
if (mod.defense_pct) model.defense = Math.round(model.defense * (1 + mod.defense_pct / 100));
|
||||
}
|
||||
|
||||
phase.index++;
|
||||
|
||||
this.onPhaseEnter(e);
|
||||
|
||||
mLogger.log(DEBUG, "BossPhase", `${model.hero_name} 阶段切换 P${from + 1}→P${phase.index + 1}(方案 ${phase.plan_id})`);
|
||||
|
||||
// 广播阶段切换(payload 用 eid 定位实体,防监听方持有已回收实体裸引用)
|
||||
oops.message.dispatchEvent(GameEvent.BossPhaseChange, {
|
||||
eid: e.eid,
|
||||
planId: phase.plan_id,
|
||||
fromPhase: from,
|
||||
toPhase: phase.index,
|
||||
});
|
||||
}
|
||||
|
||||
/** TODO: 阶段退场表现(震屏/变暗等),后续迭代实现 */
|
||||
private onPhaseExit(e: ecs.Entity): void { }
|
||||
|
||||
/** TODO: 阶段入场表现(怒吼/无敌帧/技能横幅等),后续迭代实现 */
|
||||
private onPhaseEnter(e: ecs.Entity): void { }
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { SkillTriggerType, HType } from "../common/config/heroSet";
|
||||
import { SkillTriggerHelper } from "./SkillTriggerHelper";
|
||||
import { BossPhaseSet } from "../common/config/BossPhaseSet";
|
||||
import { BossPhaseComp } from "./BossPhaseComp";
|
||||
import "./BossPhaseSystem";
|
||||
import { mLogger } from "../common/Logger";
|
||||
/** 怪物实体:负责怪物对象池复用、属性初始化、入场动画与回收 */
|
||||
@ecs.register(`Monster`)
|
||||
|
||||
Reference in New Issue
Block a user