2025-11-25 08:19:33 -05:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using Sirenix.OdinInspector;
|
2026-01-17 11:35:49 -05:00
|
|
|
|
using SoftCircuits.Collections;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
namespace Cielonos.MainGame.Inventory
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储连招树定义的 ScriptableObject。
|
|
|
|
|
|
/// 这是你的“蓝图”资产。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[CreateAssetMenu(fileName = "ComboData", menuName = "Cielonos/Items/ComboData")]
|
2026-01-17 11:35:49 -05:00
|
|
|
|
public class ComboData : SerializedScriptableObject
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-01-17 11:35:49 -05:00
|
|
|
|
//[InlineProperty] public ComboTreeData tree;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-01-17 11:35:49 -05:00
|
|
|
|
[InlineProperty]
|
|
|
|
|
|
[Title("连招树编辑器", "可视化编辑连招树的节点和分支")]
|
|
|
|
|
|
public Dictionary<string, ComboTreeData> comboTrees;
|
|
|
|
|
|
|
|
|
|
|
|
public ComboTreeData mainTree => comboTrees["Main"];
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
[OnInspectorInit]
|
|
|
|
|
|
private void OnEnable()
|
|
|
|
|
|
{
|
2026-01-17 11:35:49 -05:00
|
|
|
|
if (comboTrees == null)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-01-17 11:35:49 -05:00
|
|
|
|
comboTrees = new Dictionary<string, ComboTreeData>();
|
|
|
|
|
|
ComboTreeData tree = new ComboTreeData();
|
|
|
|
|
|
if (tree.nodes == null || tree.nodes.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
tree.nodes = new List<ComboNodeData>();
|
|
|
|
|
|
tree.nodes.Add(new ComboNodeData() { referenceName = "Root" });
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-01-17 11:35:49 -05:00
|
|
|
|
tree.InitializeNodeReferences();
|
|
|
|
|
|
comboTrees["Main"] = tree;
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连招树的数据容器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class ComboTreeData
|
|
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
public float resetTime = 0.25f;
|
2026-01-17 11:35:49 -05:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
[ListDrawerSettings(
|
|
|
|
|
|
DraggableItems = true, // 现在可以安全地拖动排序了!
|
|
|
|
|
|
NumberOfItemsPerPage = 20,
|
|
|
|
|
|
CustomAddFunction = "AddNode",
|
2026-01-17 11:35:49 -05:00
|
|
|
|
CustomRemoveElementFunction = "RemoveNode",
|
|
|
|
|
|
ListElementLabelName = "GetComboNodeLabel")]
|
|
|
|
|
|
[HideReferenceObjectPicker]
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public List<ComboNodeData> nodes;
|
2026-01-17 11:35:49 -05:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Odin Inspector 自定义添加按钮的实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Odin Inspector 自定义移除按钮的实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void RemoveNode(ComboNodeData node)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 规则:不可删除 RootNode
|
|
|
|
|
|
if (node.referenceName == "Root")
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("不能删除 Root 节点。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nodes.Remove(node);
|
|
|
|
|
|
InitializeNodeReferences(); // 移除后,更新引用
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将父树实例传递给所有节点(和分支),用于Odin下拉列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void InitializeNodeReferences()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nodes == null) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 帮助 Odin 下拉列表获取所有可用节点名称
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IEnumerable<string> GetNodeReferenceNames()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nodes == null) return Enumerable.Empty<string>();
|
|
|
|
|
|
return nodes.Select(n => n.referenceName).Where(n => !string.IsNullOrEmpty(n));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 11:35:49 -05:00
|
|
|
|
public ComboSubmodule.ComboTree ToRuntime(ComboSubmodule owner)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-01-17 11:35:49 -05:00
|
|
|
|
var runtimeTree = new ComboSubmodule.ComboTree(owner, resetTime);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
runtimeTree.nodes = new List<ComboSubmodule.ComboTree.Node>();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建运行时节点
|
|
|
|
|
|
foreach (var nodeData in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var runtimeNode = new ComboSubmodule.ComboTree.Node(nodes.IndexOf(nodeData), nodeData.referenceName);
|
|
|
|
|
|
runtimeNode.branches = new List<ComboSubmodule.ComboTree.Branch>();
|
|
|
|
|
|
// 创建运行时分支
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-01-17 11:35:49 -05:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连招节点的数据定义
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class ComboNodeData
|
|
|
|
|
|
{
|
|
|
|
|
|
[ValidateInput("ValidateReferenceName", "引用名称(Reference Name)必须唯一且不能为空。")]
|
|
|
|
|
|
[InfoBox("这是 Root 节点,是所有连招的起点。", InfoMessageType.Info, "IsRootNode")]
|
|
|
|
|
|
public string referenceName;
|
|
|
|
|
|
|
2026-01-17 11:35:49 -05:00
|
|
|
|
[ListDrawerSettings(NumberOfItemsPerPage = 10, CustomAddFunction = "AddBranch")]
|
|
|
|
|
|
[HideReferenceObjectPicker]
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public List<ComboBranchData> branches = new List<ComboBranchData>();
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsRootNode() => referenceName == "Root";
|
|
|
|
|
|
|
|
|
|
|
|
private bool ValidateReferenceName(string name, ref string errorMessage)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
|
|
|
|
{
|
|
|
|
|
|
errorMessage = "引用名称不能为空。";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-01-17 11:35:49 -05:00
|
|
|
|
|
|
|
|
|
|
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<string> targetNodes = branches
|
|
|
|
|
|
.Select(b => b == null || string.IsNullOrEmpty(b.nextNodeRefName) ? "?" : $"{b.operation}->{b.nextNodeRefName}").ToList();
|
|
|
|
|
|
|
|
|
|
|
|
return $"{name} - ({string.Join(", ", targetNodes)})";
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连招分支的数据定义
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class ComboBranchData
|
|
|
|
|
|
{
|
|
|
|
|
|
[Tooltip("触发此分支的输入操作,例如 'L', 'R', 'Attack'")] [HorizontalGroup("Branch", 100)] [LabelWidth(60)]
|
|
|
|
|
|
public string operation;
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("此分支链接到的下一个节点")]
|
|
|
|
|
|
[HorizontalGroup("Branch")]
|
|
|
|
|
|
[LabelText("下一个节点")]
|
|
|
|
|
|
public string nextNodeRefName; // <-- 使用名称引用,而不是索引
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|