feat(editor): EnemyAiBrain 检视器——二选一不变量提前到编辑期,定义 id 改下拉
This commit is contained in:
@@ -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:
|
||||||
Reference in New Issue
Block a user