using BaseGames.AI; namespace BaseGames.Enemies { /// /// 嘲风的 AI 图(定制路径:Boss 数量少、彼此差异大,手写图而非配方)。 /// /// 阶段 0(地面):逼近 ↔ 选招。地面招由 BossPhaseAbilityGate 在阶段 0 放行。 /// 阶段 1(空中):悬停 ↔ 选招。空中招由阶段门在阶段 1 放行,地面招被禁用。 /// /// 招式的射程 / 冷却 / 权重全归各能力自己的 EnemyAbilitySO;本图只决定 /// 「什么时候该逼近、什么时候该出手、什么时候该换阶段」。 /// 死亡不在本图:EnemyBase 的死亡流程发 Died 事件,全局边把决策停在 Death 终态。 /// /// 三条刻意的形状,改图时别顺手抹掉: /// 1. Boss 不脱战——逼近态的 rest 指回自身,脱离感知区也不回静置态。 /// 2. 阶段单向推进——空中阶段没有回地面阶段的边,血量回升也不退阶段(招池已换)。 /// 3. 起手就打完——攻击态不挂脱战边,前摇中玩家跑开仍完整打完。 /// [AiDefinition("ChaoFeng")] public sealed class ChaoFengAi : AiScript { public const string Wait = "Wait"; public const string Intro = "Intro"; public const string Ground = "Ground"; public const string GroundAttack = "GroundAttack"; public const string PhaseTx = "PhaseTransition"; public const string Air = "Air"; public const string AirAttack = "AirAttack"; public const string Death = "Death"; /// 入场演出能力 id(对应入场能力资产的 abilityId)。 public const string IntroAbility = "chaofeng_intro"; /// 空中阶段的血量阈值。 private const float AirPhaseHpRatio = 0.5f; /// 阶段过渡的目标阶段索引。 private const int AirPhaseIndex = 1; /// 阶段过渡无敌时长,须 ≥ 浮空上升时长 + 缓冲。 private const float PhaseTxInvincible = 2f; protected override void Build(BrainBuilder b) { b.Entry(Wait); // 竞技场触发前静置:不巡逻、不警觉(Boss 不搜敌,等玩家进场) AiStateFragments.Locomotion(b, Wait, LocomotionMode.Idle) .To(Intro).OnEvent(AiSignal.Engaged); // 入场演出:一次性能力,播完进战 AiStateFragments.AbilityOnce(b, Intro, IntroAbility) .To(Ground).When(x => !x.Combat.IsAbilityRunning(IntroAbility), "introDone"); // 阶段 0(地面):逼近 ↔ 选招。rest 指回自身——Boss 不脱战。 BossFragments.ApproachAttack(b, Ground, GroundAttack, rest: Ground) .To(PhaseTx).When(x => x.Vitals.HpBelow(AirPhaseHpRatio), "hp<50%"); // 阶段过渡:无敌 + 浮空演出由 Boss 侧的阶段过渡回调承担。 // PhaseTransition 片段刻意不带出边——这条 txDone 边就是它的出口,删了会把 Boss 永久锁死。 BossFragments.PhaseTransition(b, PhaseTx, AirPhaseIndex, PhaseTxInvincible) .To(Air).When(x => !x.Boss.IsPhaseTransitioning, "txDone"); // 阶段 1(空中):悬停朝向玩家 ↔ 选招。刻意不挂回地面阶段的边(阶段单向推进)。 AiStateFragments.Locomotion(b, Air, LocomotionMode.Face) .To(AirAttack).When(x => x.Combat.HasEligibleAttack(), "attackInRange"); // 与地面攻击态同语义:起手就打完,不挂脱战边 b.DeclareState(AirAttack) .OnEnter(x => { x.Locomotion.Stop(); x.Combat.UseBestAttack(); }) .OnExit (x => x.Combat.InterruptAbilities()) .To(Air).When(x => !x.Combat.IsAbilityRunning(), "attackDone"); // 死亡终态:演出走物理层的击败序列,图上只需停止决策 AiStateFragments.Terminal(b, Death); b.Global().To(Death).OnEvent(AiSignal.Died); } } }