Files
zeling_v2/Assets/_Game/Scripts/Enemies/AIBrain/EnemyAiBrain.cs
T
joywayerandClaude Opus 5 cf0ac60fad refactor(enemy): 清全库行为树残留,删无人使用的 Boss 技能事件频道
Task 21 Step 2 全库扫描后的收尾:

注释与 Tooltip 里的旧决策层措辞逐处改为只描述功能——
EnemyControlledState / RangedEnemy / FlyingEnemy / SensorSlotNames /
EnemyPoiseComponent / EnemyAnimationConfigSO / EnemyAiBrain。

EnemyQuotaManager 的序列化字段 _maxActiveBehaviorTrees 改名 _maxActiveBrains
(它裁剪的实际是 EnemyAiBrain),带 FormerlySerializedAs。复核确认目前
无任何场景 / 预制体挂载该组件,故无值可丢;属未雨绸缪。

BossSkillEventChannelSO + BossEvents.BossSkillEvent + EVT_BossSkill.asset 删除:
唯一发送方 BossSkillExecutor 已随旧轨删除,复核确认全项目零发送方、零订阅方,
资产 guid 在场景 / 预制体 / Addressables 分组中均无引用。

自检:编译 0 error;EditMode 260/260 通过;
SO 校验 0 error 0 warning;层矩阵 25/25 正确。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 17:14:09 +08:00

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、逐帧推进。
/// 是敌人唯一的决策层组件(小怪与 Boss 共用)。
/// </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);
}
}