架构大更
This commit is contained in:
@@ -1,158 +1,299 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Continentis.MainGame.Base;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSFramework.General;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using SLSFramework.UModAssistance;
|
||||
using Continentis.MainGame.Character;
|
||||
using Continentis.Mods;
|
||||
|
||||
namespace Continentis.MainGame.Card
|
||||
{
|
||||
[CustomEditor(typeof(CardData))]
|
||||
public partial class CardDataEditor : DataEditor
|
||||
// =========================================================================
|
||||
// CardDataEditor
|
||||
//
|
||||
// 注意:此类已不再使用 [CustomEditor] 注解。
|
||||
// Odin Inspector 会自动接管 CardData(ScriptableObject)的 Inspector 渲染,
|
||||
// 无需任何自定义 Editor 类。本文件现主要用于承载 CardData 的编辑器扩展分部类。
|
||||
// 如需将来添加 OdinEditorWindow 工具或其他编辑器工具类,可在此文件中继续扩展。
|
||||
// =========================================================================
|
||||
internal static class CardDataEditorPlaceholder
|
||||
{
|
||||
// 存储我们需要自定义绘制的属性的引用
|
||||
private SerializedProperty modNameProp;
|
||||
private SerializedProperty categoryNameProp;
|
||||
private SerializedProperty classNameProp;
|
||||
private SerializedProperty displayNameProp;
|
||||
private SerializedProperty cardRarityProp;
|
||||
private SerializedProperty cardTypeProp;
|
||||
private SerializedProperty keywordsProp;
|
||||
|
||||
private SerializedProperty cardSpriteProp;
|
||||
private SerializedProperty cardLayoutTagsProp;
|
||||
private SerializedProperty functionTextProp;
|
||||
private SerializedProperty cardDescriptionProp;
|
||||
|
||||
private SerializedProperty intentionIconKeysProp;
|
||||
private SerializedProperty intentionValueNamesProp;
|
||||
private SerializedProperty intentionTextOverrideProp;
|
||||
private SerializedProperty baseWeightProp;
|
||||
|
||||
|
||||
private SerializedProperty variableAttributesProp;
|
||||
private SerializedProperty originalAttributesProp;
|
||||
private SerializedProperty runtimeCurrentAttributesProp;
|
||||
|
||||
private SerializedProperty upgradeNodeProp;
|
||||
|
||||
private SerializedProperty prefabsProp;
|
||||
private SerializedProperty derivativeCardsProp;
|
||||
private SerializedProperty derivativeCharactersProp;
|
||||
|
||||
protected override void OnEnable()
|
||||
// 保留此类以维持文件存在意义,可在此添加未来的编辑器工具方法。
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// partial class CardData — 编辑器专属扩展
|
||||
//
|
||||
// 包含:
|
||||
// 1. Odin ValueDropdown 数据提供方法
|
||||
// 2. OnValueChanged 回调(自动填充 modName / categoryName / 文本Key)
|
||||
// 3. DrawCardSpriteField() — 16:9 自定义预览绘制器
|
||||
// 4. PasteDefaultAttributes() — 粘贴默认属性模板按钮
|
||||
// 5. OpenCardPreviewer() — 打开 CardPreviewer 工具窗口按钮
|
||||
// 6. 内部共享辅助方法(AssetDatabase 查询)
|
||||
// =========================================================================
|
||||
public partial class CardData
|
||||
{
|
||||
// ── 1. 卡牌逻辑类选择器 ───────────────────────────────────────────────
|
||||
// 替代旧 CardDataEditor 中的 DrawSearchableTypeSelector()
|
||||
// 配合 CardData.cs 中字段上的 [ValueDropdown("@CardData.GetCardLogicDropdownItems()")]
|
||||
|
||||
/// <summary>
|
||||
/// 为 [ValueDropdown] 提供所有 CardLogicBase 子类的层级下拉项。
|
||||
/// 路径格式为 "ModName/Category/ClassName",与旧 TypeSelectorWindow 分组逻辑一致。
|
||||
/// 此方法为 static,故在 [ValueDropdown] 中使用 @ 表达式语法引用。
|
||||
/// </summary>
|
||||
public static IEnumerable<ValueDropdownItem<string>> GetCardLogicDropdownItems()
|
||||
{
|
||||
base.OnEnable();
|
||||
// 在启用时,根据我们修改后的字段名找到对应的SerializedProperty
|
||||
modNameProp = serializedObject.FindProperty("modName");
|
||||
classNameProp = serializedObject.FindProperty("className");
|
||||
categoryNameProp = serializedObject.FindProperty("categoryName");
|
||||
displayNameProp = serializedObject.FindProperty("displayName");
|
||||
cardRarityProp = serializedObject.FindProperty("cardRarity");
|
||||
cardTypeProp = serializedObject.FindProperty("cardType");
|
||||
keywordsProp = serializedObject.FindProperty("keywords");
|
||||
cardSpriteProp = serializedObject.FindProperty("cardSprite");
|
||||
cardLayoutTagsProp = serializedObject.FindProperty("cardLayoutTags");
|
||||
functionTextProp = serializedObject.FindProperty("functionText");
|
||||
cardDescriptionProp = serializedObject.FindProperty("cardDescription");
|
||||
|
||||
intentionIconKeysProp = serializedObject.FindProperty("intentionIconKeys");
|
||||
intentionValueNamesProp = serializedObject.FindProperty("intentionValueNames");
|
||||
intentionTextOverrideProp = serializedObject.FindProperty("intentionTextOverride");
|
||||
baseWeightProp = serializedObject.FindProperty("baseWeight");
|
||||
|
||||
variableAttributesProp = serializedObject.FindProperty("variableAttributes");
|
||||
originalAttributesProp = serializedObject.FindProperty("originalAttributes");
|
||||
runtimeCurrentAttributesProp = serializedObject.FindProperty("runtimeCurrentAttributes");
|
||||
upgradeNodeProp = serializedObject.FindProperty("upgradeNode");
|
||||
prefabsProp = serializedObject.FindProperty("prefabRefs");
|
||||
derivativeCardsProp = serializedObject.FindProperty("derivativeCardDataRefs");
|
||||
derivativeCharactersProp = serializedObject.FindProperty("derivativeCharacterDataRefs");
|
||||
const string namespacePrefix = "Continentis.Mods";
|
||||
const string namespaceToRemove = "Cards";
|
||||
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return a.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Type.EmptyTypes;
|
||||
}
|
||||
})
|
||||
.Where(t => typeof(CardLogicBase).IsAssignableFrom(t)
|
||||
&& !t.IsAbstract
|
||||
&& !t.IsInterface
|
||||
&& t != typeof(CardLogicBase));
|
||||
|
||||
foreach (var type in types.OrderBy(t => t.FullName))
|
||||
{
|
||||
string path;
|
||||
|
||||
if (type.Namespace != null && type.Namespace.StartsWith(namespacePrefix))
|
||||
{
|
||||
// 去掉公共前缀,拆分剩余命名空间段落
|
||||
var 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;
|
||||
}
|
||||
|
||||
yield return new ValueDropdownItem<string>(path, type.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
/// <summary>
|
||||
/// 当 className 通过下拉菜单改变后,自动填充 modName / categoryName / displayName / functionText。
|
||||
/// 等价于旧 CardDataEditor 中 DrawSearchableTypeSelector 的 onTypeSelected 回调。
|
||||
/// 被 CardData.cs 中字段上的 [OnValueChanged("OnCardLogicClassSelected")] 触发。
|
||||
/// </summary>
|
||||
private void OnCardLogicClassSelected()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// --- 绘制自定义的Type选择器 ---
|
||||
// 我们把它从所有自动绘制的属性中分离出来,放在最前面或最后面,让布局更清晰
|
||||
EditorGUILayout.Space(); // 增加一点间距
|
||||
EditorGUILayout.LabelField("Fundamental", EditorStyles.boldLabel);
|
||||
|
||||
DrawSearchableTypeSelector(
|
||||
classNameProp, // 1. 存储类名的字符串属性
|
||||
"Card Logic Class", // 2. 显示的标签
|
||||
typeof(CardLogicBase), // 3. 要搜索的基类
|
||||
(outType) => // 4. 【关键】当用户选择后执行的回调
|
||||
{
|
||||
string className = outType.Name;
|
||||
string modName = outType.Namespace!.Replace("Continentis.Mods.", "").Split('.')[0];
|
||||
string categoryName = outType.Namespace!.Replace("Continentis.Mods." + modName + ".Cards", "");
|
||||
if (!string.IsNullOrEmpty(categoryName))
|
||||
{
|
||||
categoryName = outType.Namespace!.Replace("Continentis.Mods." + modName + ".Cards", "").Substring(1); // 去掉开头的点
|
||||
}
|
||||
string displayName = "Card_" + modName + "_" + className + "_DisplayName";
|
||||
string functionTextName = "Card_" + modName + "_" + className + "_FunctionText";
|
||||
|
||||
classNameProp.stringValue = className;
|
||||
modNameProp.stringValue = modName;
|
||||
categoryNameProp.stringValue = categoryName;
|
||||
displayNameProp.stringValue = displayName;
|
||||
functionTextProp.stringValue = functionTextName;
|
||||
|
||||
Debug.Log(outType.FullName);
|
||||
Debug.Log($"modName: {modName}, categoryName: {categoryName}, className: {className}, displayName: {displayName}, functionText: {functionTextName}");
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
},
|
||||
"Continentis.Mods", // 5. 你的 namespacePrefix
|
||||
"Cards" // 6. 你的 namespaceToRemove (注意:根据你的代码,这里不应包含".")
|
||||
);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.PropertyField(modNameProp);
|
||||
EditorGUILayout.PropertyField(categoryNameProp);
|
||||
EditorGUILayout.PropertyField(classNameProp);
|
||||
EditorGUILayout.PropertyField(displayNameProp);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.PropertyField(cardRarityProp);
|
||||
EditorGUILayout.PropertyField(cardTypeProp);
|
||||
DrawListWithEditRefSelector(keywordsProp, "CardKeywords");
|
||||
|
||||
EditorGUILayout.PropertyField(cardSpriteProp);
|
||||
EditorGUILayout.PropertyField(cardLayoutTagsProp, true);
|
||||
EditorGUILayout.PropertyField(functionTextProp);
|
||||
EditorGUILayout.PropertyField(cardDescriptionProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Attributes", EditorStyles.boldLabel);
|
||||
DrawListWithEditRefSelector(intentionIconKeysProp, "IntentionIconKeys");
|
||||
DrawListWithLocalSelector(intentionValueNamesProp, "variableAttributes");
|
||||
EditorGUILayout.PropertyField(intentionTextOverrideProp);
|
||||
EditorGUILayout.PropertyField(baseWeightProp);
|
||||
EditorGUILayout.PropertyField(variableAttributesProp, true);
|
||||
EditorGUILayout.PropertyField(originalAttributesProp, true);
|
||||
EditorGUILayout.PropertyField(runtimeCurrentAttributesProp, true);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Upgrade", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(upgradeNodeProp);
|
||||
|
||||
// --- 绘制自定义的引用列表 ---
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("References", EditorStyles.boldLabel);
|
||||
DrawCharacterListGUI<GameObject>(prefabsProp);
|
||||
DrawCharacterListGUI<CardData>(derivativeCardsProp);
|
||||
DrawCharacterListGUI<CharacterData>(derivativeCharactersProp);
|
||||
if (string.IsNullOrEmpty(className)) return;
|
||||
|
||||
HandleObjectPicker();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
// 根据 className 反查对应 Type(仅首个匹配)
|
||||
var selectedType = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return a.GetTypes();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Type.EmptyTypes;
|
||||
}
|
||||
})
|
||||
.FirstOrDefault(t =>
|
||||
t.Name == className
|
||||
&& typeof(CardLogicBase).IsAssignableFrom(t)
|
||||
&& !t.IsAbstract);
|
||||
|
||||
if (selectedType?.Namespace == null) return;
|
||||
|
||||
const string prefix = "Continentis.Mods.";
|
||||
var ns = selectedType.Namespace;
|
||||
if (!ns.StartsWith(prefix)) return;
|
||||
|
||||
// 解析 Mod 名与分类名
|
||||
// 例:ns = "Continentis.Mods.Basic.Cards.Assassin"
|
||||
// → afterPrefix = "Basic.Cards.Assassin"
|
||||
// → parts = ["Basic", "Cards", "Assassin"]
|
||||
var afterPrefix = ns.Substring(prefix.Length);
|
||||
var parts = afterPrefix.Split('.');
|
||||
var resolvedMod = parts.Length > 0 ? parts[0] : string.Empty;
|
||||
|
||||
// 去掉 "{ModName}.Cards" 后的剩余部分即为 categoryName
|
||||
var cardsSegment = resolvedMod + ".Cards";
|
||||
var resolvedCategory = ns.Contains(cardsSegment)
|
||||
? ns.Replace(prefix + cardsSegment, "").TrimStart('.')
|
||||
: string.Empty;
|
||||
|
||||
modName = resolvedMod;
|
||||
categoryName = resolvedCategory;
|
||||
displayName = $"Card_{resolvedMod}_{className}_DisplayName";
|
||||
functionText = $"Card_{resolvedMod}_{className}_FunctionText";
|
||||
|
||||
EditorUtility.SetDirty(this);
|
||||
|
||||
Debug.Log($"[CardData] 已自动填充 → modName: {modName}, categoryName: {categoryName}, " +
|
||||
$"className: {className}, displayName: {displayName}, functionText: {functionText}");
|
||||
}
|
||||
|
||||
// ── 2. 关键词下拉 ─────────────────────────────────────────────────────
|
||||
// 从所有 EditorBaseCollection 的 cardKeywords 字典聚合 Key 列表
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetAvailableKeywords()
|
||||
{
|
||||
return EditorBaseCollection.GetCardKeywordsDropdown();
|
||||
}
|
||||
|
||||
// ── 3. 意图图标下拉 ───────────────────────────────────────────────────
|
||||
// 从所有 EditorBaseCollection 的 intentionIcons 字典聚合 Key 列表
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetAvailableIntentionIcons()
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var coll in EditorBaseCollection.GetAllCollections())
|
||||
{
|
||||
if (coll.intentionIcons == null) continue;
|
||||
foreach (var key in coll.intentionIcons.Keys)
|
||||
if (seen.Add(key))
|
||||
yield return new ValueDropdownItem<string>(key, key);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. intentionValueNames 候选(来自 variableAttributes 的 Key)────────
|
||||
// 替代旧 DrawListWithLocalSelector(intentionValueNamesProp, "variableAttributes")
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetVariableAttributeKeys()
|
||||
{
|
||||
if (variableAttributes == null) yield break;
|
||||
foreach (var key in variableAttributes.Keys)
|
||||
yield return new ValueDropdownItem<string>(key, key);
|
||||
}
|
||||
|
||||
// ── 5. References 列表的资产名称选择器 ──────────────────────────────────
|
||||
// 替代旧 DrawCharacterListGUI<T>(prop)
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetAvailablePrefabs()
|
||||
{
|
||||
return GetAssetNameDropdown("t:Prefab");
|
||||
}
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetAvailableCardData()
|
||||
{
|
||||
return GetAssetNameDropdown("t:CardData");
|
||||
}
|
||||
|
||||
private IEnumerable<ValueDropdownItem<string>> GetAvailableCharacterData()
|
||||
{
|
||||
return GetAssetNameDropdown("t:CharacterData");
|
||||
}
|
||||
|
||||
|
||||
[BoxGroup("Display")]
|
||||
[PropertyOrder(11)]
|
||||
[Button("✨ 预览卡牌效果", ButtonSizes.Medium)]
|
||||
[GUIColor(0.5f, 0.8f, 1f)]
|
||||
private void OpenCardPreviewer()
|
||||
{
|
||||
CardPreviewer.OpenWith(this);
|
||||
}
|
||||
|
||||
[BoxGroup("Data/属性/工具")]
|
||||
[PropertyOrder(35)]
|
||||
[Button("粘贴默认属性", ButtonSizes.Medium)]
|
||||
[GUIColor(0.7f, 1f, 0.7f)]
|
||||
private void PasteDefaultAttributes()
|
||||
{
|
||||
var targetCollections = new List<CardAttributesDefaultCollection>();
|
||||
var guids = AssetDatabase.FindAssets("t:CardAttributesDefaultCollection");
|
||||
|
||||
if (guids.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[CardData] 未找到任何 CardAttributesDefaultCollection 资产。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (guids.Length > 1) Debug.Log("[CardData] 找到多个 CardAttributesDefaultCollection,将合并后导入。");
|
||||
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var directory = Path.GetDirectoryName(path)?.Replace('\\', '/');
|
||||
|
||||
var collection =
|
||||
AssetDatabase.LoadAssetAtPath<CardAttributesDefaultCollection>(path);
|
||||
|
||||
if (collection == null) continue;
|
||||
|
||||
// 只收集位于 Assets/Mods/{ModName}/Cards/DefaultCollections/ 路径下的资产
|
||||
if (!string.IsNullOrEmpty(directory)
|
||||
&& directory.StartsWith("Assets/Mods/")
|
||||
&& directory.EndsWith("/Cards/DefaultCollections"))
|
||||
{
|
||||
targetCollections.Add(collection);
|
||||
Debug.Log($"[CardData] 已加载默认集合:{path}");
|
||||
}
|
||||
}
|
||||
|
||||
if (targetCollections.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[CardData] 未在 Assets/Mods/{ModName}/Cards/DefaultCollections/ " +
|
||||
"路径下找到有效的 CardAttributesDefaultCollection 资产。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并各 Collection 中的数据
|
||||
var merged_variable = new Dictionary<string, float>();
|
||||
var merged_original = new Dictionary<string, float>();
|
||||
// Collection 端字段名仍为 endowingCurrentAttributes(CardData 端已用 [FormerlySerializedAs] 改名)
|
||||
var merged_runtime = new Dictionary<string, string>();
|
||||
|
||||
foreach (var collection in targetCollections)
|
||||
{
|
||||
collection.variableAttributes?.PasteDictionary(merged_variable);
|
||||
collection.originalAttributes?.PasteDictionary(merged_original);
|
||||
collection.endowingCurrentAttributes?.PasteDictionary(merged_runtime);
|
||||
}
|
||||
|
||||
// 写入当前 CardData
|
||||
merged_variable.PasteDictionary(variableAttributes);
|
||||
merged_original.PasteDictionary(originalAttributes);
|
||||
merged_runtime.PasteDictionary(runtimeCurrentAttributes);
|
||||
|
||||
EditorUtility.SetDirty(this);
|
||||
Debug.Log($"[CardData] 已将默认属性粘贴至 '{name}'。");
|
||||
}
|
||||
|
||||
// ── 内部共享辅助方法 ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 通过 AssetDatabase 搜索特定类型/标签的资产,将文件名(无扩展名)作为下拉项返回。
|
||||
/// 用于 References 列表的字符串引用选择。
|
||||
/// </summary>
|
||||
private static IEnumerable<ValueDropdownItem<string>> GetAssetNameDropdown(string searchFilter)
|
||||
{
|
||||
var guids = AssetDatabase.FindAssets(searchFilter);
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
yield return new ValueDropdownItem<string>(name, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user