using UnityEngine; using BaseGames.AI; namespace BaseGames.Enemies { /// /// 把 BrainGraph 决策层挂到敌人上:按 id 取共享 AiGraph、构造 AiRuntime、逐帧推进。 /// 取代旧的行为树组件。 /// [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; 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); if (hasRecipe == hasId) // 都配了 或 都没配 { // 根因暴露:配置歧义 / 漏配直接报错,不静默兜底。 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() { 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); } }