diff --git a/Assets/_Game/Scripts/Enemies/AIBrain.meta b/Assets/_Game/Scripts/Enemies/AIBrain.meta new file mode 100644 index 00000000..e8d13898 --- /dev/null +++ b/Assets/_Game/Scripts/Enemies/AIBrain.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4dbaeb7890d1b7140b255b738fdc3ce0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs new file mode 100644 index 00000000..d6020fa6 --- /dev/null +++ b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs @@ -0,0 +1,105 @@ +using UnityEngine; +using BaseGames.AI; + +namespace BaseGames.Enemies +{ + /// + /// 把 EnemyBase 的子系统包装成 BrainGraph 的能力接口(每敌人一实例)。 + /// 状态逻辑只依赖这些接口,不直接引用具体子系统。 + /// + public sealed class EnemyBrainContext : IAiContext, ISensor, IMover, ICombatant, IActorVitals + { + 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 (_enemy.IsPlayerVisible() && _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(); + public bool InRange(float range) => _enemy.IsPlayerInRange(range); + public bool LostFor(float seconds) => !_enemy.IsPlayerVisible() && _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(); + + // ---- 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; + } +} diff --git a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta new file mode 100644 index 00000000..9934fd4b --- /dev/null +++ b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9459e1a1d93189547ab3354d181bf4ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef b/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef index 38eff23e..a6bc0a21 100644 --- a/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef +++ b/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef @@ -3,6 +3,7 @@ "rootNamespace": "BaseGames.Enemies", "references": [ "BaseGames.Core", + "BaseGames.AI", "BaseGames.Core.Events", "BaseGames.Combat", "BaseGames.Feedback",