using UnityEngine; using BaseGames.AI; namespace BaseGames.Enemies { /// /// 把 BrainGraph 决策层挂到敌人上:按 id 取共享 AiGraph、构造 AiRuntime、逐帧推进。 /// 是敌人唯一的决策层组件(小怪与 Boss 共用)。 /// [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; /// 调试显示用。配方路径返回资产名、脚本路径返回定义 id——两者是不同的 /// id 空间,**不要**拿它去 AiDefinitionRegistry 查表。 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(); 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); } /// 对象池复用时由 EnemyBase.OnSpawn 调用:回到 Entry、清临时态。 public void ResetBrain() { _context?.ResetScratch(); _runtime?.Reset(); } /// 信号入口,供 EnemyBase 转发死亡等事件到决策层。 public void Send(AiSignal signal) => _runtime?.Send(signal); } }