using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; namespace Cielonos.MainGame.Inventory { /// /// 存储连招树定义的 ScriptableObject。 /// 这是你的“蓝图”资产。 /// [CreateAssetMenu(fileName = "ComboData", menuName = "Cielonos/Items/ComboData")] public class ComboData : ScriptableObject { public float defaultDisappearTime; [InlineProperty] [HideLabel] public ComboTreeData tree; [OnInspectorInit] private void OnEnable() { defaultDisappearTime = 0.25f; tree ??= new ComboTreeData(); // 确保总有一个 RootNode if (tree.nodes == null || tree.nodes.Count == 0) { tree.nodes = new List(); // 添加不可删除的 RootNode tree.nodes.Add(new ComboNodeData() { referenceName = "Root" }); } // 关键一步:将树实例传递给所有子节点,以便它们能填充下拉列表 tree.InitializeNodeReferences(); } } /// /// 连招树的数据容器 /// [Serializable] public class ComboTreeData { // --- 修改点 --- // 将标题移到这里,使其在内联属性的顶部显示 [Title("连招树编辑器", "可视化编辑连招树的节点和分支")] // --- 修改结束 --- [ListDrawerSettings( DraggableItems = true, // 现在可以安全地拖动排序了! NumberOfItemsPerPage = 20, CustomAddFunction = "AddNode", CustomRemoveElementFunction = "RemoveNode")] public List nodes; /// /// Odin Inspector 自定义添加按钮的实现 /// private void AddNode() { // 查找一个唯一的默认名称 int counter = nodes.Count - 1; string newName = counter.ToString(); while (nodes.Any(n => n.referenceName == newName)) { counter++; newName = counter.ToString(); } var newNode = new ComboNodeData() { referenceName = newName }; nodes.Add(newNode); // 添加后,立即初始化引用 InitializeNodeReferences(); } /// /// Odin Inspector 自定义移除按钮的实现 /// private void RemoveNode(ComboNodeData node) { // 规则:不可删除 RootNode if (node.referenceName == "Root") { Debug.LogWarning("不能删除 Root 节点。"); return; } nodes.Remove(node); InitializeNodeReferences(); // 移除后,更新引用 } /// /// 将父树实例传递给所有节点(和分支),用于Odin下拉列表 /// public void InitializeNodeReferences() { if (nodes == null) return; } /// /// 帮助 Odin 下拉列表获取所有可用节点名称 /// public IEnumerable GetNodeReferenceNames() { if (nodes == null) return Enumerable.Empty(); return nodes.Select(n => n.referenceName).Where(n => !string.IsNullOrEmpty(n)); } public ComboSubmodule.ComboTree ToRuntime() { var runtimeTree = new ComboSubmodule.ComboTree(); runtimeTree.nodes = new List(); // 创建运行时节点 foreach (var nodeData in nodes) { var runtimeNode = new ComboSubmodule.ComboTree.Node(nodes.IndexOf(nodeData), nodeData.referenceName); runtimeNode.branches = new List(); // 创建运行时分支 foreach (var branchData in nodeData.branches) { var runtimeBranch = new ComboSubmodule.ComboTree.Branch(nodes.FindIndex(n => n.referenceName == branchData.nextNodeRefName), branchData.operation); runtimeNode.branches.Add(runtimeBranch); } runtimeTree.nodes.Add(runtimeNode); } return runtimeTree; } } /// /// 连招节点的数据定义 /// [Serializable] public class ComboNodeData { [ValidateInput("ValidateReferenceName", "引用名称(Reference Name)必须唯一且不能为空。")] [InfoBox("这是 Root 节点,是所有连招的起点。", InfoMessageType.Info, "IsRootNode")] public string referenceName; [ListDrawerSettings(NumberOfItemsPerPage = 5)] public List branches = new List(); private bool IsRootNode() => referenceName == "Root"; private bool ValidateReferenceName(string name, ref string errorMessage) { if (string.IsNullOrEmpty(name)) { errorMessage = "引用名称不能为空。"; return false; } return true; } } /// /// 连招分支的数据定义 /// [Serializable] public class ComboBranchData { [Tooltip("触发此分支的输入操作,例如 'L', 'R', 'Attack'")] [HorizontalGroup("Branch", 100)] [LabelWidth(60)] public string operation; [Tooltip("此分支链接到的下一个节点")] [HorizontalGroup("Branch")] [LabelText("下一个节点")] public string nextNodeRefName; // <-- 使用名称引用,而不是索引 // --- Odin 辅助字段和方法 --- } }