feat(ai): EnemyBrainContext——EnemyBase子系统适配为BrainGraph能力接口

This commit is contained in:
2026-07-03 14:58:52 +08:00
parent 683a2d9ffb
commit 20c2568222
4 changed files with 125 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4dbaeb7890d1b7140b255b738fdc3ce0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,105 @@
using UnityEngine;
using BaseGames.AI;
namespace BaseGames.Enemies
{
/// <summary>
/// 把 EnemyBase 的子系统包装成 BrainGraph 的能力接口(每敌人一实例)。
/// 状态逻辑只依赖这些接口,不直接引用具体子系统。
/// </summary>
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;
/// <summary>每帧由 EnemyAiBrain 在 Tick 之前调用,维护最后已知位置与丢失计时。</summary>
public void Refresh(float dt)
{
if (_enemy.IsPlayerVisible() && _enemy.PlayerTransform != null)
{
_lastKnown = _enemy.PlayerTransform.position;
_lostTimer = 0f;
}
else
{
_lostTimer += dt;
}
}
/// <summary>对象池复用时清空临时态。</summary>
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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9459e1a1d93189547ab3354d181bf4ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -3,6 +3,7 @@
"rootNamespace": "BaseGames.Enemies",
"references": [
"BaseGames.Core",
"BaseGames.AI",
"BaseGames.Core.Events",
"BaseGames.Combat",
"BaseGames.Feedback",