Files
zeling_v2/Assets/_Game/Scripts/Enemies/AIBrain/EnemyAiBrain.cs
T

84 lines
3.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(配方资产)或 _definitionIdAiScript 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);
}
}