250 lines
9.7 KiB
C#
250 lines
9.7 KiB
C#
#if UNITY_EDITOR
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using Object = UnityEngine.Object;
|
||
|
||
namespace SLSFramework.UModAssistance
|
||
{
|
||
#region List<string>选择器,通过类型查找资产,将其名称存储在列表中
|
||
|
||
public partial class DataEditor : Editor
|
||
{
|
||
private string _pickerTargetListName;
|
||
|
||
private Dictionary<string, Object> _assetCache;
|
||
|
||
protected virtual void OnEnable()
|
||
{
|
||
// 每次选中新对象时,都清空缓存
|
||
_assetCache = new Dictionary<string, Object>();
|
||
}
|
||
|
||
|
||
protected void DrawCharacterListGUI<T>(SerializedProperty listProperty, string searchFilter = "") where T : Object
|
||
{
|
||
if (string.IsNullOrEmpty(searchFilter))
|
||
{
|
||
searchFilter = $"t:{typeof(T).Name}";
|
||
}
|
||
|
||
listProperty.isExpanded = EditorGUILayout.Foldout(listProperty.isExpanded, listProperty.displayName, false);
|
||
|
||
if (listProperty.isExpanded)
|
||
{
|
||
EditorGUI.indentLevel++;
|
||
|
||
for (int i = 0; i < listProperty.arraySize; i++)
|
||
{
|
||
SerializedProperty elementNameProp = listProperty.GetArrayElementAtIndex(i);
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
EditorGUILayout.PropertyField(elementNameProp, GUIContent.none);
|
||
bool valueChanged = EditorGUI.EndChangeCheck();
|
||
string assetName = elementNameProp.stringValue;
|
||
|
||
if (valueChanged || !_assetCache.TryGetValue(assetName, out Object foundAsset))
|
||
{
|
||
foundAsset = FindObjectData<T>(assetName, searchFilter);
|
||
_assetCache[assetName] = foundAsset; // 将搜索结果(即使是null)存入缓存
|
||
}
|
||
|
||
if (foundAsset != null)
|
||
{
|
||
EditorGUI.BeginDisabledGroup(true);
|
||
EditorGUILayout.ObjectField(foundAsset, typeof(T), false, GUILayout.Width(150));
|
||
EditorGUI.EndDisabledGroup();
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.LabelField(new GUIContent(EditorGUIUtility.IconContent("console.warnicon").image, "资产未找到或名称不匹配"),
|
||
GUILayout.Width(20));
|
||
}
|
||
|
||
if (GUILayout.Button("-", GUILayout.Width(20)))
|
||
{
|
||
_assetCache.Remove(elementNameProp.stringValue);
|
||
listProperty.DeleteArrayElementAtIndex(i);
|
||
i--;
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
if (GUILayout.Button("Add by Search..."))
|
||
{
|
||
_pickerTargetListName = listProperty.propertyPath;
|
||
EditorGUIUtility.ShowObjectPicker<T>(null, false, searchFilter, GUI.skin.GetHashCode());
|
||
}
|
||
|
||
EditorGUI.indentLevel--;
|
||
}
|
||
}
|
||
|
||
protected T FindObjectData<T>(string assetName, string searchFilter) where T : Object
|
||
{
|
||
if (string.IsNullOrEmpty(assetName)) return null;
|
||
|
||
string[] guids = AssetDatabase.FindAssets($"{assetName} {searchFilter}");
|
||
if (guids.Length > 0)
|
||
{
|
||
return AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(guids[0]));
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
protected void HandleObjectPicker()
|
||
{
|
||
if (Event.current.commandName == "ObjectSelectorUpdated" && EditorGUIUtility.GetObjectPickerControlID() == GUI.skin.GetHashCode())
|
||
{
|
||
Object pickedObject = EditorGUIUtility.GetObjectPickerObject();
|
||
if (pickedObject != null)
|
||
{
|
||
SerializedProperty targetListProp = serializedObject.FindProperty(_pickerTargetListName);
|
||
if (targetListProp != null)
|
||
{
|
||
bool exists = false;
|
||
for (int i = 0; i < targetListProp.arraySize; i++)
|
||
{
|
||
if (targetListProp.GetArrayElementAtIndex(i).stringValue == pickedObject.name)
|
||
{
|
||
exists = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!exists)
|
||
{
|
||
targetListProp.InsertArrayElementAtIndex(targetListProp.arraySize);
|
||
targetListProp.GetArrayElementAtIndex(targetListProp.arraySize - 1).stringValue = pickedObject.name;
|
||
}
|
||
}
|
||
|
||
_pickerTargetListName = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Type选择器,将获取的类型名存入一个string中
|
||
|
||
public partial class DataEditor
|
||
{
|
||
private static Dictionary<Tuple<Type, string>, (string[] paths, Type[] types)> _typeCache =
|
||
new Dictionary<Tuple<Type, string>, (string[], Type[])>();
|
||
|
||
/// <summary>
|
||
/// 绘制一个用于选择指定基类的所有子类的下拉菜单
|
||
/// </summary>
|
||
/// <param name="classNameProp">存储类名的字符串属性</param>
|
||
/// <param name="label">在Inspector中显示的标签</param>
|
||
/// <param name="baseType">要查找的基类 (例如 typeof(CardLogicBase))</param>
|
||
/// <param name="namespaceToRemove">可选参数,用于从路径中移除特定的命名空间部分 (例如 ".Cards")</param>
|
||
/// <returns>如果值被用户改变,则返回true</returns>
|
||
protected bool DrawTypeSelectorGUI(SerializedProperty classNameProp, string label, Type baseType,
|
||
out Type returnedType, string namespacePrefix = null, string namespaceToRemove = null)
|
||
{
|
||
// --- 核心修改 2:使用包含 namespaceToRemove 的复合键 ---
|
||
var cacheKey = new Tuple<Type, string>(baseType, namespaceToRemove ?? string.Empty);
|
||
|
||
if (!_typeCache.ContainsKey(cacheKey))
|
||
{
|
||
CacheDerivedTypes(baseType, namespacePrefix, namespaceToRemove, cacheKey);
|
||
}
|
||
|
||
var (paths, types) = _typeCache[cacheKey];
|
||
|
||
int currentIndex = -1;
|
||
for (int i = 0; i < types.Length; i++)
|
||
{
|
||
if (types[i].Name == classNameProp.stringValue)
|
||
{
|
||
currentIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
int newIndex = EditorGUILayout.Popup(label, currentIndex, paths);
|
||
|
||
if (newIndex != currentIndex)
|
||
{
|
||
classNameProp.stringValue = (newIndex >= 0 && newIndex < types.Length)
|
||
? types[newIndex].Name
|
||
: string.Empty;
|
||
|
||
returnedType = types[newIndex];
|
||
return true;
|
||
}
|
||
|
||
returnedType = null;
|
||
return false;
|
||
}
|
||
|
||
private void CacheDerivedTypes(Type baseType, string namespacePrefix, string namespaceToRemove, Tuple<Type, string> cacheKey)
|
||
{
|
||
List<(string path, Type type)> typeList = new List<(string, Type)>();
|
||
|
||
IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies()
|
||
.SelectMany(assembly => assembly.GetTypes())
|
||
.Where(t => baseType.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface && t != baseType);
|
||
|
||
foreach (var type in types)
|
||
{
|
||
string path = "Uncategorized/" + type.Name;
|
||
if (type.Namespace != null && type.Namespace.StartsWith(namespacePrefix))
|
||
{
|
||
string formattedNamespace = type.Namespace.Substring(namespacePrefix.Length);
|
||
|
||
// --- 核心修改 3:使用传入的参数来替换硬编码 ---
|
||
if (!string.IsNullOrEmpty(namespaceToRemove))
|
||
{
|
||
formattedNamespace = formattedNamespace.Replace("." + namespaceToRemove, "");
|
||
}
|
||
|
||
formattedNamespace = formattedNamespace.Replace('.', '/');
|
||
if (formattedNamespace.StartsWith("/")) formattedNamespace = formattedNamespace.Substring(1);
|
||
path = formattedNamespace + "/" + type.Name;
|
||
}
|
||
|
||
typeList.Add((path, type));
|
||
}
|
||
|
||
typeList.Sort((a, b) => a.path.CompareTo(b.path));
|
||
|
||
_typeCache[cacheKey] = (typeList.Select(t => t.path).ToArray(), typeList.Select(t => t.type).ToArray());
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 绘制一个按钮,点击后调用指定方法
|
||
public partial class DataEditor
|
||
{
|
||
protected void DrawMethodButton<T>(string buttonLabel, string functionName) where T : Object
|
||
{
|
||
if (GUILayout.Button(buttonLabel))
|
||
{
|
||
T invoker = target as T;
|
||
MethodInfo method = typeof(T).GetMethod(functionName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
if (method != null)
|
||
{
|
||
method.Invoke(invoker, null);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Method '{functionName}' not found in type '{typeof(T).Name}'.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
#endif |