feat(editor): EnemyAiBrain 检视器——二选一不变量提前到编辑期,定义 id 改下拉

This commit is contained in:
2026-07-30 08:38:13 +08:00
parent afcc0b10ee
commit b36ac96ac7
2 changed files with 107 additions and 0 deletions
@@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using BaseGames.AI;
using BaseGames.Enemies;
namespace BaseGames.Editor.AI
{
/// <summary>
/// EnemyAiBrain 的检视器:把「配方资产 / AiScript 定义 id」二选一的不变量提前到编辑期,
/// 并把定义 id 从裸字符串换成下拉(取自 AiDefinitionRegistry)。
/// 不做任何自动修正——配错就明确报出来,由作者选一条路(CLAUDE.md 第 6 条)。
/// </summary>
[CustomEditor(typeof(EnemyAiBrain))]
[CanEditMultipleObjects]
public sealed class EnemyAiBrainEditor : UnityEditor.Editor
{
const string NoneLabel = "(不用此路径)";
SerializedProperty _recipe;
SerializedProperty _definitionId;
void OnEnable()
{
_recipe = serializedObject.FindProperty("_recipe");
_definitionId = serializedObject.FindProperty("_definitionId");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(_recipe);
DrawDefinitionIdDropdown();
DrawInvariantHelp();
// 其余字段(若将来新增)照常绘制
DrawPropertiesExcluding(serializedObject, "m_Script", "_recipe", "_definitionId");
serializedObject.ApplyModifiedProperties();
}
void DrawDefinitionIdDropdown()
{
var ids = new List<string> { NoneLabel };
ids.AddRange(AiDefinitionRegistry.Ids.OrderBy(x => x));
string current = _definitionId.stringValue;
int index = string.IsNullOrEmpty(current) ? 0 : ids.IndexOf(current);
// 已配的 id 在注册表里找不到(脚本被删/改名)——保留原值并显式标出,不静默清空
bool missing = index < 0;
if (missing)
{
ids.Add($"{current} ⚠ 注册表中不存在");
index = ids.Count - 1;
}
using (new EditorGUI.DisabledScope(_recipe.objectReferenceValue != null))
{
int picked = EditorGUILayout.Popup(
new GUIContent("定制脚本 id", "[AiDefinition(id)] 的 AiScript 子类 id。与配方资产二选一"),
index, ids.Select(s => new GUIContent(s)).ToArray());
if (picked != index)
_definitionId.stringValue = picked == 0 ? string.Empty : ids[picked];
}
if (missing)
EditorGUILayout.HelpBox(
$"定义 id '{current}' 在 AiDefinitionRegistry 中不存在。" +
"对应的 [AiDefinition] AiScript 子类可能已被删除或改名。", MessageType.Error);
}
void DrawInvariantHelp()
{
if (_recipe.hasMultipleDifferentValues || _definitionId.hasMultipleDifferentValues)
return; // 多选且不一致时不下断言
bool hasRecipe = _recipe.objectReferenceValue != null;
bool hasId = !string.IsNullOrEmpty(_definitionId.stringValue);
if (hasRecipe && hasId)
EditorGUILayout.HelpBox(
"配方资产与定制脚本 id 同时配置了。二者互斥,运行时 Awake 会报错并禁用本组件。" +
"请清掉其中一项。", MessageType.Error);
else if (!hasRecipe && !hasId)
EditorGUILayout.HelpBox(
"尚未配置 AI 来源。约 95% 的敌人应指定配方资产(用菜单 " +
"BaseGames/AI/Enemy AI Recipe Wizard 创建);有独门机制的敌人才写 " +
"[AiDefinition] 的 AiScript 子类并在此选它的 id。", MessageType.Error);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1916dc03357f54d48a974ff1bbd377ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: