428 lines
14 KiB
C#
428 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
public class UniversalSOManager : EditorWindow
|
|
{
|
|
// === 布局常量 ===
|
|
private const float SIDEBAR_WIDTH = 160f;
|
|
private const float ASSET_LIST_WIDTH = 220f;
|
|
|
|
// === 核心数据 ===
|
|
private SOEditorConfig _config;
|
|
private Type _selectedType; // 现在直接存 Type,不再存 MonoScript
|
|
|
|
// === 列表与编辑 ===
|
|
private List<ScriptableObject> _assetList = new List<ScriptableObject>();
|
|
private ScriptableObject _selectedAsset;
|
|
private Editor _cachedEditor;
|
|
|
|
// === 状态 ===
|
|
private string _searchRootPath = "Assets/SOGameData";
|
|
private Vector2 _scrollPosTypes;
|
|
private Vector2 _scrollPosAssets;
|
|
private Vector2 _scrollPosInspector;
|
|
private string _searchString = "";
|
|
private string _newAssetName = "";
|
|
|
|
[MenuItem("Tools/万能SO编辑器 (Universal SO Manager)")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<UniversalSOManager>("SO Manager");
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
string configPath = EditorPrefs.GetString("UniversalSOManager_ConfigPath", "");
|
|
if (!string.IsNullOrEmpty(configPath))
|
|
{
|
|
_config = AssetDatabase.LoadAssetAtPath<SOEditorConfig>(configPath);
|
|
}
|
|
|
|
// 加载保存的资源路径
|
|
_searchRootPath = EditorPrefs.GetString("UniversalSOManager_SearchRootPath", "Assets/SOGameData");
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_cachedEditor != null) DestroyImmediate(_cachedEditor);
|
|
if (_config != null)
|
|
{
|
|
EditorPrefs.SetString("UniversalSOManager_ConfigPath", AssetDatabase.GetAssetPath(_config));
|
|
}
|
|
|
|
// 保存资源路径
|
|
EditorPrefs.SetString("UniversalSOManager_SearchRootPath", _searchRootPath);
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
DrawTopGlobalBar();
|
|
|
|
if (_config == null)
|
|
{
|
|
DrawEmptyState();
|
|
return;
|
|
}
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
DrawSidebar();
|
|
|
|
EditorGUILayout.BeginVertical();
|
|
{
|
|
DrawTypeOperationHeader();
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
DrawAssetList();
|
|
DrawInspector();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// UI 绘制函数
|
|
// ------------------------------------------------------------------------
|
|
|
|
private void DrawTopGlobalBar()
|
|
{
|
|
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(30));
|
|
{
|
|
GUILayout.Label("资源路径:", GUILayout.Width(60));
|
|
_searchRootPath = EditorGUILayout.TextField(_searchRootPath, GUILayout.Width(150));
|
|
if (GUILayout.Button("...", EditorStyles.miniButton, GUILayout.Width(25)))
|
|
{
|
|
string path = EditorUtility.OpenFolderPanel("选择根目录", Application.dataPath, "");
|
|
if (!string.IsNullOrEmpty(path) && path.StartsWith(Application.dataPath))
|
|
{
|
|
_searchRootPath = "Assets" + path.Substring(Application.dataPath.Length);
|
|
RefreshAssetList();
|
|
}
|
|
}
|
|
|
|
GUILayout.Space(20);
|
|
|
|
GUILayout.Label("Config文件:", GUILayout.Width(70));
|
|
EditorGUI.BeginChangeCheck();
|
|
_config = (SOEditorConfig)EditorGUILayout.ObjectField(_config, typeof(SOEditorConfig), false, GUILayout.Width(200));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
_selectedType = null;
|
|
_assetList.Clear();
|
|
_selectedAsset = null;
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
// 快捷编辑 Config 按钮
|
|
if (_config != null)
|
|
{
|
|
if (GUILayout.Button("编辑Config", EditorStyles.miniButton, GUILayout.Width(80)))
|
|
{
|
|
Selection.activeObject = _config;
|
|
}
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
if (GUILayout.Button("强制刷新", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
|
{
|
|
AssetDatabase.Refresh();
|
|
RefreshAssetList();
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawSidebar()
|
|
{
|
|
EditorGUILayout.BeginVertical("box", GUILayout.Width(SIDEBAR_WIDTH), GUILayout.ExpandHeight(true));
|
|
|
|
GUILayout.Label("SO 种类", EditorStyles.boldLabel);
|
|
GUILayout.Space(5);
|
|
|
|
_scrollPosTypes = EditorGUILayout.BeginScrollView(_scrollPosTypes);
|
|
|
|
if (_config != null && _config.targetTypeNames != null)
|
|
{
|
|
foreach (var typeName in _config.targetTypeNames)
|
|
{
|
|
// 解析类型
|
|
Type type = GetTypeByName(typeName);
|
|
|
|
if (type == null)
|
|
{
|
|
// 类型丢失(可能代码改名了)
|
|
GUI.color = Color.red;
|
|
GUILayout.Label($"丢失: {typeName}");
|
|
GUI.color = Color.white;
|
|
continue;
|
|
}
|
|
|
|
bool isSelected = (_selectedType == type);
|
|
if (isSelected) GUI.backgroundColor = new Color(0.6f, 0.8f, 1f);
|
|
|
|
// 使用短名显示,但在 Tooltip 显示全名
|
|
if (GUILayout.Button(new GUIContent(type.Name, type.FullName), EditorStyles.miniButton, GUILayout.Height(28)))
|
|
{
|
|
SelectType(type);
|
|
}
|
|
|
|
GUI.backgroundColor = Color.white;
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawTypeOperationHeader()
|
|
{
|
|
EditorGUILayout.BeginVertical("box", GUILayout.Height(60), GUILayout.ExpandWidth(true));
|
|
|
|
string title = _selectedType != null ? _selectedType.Name : "请选择种类";
|
|
GUILayout.Label($"当前操作: {title}", EditorStyles.boldLabel);
|
|
|
|
GUILayout.Space(5);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
GUILayout.Label("搜索:", GUILayout.Width(40));
|
|
string newSearch = EditorGUILayout.TextField(_searchString, EditorStyles.toolbarSearchField, GUILayout.Width(200));
|
|
if (newSearch != _searchString)
|
|
{
|
|
_searchString = newSearch;
|
|
RefreshAssetList();
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
GUILayout.Label("新名称:", GUILayout.Width(50));
|
|
_newAssetName = EditorGUILayout.TextField(_newAssetName, GUILayout.Width(150));
|
|
|
|
EditorGUI.BeginDisabledGroup(_selectedType == null);
|
|
if (GUILayout.Button("新增 (Create)", EditorStyles.miniButtonRight, GUILayout.Width(100)))
|
|
{
|
|
CreateAsset();
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawAssetList()
|
|
{
|
|
EditorGUILayout.BeginVertical("box", GUILayout.Width(ASSET_LIST_WIDTH), GUILayout.ExpandHeight(true));
|
|
|
|
GUILayout.Label("资源列表", EditorStyles.miniLabel);
|
|
|
|
_scrollPosAssets = EditorGUILayout.BeginScrollView(_scrollPosAssets);
|
|
|
|
if (_assetList != null)
|
|
{
|
|
for (int i = 0; i < _assetList.Count; i++)
|
|
{
|
|
var asset = _assetList[i];
|
|
if (asset == null) continue;
|
|
|
|
EditorGUILayout.BeginHorizontal("box");
|
|
{
|
|
if (_selectedAsset == asset) GUI.backgroundColor = Color.green;
|
|
|
|
if (GUILayout.Button(asset.name, EditorStyles.label, GUILayout.Height(22)))
|
|
{
|
|
SelectAsset(asset);
|
|
}
|
|
|
|
GUI.backgroundColor = Color.white;
|
|
|
|
if (GUILayout.Button(EditorGUIUtility.IconContent("d_ViewToolOrbit"), EditorStyles.iconButton, GUILayout.Width(22)))
|
|
EditorGUIUtility.PingObject(asset);
|
|
|
|
if (GUILayout.Button(EditorGUIUtility.IconContent("d_TreeEditor.Duplicate"), EditorStyles.iconButton, GUILayout.Width(22)))
|
|
DuplicateAsset(asset);
|
|
|
|
if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash"), EditorStyles.iconButton, GUILayout.Width(22)))
|
|
{
|
|
if (EditorUtility.DisplayDialog("删除确认", $"确定删除 {asset.name} 吗?", "删除", "取消"))
|
|
{
|
|
DeleteAsset(asset);
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawInspector()
|
|
{
|
|
EditorGUILayout.BeginVertical("box", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
|
|
|
if (_selectedAsset != null)
|
|
{
|
|
GUILayout.Label("属性编辑", EditorStyles.boldLabel);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Label("文件名:", GUILayout.Width(50));
|
|
string newName = EditorGUILayout.DelayedTextField(_selectedAsset.name);
|
|
if (newName != _selectedAsset.name) RenameAsset(_selectedAsset, newName);
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
GUILayout.Space(10);
|
|
|
|
_scrollPosInspector = EditorGUILayout.BeginScrollView(_scrollPosInspector);
|
|
|
|
if (_cachedEditor == null || _cachedEditor.target != _selectedAsset)
|
|
{
|
|
if (_cachedEditor != null) DestroyImmediate(_cachedEditor);
|
|
_cachedEditor = Editor.CreateEditor(_selectedAsset);
|
|
}
|
|
|
|
if (_cachedEditor != null)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
_cachedEditor.OnInspectorGUI();
|
|
if (EditorGUI.EndChangeCheck()) EditorUtility.SetDirty(_selectedAsset);
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
else
|
|
{
|
|
GUILayout.FlexibleSpace();
|
|
GUILayout.Label("<< 请选择资源", EditorStyles.centeredGreyMiniLabel);
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawEmptyState()
|
|
{
|
|
EditorGUILayout.BeginVertical();
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.HelpBox("请在顶部指定一个 SOEditorConfig 配置文件。", MessageType.Info);
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 逻辑处理
|
|
// ------------------------------------------------------------------------
|
|
|
|
// 辅助:从字符串获取类型 (遍历所有程序集)
|
|
private Type GetTypeByName(string fullName)
|
|
{
|
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
var type = assembly.GetType(fullName);
|
|
if (type != null) return type;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void SelectType(Type type)
|
|
{
|
|
_selectedType = type;
|
|
_selectedAsset = null;
|
|
_newAssetName = "New " + type.Name;
|
|
RefreshAssetList();
|
|
}
|
|
|
|
private void SelectAsset(ScriptableObject asset)
|
|
{
|
|
_selectedAsset = asset;
|
|
GUI.FocusControl(null);
|
|
}
|
|
|
|
private void RefreshAssetList()
|
|
{
|
|
if (_selectedType == null) return;
|
|
_assetList.Clear();
|
|
|
|
// 查找资源
|
|
string[] guids = AssetDatabase.FindAssets($"t:{_selectedType.Name}", new[] { _searchRootPath });
|
|
|
|
foreach (var guid in guids)
|
|
{
|
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
|
var asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
|
|
|
|
// 严格类型检查 (支持继承)
|
|
if (asset != null && _selectedType.IsAssignableFrom(asset.GetType()))
|
|
{
|
|
if (string.IsNullOrEmpty(_searchString) || asset.name.ToLower().Contains(_searchString.ToLower()))
|
|
{
|
|
_assetList.Add(asset);
|
|
}
|
|
}
|
|
}
|
|
|
|
_assetList.Sort((a, b) => string.Compare(a.name, b.name, StringComparison.Ordinal));
|
|
}
|
|
|
|
private void CreateAsset()
|
|
{
|
|
if (_selectedType == null) return;
|
|
|
|
string targetFolder = $"{_searchRootPath}/{_selectedType.Name}";
|
|
if (!AssetDatabase.IsValidFolder(targetFolder))
|
|
{
|
|
if (AssetDatabase.IsValidFolder(_searchRootPath))
|
|
AssetDatabase.CreateFolder(_searchRootPath, _selectedType.Name);
|
|
else
|
|
targetFolder = _searchRootPath;
|
|
}
|
|
|
|
string fullPath = $"{targetFolder}/{_newAssetName}.asset";
|
|
fullPath = AssetDatabase.GenerateUniqueAssetPath(fullPath);
|
|
|
|
var instance = ScriptableObject.CreateInstance(_selectedType);
|
|
AssetDatabase.CreateAsset(instance, fullPath);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
AssetDatabase.Refresh();
|
|
RefreshAssetList();
|
|
SelectAsset(instance);
|
|
}
|
|
|
|
private void DuplicateAsset(ScriptableObject asset)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(asset);
|
|
string newPath = AssetDatabase.GenerateUniqueAssetPath(path);
|
|
AssetDatabase.CopyAsset(path, newPath);
|
|
AssetDatabase.SaveAssets();
|
|
RefreshAssetList();
|
|
}
|
|
|
|
private void DeleteAsset(ScriptableObject asset)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(asset);
|
|
AssetDatabase.DeleteAsset(path);
|
|
AssetDatabase.SaveAssets();
|
|
if (_selectedAsset == asset) _selectedAsset = null;
|
|
RefreshAssetList();
|
|
}
|
|
|
|
private void RenameAsset(ScriptableObject asset, string newName)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(asset);
|
|
AssetDatabase.RenameAsset(path, newName);
|
|
AssetDatabase.SaveAssets();
|
|
RefreshAssetList();
|
|
}
|
|
} |