using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using SoftCircuits.Collections; using UnityEngine; namespace Cielonos.MainGame.Inventory { /// /// 存储连招树定义的 ScriptableObject。 /// 这是你的“蓝图”资产。 /// [CreateAssetMenu(fileName = "ComboData", menuName = "Cielonos/Items/ComboData")] public class ComboData : SerializedScriptableObject { //[InlineProperty] public ComboTreeData tree; [InlineProperty] [Title("连招树编辑器", "可视化编辑连招树的节点和分支")] public Dictionary comboTrees; public ComboTreeData mainTree => comboTrees["Main"]; [OnInspectorInit] private void OnEnable() { if (comboTrees == null) { comboTrees = new Dictionary(); ComboTreeData tree = new ComboTreeData(); if (tree.nodes == null || tree.nodes.Count == 0) { tree.nodes = new List(); tree.nodes.Add(new ComboNodeData() { referenceName = "Root" }); } tree.InitializeNodeReferences(); comboTrees["Main"] = tree; } } } /// /// 连招树的数据容器 /// [Serializable] public class ComboTreeData { public float resetTime = 0.2f; [ListDrawerSettings( DraggableItems = true, // 现在可以安全地拖动排序了! NumberOfItemsPerPage = 20, CustomAddFunction = "AddNode", CustomRemoveElementFunction = "RemoveNode", ListElementLabelName = "GetComboNodeLabel")] [HideReferenceObjectPicker] 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(ComboSubmodule owner) { var runtimeTree = new ComboSubmodule.ComboTree(owner, resetTime); 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 = 10, CustomAddFunction = "AddBranch")] [HideReferenceObjectPicker] 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; } private void AddBranch() { branches.Add(new ComboBranchData()); } private string GetComboNodeLabel() { // 1. 获取当前节点名称 string name = string.IsNullOrEmpty(referenceName) ? "[未命名]" : referenceName; // 2. 如果没有分支,直接返回节点名 if (branches == null || branches.Count == 0) { return name; } // 3. 拼接子节点名称 // 这里使用 Linq 提取所有分支的目标节点名 // 格式示例: "Root - (1, 2, 3)" List targetNodes = branches .Select(b => b == null || string.IsNullOrEmpty(b.nextNodeRefName) ? "?" : $"{b.operation}->{b.nextNodeRefName}").ToList(); return $"{name} - ({string.Join(", ", targetNodes)})"; } } /// /// 连招分支的数据定义 /// [Serializable] public class ComboBranchData { [Tooltip("触发此分支的输入操作,例如 'L', 'R', 'Attack'")] [HorizontalGroup("Branch", 100)] [LabelWidth(60)] public string operation; [Tooltip("此分支链接到的下一个节点")] [HorizontalGroup("Branch")] [LabelText("下一个节点")] public string nextNodeRefName; // <-- 使用名称引用,而不是索引 } }