feat(ai): Boss 图共享片段 + 竞技场锚点 + AiSignal.Engaged

BossFragments:阶段过渡 / 逼近选招 / 移动到锚点三个静态建态片段。
ApproachAttackEngagement 的建态逻辑提为 internal static Declare(态名参数化),
模块路径与 Boss 图路径共用同一份行为,Boss 可建多组(地面组/空中组)。

锚点坐标经 IBossControl.AnchorAt / DistanceToAnchor 暴露,不走黑板——
EnemyAiBrain._context 是私有的(BossBase 无从写入),且 ResetScratch 会清黑板
带来隐式时序约束。锚点本就是 Boss 专属知识,归 Boss facet 更直。
锚点漏配 / 下标越界显式抛,不回退到自身位置。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 14:32:32 +08:00
co-authored by Claude Opus 5
parent 8c56d207bd
commit eaba54ea76
8 changed files with 190 additions and 9 deletions
@@ -29,6 +29,10 @@ namespace BaseGames.Enemies
[Tooltip("按阶段启用 / 禁用能力组件;未挂载则所有能力全阶段可用")]
[SerializeField] private BossPhaseAbilityGate _phaseGate;
[Header("竞技场锚点(可选)")]
[Tooltip("定点走位用的锚点集合;AI 图未用到锚点片段时可留空")]
[SerializeField] private BossArenaAnchors _arenaAnchors;
[Header("玩家反制事件(可选)")]
[Tooltip("订阅此频道以响应玩家弹反成功事件")]
[SerializeField] private ParryInfoEventChannelSO _onParrySuccess;
@@ -49,6 +53,20 @@ namespace BaseGames.Enemies
: throw new System.InvalidOperationException(
$"[BossBase] '{name}' 的 AI 图查询了资源满值,但未挂 BossResource 组件。" +
"请挂上该组件,或从 AI 图里移除资源相关的边。");
/// <summary>第 index 个竞技场锚点坐标。IBossControl 实现。未挂锚点组件即抛——
/// AI 图用了锚点片段却没配锚点是配置错误,必须暴露而非回退到自身位置。</summary>
public Vector2 AnchorAt(int index) => RequireAnchors().At(index);
/// <summary>当前位置到第 index 个锚点的距离。IBossControl 实现。</summary>
public float DistanceToAnchor(int index)
=> Vector2.Distance(transform.position, RequireAnchors().At(index));
private BossArenaAnchors RequireAnchors()
=> _arenaAnchors != null ? _arenaAnchors : throw new System.InvalidOperationException(
$"[BossBase] '{name}' 的 AI 图使用了竞技场锚点,但未挂 BossArenaAnchors 组件。" +
"请挂上该组件并配好锚点,或从 AI 图里移除锚点片段。");
private Coroutine _counterStaggerCoroutine;
// 缓存加权候选与其有效权重(两者等长、下标对应),避免 UseBossSkillWeighted() 每次 new List → GC 分配
@@ -65,6 +83,7 @@ namespace BaseGames.Enemies
if (_skillExecutor == null) _skillExecutor = GetComponentInChildren<BossSkillExecutor>(true);
if (_bossResource == null) _bossResource = GetComponentInChildren<BossResource>(true);
if (_phaseGate == null) _phaseGate = GetComponentInChildren<BossPhaseAbilityGate>(true);
if (_arenaAnchors == null) _arenaAnchors = GetComponentInChildren<BossArenaAnchors>(true);
}
protected override void OnEnable()