using UnityEngine; using BaseGames.AI; namespace BaseGames.Enemies { /// /// 把 EnemyBase 的子系统包装成 BrainGraph 的能力接口(每敌人一实例)。 /// 状态逻辑只依赖这些接口,不直接引用具体子系统。 /// public sealed class EnemyBrainContext : IAiContext, ISensor, IMover, ICombatant, IActorVitals, IEnemyActor { readonly EnemyBase _enemy; readonly Blackboard _blackboard = new Blackboard(); Vector2 _lastKnown; float _lostTimer; public EnemyBrainContext(EnemyBase enemy) { _enemy = enemy; } // ---- IAiContext ---- public ISensor Sensor => this; public IMover Mover => this; public ICombatant Combat => this; public IActorVitals Vitals => this; public Blackboard Blackboard => _blackboard; /// 每帧由 EnemyAiBrain 在 Tick 之前调用,维护最后已知位置与丢失计时。 public void Refresh(float dt) { if (DetectsPlayer() && _enemy.PlayerTransform != null) { _lastKnown = _enemy.PlayerTransform.position; _lostTimer = 0f; } else { _lostTimer += dt; } } /// 对象池复用时清空临时态。 public void ResetScratch() { _blackboard.Clear(); _lastKnown = _enemy.transform.position; _lostTimer = 0f; } // ---- ISensor ---- public bool SeesPlayer() => _enemy.IsPlayerVisible(); /// 视线 OR 近身 aggro 槽位,任一探到玩家即为真(对齐设计"视线 OR 碰撞触发圆")。 public bool DetectsPlayer() { if (_enemy.IsPlayerVisible()) return true; var hub = _enemy.SensorHub; return hub != null && hub.HasAnyDetection(Perception.SensorSlotNames.Aggro); } public bool InRange(float range) => _enemy.IsPlayerInRange(range); public bool LostFor(float seconds) => !DetectsPlayer() && _lostTimer >= seconds; public Vector2 LastKnown => _lastKnown; // ---- IMover ---- public void MoveTo(Vector2 target) => _enemy.MoveTo(target); public void FacePlayer() => _enemy.FacePlayer(); public void Stop() => _enemy.StopMovement(); public void WalkRandom() { var nav = _enemy.Nav; if (nav != null && !nav.IsMoving) nav.WalkToRandom(); } public void LookAround() => _enemy.BeginLookAround(); public void UseChaseSpeed() => _enemy.Nav?.SetSpeed(_enemy.Stats != null ? _enemy.Stats.RunSpeed : 0f); public void UsePatrolSpeed() => _enemy.Nav?.SetSpeed(_enemy.Stats != null ? _enemy.Stats.WalkSpeed : 0f); // ---- ICombatant ---- public bool UseAbility(string abilityId) { var a = _enemy.Abilities?.Get(abilityId); return a != null && a.Execute(); } public bool IsAbilityRunning(string abilityId = null) { var reg = _enemy.Abilities; if (reg == null) return false; if (abilityId != null) { var a = reg.Get(abilityId); return a != null && a.IsRunning; } var all = reg.All; for (int i = 0; i < all.Count; i++) if (all[i].IsRunning) return true; return false; } public bool NoAbilityRunning => !IsAbilityRunning(); public bool CanUseAbility(string abilityId) { var a = _enemy.Abilities?.Get(abilityId); return a != null && a.CanUse; } // ---- IActorVitals ---- public bool IsAlive => _enemy.IsAlive; public bool IsControllable => _enemy.CurrentState == EnemyStateType.Controlled; public float HpPercent { get { var stats = _enemy.Stats; return stats != null && stats.MaxHP > 0 ? (float)stats.CurrentHP / stats.MaxHP : 0f; } } public bool HpBelow(float ratio) => HpPercent < ratio; // ---- IEnemyActor ---- public void SetPhase(AiPhase phase) => _enemy.SetAiPhase(phase); } }