#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Linq; using Sirenix.OdinInspector; using SLSFramework.General; using UnityEditor; using UnityEngine; namespace Continentis.MainGame.Character { // ========================================================================= // CharacterDataEditor // // 注意:此类已不再使用 [CustomEditor] 注解。 // Odin Inspector 会自动接管 CharacterData(ScriptableObject)的 Inspector 渲染, // 无需任何自定义 Editor 类。本文件现主要用于承载 CharacterData 的编辑器扩展分部类。 // ========================================================================= internal static class CharacterDataEditorPlaceholder { // 保留此类以维持文件存在意义,可在此添加未来的编辑器工具方法。 } // ========================================================================= // partial class CharacterData — 编辑器专属扩展 // // 包含: // 1. GetCharacterLogicDropdownItems() — 角色逻辑类下拉列表(静态) // 2. OnCharacterLogicClassSelected() — 选中逻辑类后自动填充字段的回调 // 3. GetAvailable*() — References 列表的资产名称选择器 // 4. PasteDefaultAttributes() — 粘贴默认属性模板按钮 // 5. 内部共享辅助方法(AssetDatabase 查询) // ========================================================================= public partial class CharacterData { // ── 1. 角色逻辑类选择器 ──────────────────────────────────────────────── // 替代旧 CharacterDataEditor 中的 DrawSearchableTypeSelector() // 配合 CharacterData.cs 中 classFullName 字段上的 // [ValueDropdown("@CharacterData.GetCharacterLogicDropdownItems()")] /// /// 为 [ValueDropdown] 提供所有 CharacterLogicBase 子类的层级下拉项。 /// 路径格式为 "ModName/Category/ClassName",与旧 TypeSelectorWindow 分组逻辑一致。 /// 此方法为 static,故在 [ValueDropdown] 中使用 @ 表达式语法引用。 /// public static IEnumerable> GetCharacterLogicDropdownItems() { const string namespacePrefix = "Continentis.Mods"; const string namespaceToRemove = "Characters"; IEnumerable types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch { return Type.EmptyTypes; } }) .Where(t => typeof(CharacterLogicBase).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface && t != typeof(CharacterLogicBase)); foreach (Type type in types.OrderBy(t => t.FullName)) { string path; if (type.Namespace != null && type.Namespace.StartsWith(namespacePrefix)) { List segments = type.Namespace .Substring(namespacePrefix.Length) .Split('.') .Where(s => !string.IsNullOrEmpty(s) && s != namespaceToRemove) .ToList(); path = segments.Count > 0 ? string.Join("/", segments) + "/" + type.Name : type.Name; } else { path = "Uncategorized/" + type.Name; } // value 存储类型的完整名称(FullName),便于在 OnValueChanged 中精确反查 yield return new ValueDropdownItem(path, type.FullName ?? type.Name); } } /// /// 当 classFullName 通过下拉菜单改变后,自动填充 modName / className。 /// 等价于旧 CharacterDataEditor 中 DrawSearchableTypeSelector 的 onTypeSelected 回调。 /// 被 CharacterData.cs 中字段上的 [OnValueChanged("OnCharacterLogicClassSelected")] 触发。 /// private void OnCharacterLogicClassSelected() { if (string.IsNullOrEmpty(classFullName)) return; // 根据 FullName 反查对应 Type Type selectedType = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch { return Type.EmptyTypes; } }) .FirstOrDefault(t => (t.FullName == classFullName || t.Name == classFullName) && typeof(CharacterLogicBase).IsAssignableFrom(t) && !t.IsAbstract); if (selectedType == null) return; const string prefix = "Continentis.Mods."; string ns = selectedType.Namespace ?? string.Empty; if (ns.StartsWith(prefix)) { // 例:ns = "Continentis.Mods.Basic.Characters.Assassin" // → afterPrefix = "Basic.Characters.Assassin" // → parts = ["Basic", "Characters", "Assassin"] string afterPrefix = ns.Substring(prefix.Length); string[] parts = afterPrefix.Split('.'); string resolvedMod = parts.Length > 0 ? parts[0] : string.Empty; string charactersSegment = resolvedMod + ".Characters"; string resolvedCategory = ns.Contains(charactersSegment) ? ns.Replace(prefix + charactersSegment, "").TrimStart('.') : string.Empty; modName = resolvedMod; className = string.IsNullOrEmpty(resolvedCategory) ? selectedType.Name : resolvedCategory + "." + selectedType.Name; } else { modName = string.Empty; className = selectedType.Name; } EditorUtility.SetDirty(this); Debug.Log($"[CharacterData] 已自动填充 → modName: {modName}, className: {className}, " + $"classFullName: {classFullName}"); } // ── 2. References 列表的资产名称选择器 ────────────────────────────────── // 替代旧 DrawCharacterListGUI(prop) private IEnumerable> GetAvailablePrefabs() => GetAssetNameDropdown("t:Prefab"); private IEnumerable> GetAvailableCardData() => GetAssetNameDropdown("t:CardData"); private IEnumerable> GetAvailableCharacterData() => GetAssetNameDropdown("t:CharacterData"); private IEnumerable> GetAvailableHUDData() => GetAssetNameDropdown("t:HUDData"); // ── 3. 粘贴默认属性 ─────────────────────────────────────────────────── [BoxGroup("Data/属性/工具"), PropertyOrder(31)] [Button("粘贴默认属性架构库", ButtonSizes.Medium)] [GUIColor(0.7f, 1f, 0.7f)] public void PasteDefaultAttributes() { if (generalAttributes == null) generalAttributes = new SLSUtilities.General.SerializedDictionary(); if (runtimeGeneralAttributes == null) runtimeGeneralAttributes = new SLSUtilities.General.SerializedDictionary(); generalAttributes.Clear(); runtimeGeneralAttributes.Clear(); var collections = Continentis.MainGame.Base.EditorBaseCollection.GetAllCollections().ToList(); if (collections.Count == 0) { Debug.LogWarning("[CharacterData] 未找到任何 EditorBaseCollection(属性词典),无法提取数据!"); return; } foreach (var coll in collections) { if (coll.characterGeneralAttributes != null) { foreach (var kvp in coll.characterGeneralAttributes) { if (!generalAttributes.ContainsKey(kvp.Key)) { float defaultVal = kvp.Key.Contains("Multiplier") ? 1f : 0f; generalAttributes.Add(kvp.Key, defaultVal); } // 处理 runtimeGeneralAttributes 的特殊绑定 (如寻找含有 Maximum 的关联词条) if (kvp.Key.Contains("Maximum")) { string baseKey = kvp.Key.Replace("Maximum", ""); if (coll.characterGeneralAttributes.ContainsKey(baseKey)) { if (!runtimeGeneralAttributes.ContainsKey(baseKey)) { runtimeGeneralAttributes.Add(baseKey, kvp.Key); } } } } } } EditorUtility.SetDirty(this); Debug.Log($"[CharacterData] '{this.name}' 提取完毕。从 {collections.Count} 个主库中提取了 {generalAttributes.Count} 个属性以及 {runtimeGeneralAttributes.Count} 个运行时约束!"); } // ── 内部共享辅助方法 ───────────────────────────────────────────────── /// /// 通过 AssetDatabase 搜索特定类型/标签的资产,将文件名(无扩展名)作为下拉项返回。 /// 用于 References 列表的字符串引用选择。 /// private static IEnumerable> GetAssetNameDropdown(string searchFilter) { string[] guids = AssetDatabase.FindAssets(searchFilter); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); string name = Path.GetFileNameWithoutExtension(path); yield return new ValueDropdownItem(name, name); } } } } #endif