84 lines
3.4 KiB
C#
84 lines
3.4 KiB
C#
using UnityEngine;
|
||
using BaseGames.AI;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
/// <summary>
|
||
/// 把 BrainGraph 决策层挂到敌人上:按 id 取共享 AiGraph、构造 AiRuntime、逐帧推进。
|
||
/// 取代旧的行为树组件。
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
[RequireComponent(typeof(EnemyBase))]
|
||
public sealed class EnemyAiBrain : MonoBehaviour
|
||
{
|
||
[Tooltip("配方路径(约 95% 的敌人):直接引用 AI 配方资产")]
|
||
[SerializeField] AiRecipeSO _recipe;
|
||
|
||
[Tooltip("定制路径(约 5% 的敌人):[AiDefinition(id)] 的 AiScript 子类 id。与配方二选一")]
|
||
[SerializeField] string _definitionId;
|
||
|
||
EnemyBase _enemy;
|
||
EnemyBrainContext _context;
|
||
AiRuntime _runtime;
|
||
bool _configValid;
|
||
|
||
/// <summary>调试显示用。配方路径返回资产名、脚本路径返回定义 id——两者是不同的
|
||
/// id 空间,**不要**拿它去 AiDefinitionRegistry 查表。</summary>
|
||
public string DefinitionId => _recipe != null ? _recipe.name : _definitionId;
|
||
public string CurrentStateName => _runtime != null ? _runtime.CurrentStateName : "(none)";
|
||
public bool IsSuspended => _runtime != null && _runtime.IsSuspended;
|
||
public AiRuntime Runtime => _runtime;
|
||
|
||
void Awake()
|
||
{
|
||
_enemy = GetComponent<EnemyBase>();
|
||
|
||
bool hasRecipe = _recipe != null;
|
||
bool hasId = !string.IsNullOrEmpty(_definitionId);
|
||
_configValid = hasRecipe != hasId;
|
||
if (!_configValid)
|
||
{
|
||
// 根因暴露:配置歧义 / 漏配直接报错,不静默兜底。
|
||
Debug.LogError(
|
||
$"EnemyAiBrain 必须且只能配置一项:_recipe(配方资产)或 _definitionId(AiScript id)。当前 recipe={(hasRecipe ? _recipe.name : "空")}, id='{_definitionId}':{name}",
|
||
this);
|
||
enabled = false;
|
||
}
|
||
}
|
||
|
||
// 在 Start 构造 runtime:确保 EnemyBase.Awake 已发现 Locomotion/子系统后,
|
||
// 再触发入口状态 OnEnter(其会声明 locomotion 意图)。所有 Awake 先于任一 Start 执行。
|
||
void Start()
|
||
{
|
||
// 不依赖"禁用组件即跳过 Start"这一生命周期细节:组件若被事后重新启用,
|
||
// 配置仍然是错的,不能让它带着坏配置建图。
|
||
if (!_configValid) return;
|
||
|
||
IAiDefinition definition = _recipe != null
|
||
? (IAiDefinition)_recipe
|
||
: AiDefinitionRegistry.GetDefinition(_definitionId); // 不存在则抛异常
|
||
var graph = definition.GetOrBuildGraph();
|
||
_context = new EnemyBrainContext(_enemy);
|
||
_runtime = new AiRuntime(graph, _context);
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (_runtime == null) return;
|
||
float dt = Time.deltaTime;
|
||
_context.Refresh(dt);
|
||
_runtime.Tick(dt);
|
||
}
|
||
|
||
/// <summary>对象池复用时由 EnemyBase.OnSpawn 调用:回到 Entry、清临时态。</summary>
|
||
public void ResetBrain()
|
||
{
|
||
_context?.ResetScratch();
|
||
_runtime?.Reset();
|
||
}
|
||
|
||
/// <summary>信号入口,供 EnemyBase 转发死亡等事件到决策层。</summary>
|
||
public void Send(AiSignal signal) => _runtime?.Send(signal);
|
||
}
|
||
}
|