230 lines
11 KiB
C#
230 lines
11 KiB
C#
#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()")]
|
||
|
||
/// <summary>
|
||
/// 为 [ValueDropdown] 提供所有 CharacterLogicBase 子类的层级下拉项。
|
||
/// 路径格式为 "ModName/Category/ClassName",与旧 TypeSelectorWindow 分组逻辑一致。
|
||
/// 此方法为 static,故在 [ValueDropdown] 中使用 @ 表达式语法引用。
|
||
/// </summary>
|
||
public static IEnumerable<ValueDropdownItem<string>> GetCharacterLogicDropdownItems()
|
||
{
|
||
const string namespacePrefix = "Continentis.Mods";
|
||
const string namespaceToRemove = "Characters";
|
||
|
||
IEnumerable<Type> 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<string> 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<string>(path, type.FullName ?? type.Name);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当 classFullName 通过下拉菜单改变后,自动填充 modName / className。
|
||
/// 等价于旧 CharacterDataEditor 中 DrawSearchableTypeSelector 的 onTypeSelected 回调。
|
||
/// 被 CharacterData.cs 中字段上的 [OnValueChanged("OnCharacterLogicClassSelected")] 触发。
|
||
/// </summary>
|
||
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<T>(prop)
|
||
|
||
private IEnumerable<ValueDropdownItem<string>> GetAvailablePrefabs()
|
||
=> GetAssetNameDropdown("t:Prefab");
|
||
|
||
private IEnumerable<ValueDropdownItem<string>> GetAvailableCardData()
|
||
=> GetAssetNameDropdown("t:CardData");
|
||
|
||
private IEnumerable<ValueDropdownItem<string>> GetAvailableCharacterData()
|
||
=> GetAssetNameDropdown("t:CharacterData");
|
||
|
||
private IEnumerable<ValueDropdownItem<string>> 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<string, float, CharacterAttributePair>();
|
||
if (runtimeGeneralAttributes == null) runtimeGeneralAttributes = new SLSUtilities.General.SerializedDictionary<string, string>();
|
||
|
||
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} 个运行时约束!");
|
||
}
|
||
|
||
// ── 内部共享辅助方法 ─────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 通过 AssetDatabase 搜索特定类型/标签的资产,将文件名(无扩展名)作为下拉项返回。
|
||
/// 用于 References 列表的字符串引用选择。
|
||
/// </summary>
|
||
private static IEnumerable<ValueDropdownItem<string>> 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<string>(name, name);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif |