chore(huoshang): 移除 SOEditorTool 插件,SellSystem 重命名为 SaleSystem

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-12 20:58:01 +08:00
co-authored by Cursor
parent 1b3c5ee838
commit 4ee9cc647f
17 changed files with 5660 additions and 3822 deletions
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 203b6470a52cede43824c2b766b89464
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1b33e012f04c4b66890fad3401d1abd1
timeCreated: 1765258694
@@ -1,11 +0,0 @@
using UnityEngine;
public class CNNameAttribute : PropertyAttribute
{
public string Name;
public CNNameAttribute(string name)
{
this.Name = name;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 251a6a4e8eda4e9bbfa088b9a34b0e7b
timeCreated: 1765258702
-3
View File
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 108f43113da2467789e5bef2327add79
timeCreated: 1765215242
@@ -1,39 +0,0 @@
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(CNNameAttribute))]
public class CNNameDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// 1. 获取特性中的中文名
CNNameAttribute attr = (CNNameAttribute)attribute;
// 2. 替换原本的 Label
GUIContent newLabel = new GUIContent(attr.Name, label.tooltip);
// 3. 绘制属性(使用中文名)
EditorGUI.PropertyField(position, property, newLabel, true);
// 4. 绘制变量原名(灰色小字,显示在 Label 区域的右侧)
// 计算 Label 的区域
float labelWidth = EditorGUIUtility.labelWidth;
Rect labelRect = new Rect(position.x, position.y, labelWidth, position.height);
// 设置小字样式
GUIStyle subStyle = new GUIStyle(EditorStyles.miniLabel);
subStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f, 0.6f); // 半透明灰色
subStyle.alignment = TextAnchor.MiddleRight; // 右对齐
subStyle.fontSize = 9;
subStyle.padding = new RectOffset(0, 2, 0, 0); //稍微留点边距
// 绘制变量名 (例如: "maxHP")
GUI.Label(labelRect, property.name, subStyle);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label);
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 0c0f975bcc144b9187928b83dc9aaeac
timeCreated: 1765258716
@@ -1,164 +0,0 @@
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Collections.Generic;
[CustomEditor(typeof(SOEditorConfig))]
public class SOEditorConfigEditor : Editor
{
private SerializedProperty _typeNamesProp;
private string _searchText = "";
private void OnEnable()
{
_typeNamesProp = serializedObject.FindProperty("targetTypeNames");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
SOEditorConfig config = (SOEditorConfig)target;
GUILayout.Label("已管理的 SO 类型:", EditorStyles.boldLabel);
// === 1. 绘制已添加的类型列表 ===
EditorGUILayout.BeginVertical("box");
for (int i = 0; i < _typeNamesProp.arraySize; i++)
{
SerializedProperty prop = _typeNamesProp.GetArrayElementAtIndex(i);
string typeName = prop.stringValue;
EditorGUILayout.BeginHorizontal();
{
GUILayout.Label($"{i + 1}. {typeName}", EditorStyles.label);
if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash"), EditorStyles.iconButton, GUILayout.Width(25)))
{
_typeNamesProp.DeleteArrayElementAtIndex(i);
break;
}
}
EditorGUILayout.EndHorizontal();
}
if (_typeNamesProp.arraySize == 0)
{
GUILayout.Label("暂无类型,请添加", EditorStyles.centeredGreyMiniLabel);
}
EditorGUILayout.EndVertical();
GUILayout.Space(10);
// === 2. 拖拽区域 (核心修改) ===
DrawDragDropArea();
GUILayout.Space(10);
// === 3. 搜索添加 ===
GUILayout.Label("或者:搜索添加", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
_searchText = EditorGUILayout.TextField(_searchText, EditorStyles.toolbarSearchField);
if (GUILayout.Button("搜索", GUILayout.Width(60)))
{
ShowTypeSelectorMenu(config);
}
EditorGUILayout.EndHorizontal();
serializedObject.ApplyModifiedProperties();
}
private void DrawDragDropArea()
{
// 绘制一个虚线框区域
Rect dropArea = GUILayoutUtility.GetRect(0.0f, 60.0f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "\n拖拽文件到这里添加\n(支持 .cs 脚本 或 .asset 资源)", EditorStyles.helpBox);
Event evt = Event.current;
if (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform)
{
if (!dropArea.Contains(evt.mousePosition)) return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (UnityEngine.Object dragged_object in DragAndDrop.objectReferences)
{
Type typeToAdd = null;
// 情况 A: 拖入的是 .cs 脚本文件
if (dragged_object is MonoScript script)
{
typeToAdd = script.GetClass();
}
// 情况 B: 拖入的是 .asset 资源实例 (比如 New Tank.asset)
else if (dragged_object is ScriptableObject soInstance)
{
// 直接获取这个实例的类型
typeToAdd = soInstance.GetType();
}
// 执行添加逻辑
if (typeToAdd != null)
{
// 过滤掉 Config 本身,防止把自己加进去
if (typeToAdd == typeof(SOEditorConfig)) continue;
if (typeToAdd.IsSubclassOf(typeof(ScriptableObject)))
{
AddType(typeToAdd.FullName);
}
}
}
}
}
}
private void ShowTypeSelectorMenu(SOEditorConfig config)
{
GenericMenu menu = new GenericMenu();
var allSoTypes = TypeCache.GetTypesDerivedFrom<ScriptableObject>()
.Where(t => !t.IsAbstract && !t.IsGenericType && !string.IsNullOrEmpty(t.Namespace) && !t.Namespace.StartsWith("Unity") && !t.Namespace.StartsWith("UnityEditor"))
.OrderBy(t => t.Name);
foreach (var type in allSoTypes)
{
if (!string.IsNullOrEmpty(_searchText) && !type.Name.ToLower().Contains(_searchText.ToLower()))
continue;
string menuPath = string.IsNullOrEmpty(type.Namespace) ? type.Name : $"{type.Namespace}/{type.Name}";
menu.AddItem(new GUIContent(menuPath), config.targetTypeNames.Contains(type.FullName), () => { AddType(type.FullName); });
}
menu.ShowAsContext();
}
private void AddType(string typeFullName)
{
serializedObject.Update();
bool exists = false;
for (int i = 0; i < _typeNamesProp.arraySize; i++)
{
if (_typeNamesProp.GetArrayElementAtIndex(i).stringValue == typeFullName)
{
exists = true;
break;
}
}
if (!exists)
{
_typeNamesProp.InsertArrayElementAtIndex(_typeNamesProp.arraySize);
_typeNamesProp.GetArrayElementAtIndex(_typeNamesProp.arraySize - 1).stringValue = typeFullName;
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 45f4877471fd47efb465a700a6869eb4
timeCreated: 1765219950
@@ -1,428 +0,0 @@
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();
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 70dc192513964637860b748b8957f0f6
timeCreated: 1765215245
@@ -1,9 +0,0 @@
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "SOEditorConfig", menuName = "Tools/SO编辑器配置 (Config)")]
public class SOEditorConfig : ScriptableObject
{
// 存储类型的全名 (例如 "MyGame.TankEngine")
public List<string> targetTypeNames = new List<string>();
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b9d2ad94e1454d91833b3134b5c927bd
timeCreated: 1765218978
@@ -2436,7 +2436,7 @@ GameObject:
m_Component:
- component: {fileID: 429189693013891242}
m_Layer: 0
m_Name: SellSystem
m_Name: SaleSystem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,4 +1,4 @@
# CLAUDE.md
<!-- # CLAUDE.md
Claude Code 与 Cursor 的项目上下文,对话开始时自动加载。
@@ -171,4 +171,4 @@ Assets\Open C# Project.regenerate-sln.bat
- 火山语言粒子是核心玩法,改动需保持情绪表达逻辑
- Web 原型仅供验证,正式实现用 Unity/C#
- 保持 Windows 与 WebGL 兼容
- **开发前按需读 `Docs/` 下对应索引,避免与设计偏离**
- **开发前按需读 `Docs/` 下对应索引,避免与设计偏离** -->