DataEditor & StorySystem Graph
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLSFramework.StorySystem
|
||||
{
|
||||
public class EditorWindowBase : EditorWindow
|
||||
{
|
||||
protected GraphViewBase graphView;
|
||||
protected GraphBase currentGraph;
|
||||
protected string windowTitle = "Story System Graph";
|
||||
|
||||
protected virtual void CreateGraphView()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void SetGraph(GraphBase graph)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void OnGraphUpdated()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ada5daa7b9068f4c98c9af0655aef6d
|
||||
@@ -0,0 +1,296 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem
|
||||
{
|
||||
public partial class GraphViewBase : GraphView
|
||||
{
|
||||
protected GraphBase graph;
|
||||
protected EditorWindowBase editorWindow;
|
||||
|
||||
// 加载图表
|
||||
public void LoadGraph(GraphBase graph)
|
||||
{
|
||||
this.graph = graph;
|
||||
if (graph == null)
|
||||
{
|
||||
Debug.LogError("Graph is null, cannot load.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除当前视图
|
||||
DeleteElements(graphElements);
|
||||
|
||||
// 1. 创建所有节点
|
||||
var nodeViewMap = new Dictionary<string, BaseGraphNode>();
|
||||
foreach (var nodeData in this.graph.nodes)
|
||||
{
|
||||
BaseGraphNode nodeView = CreateNodeView(nodeData);
|
||||
AddElement(nodeView);
|
||||
nodeViewMap[nodeData.guid] = nodeView;
|
||||
}
|
||||
|
||||
// 2. 创建所有连接
|
||||
foreach (var edgeData in this.graph.edges)
|
||||
{
|
||||
if (!nodeViewMap.TryGetValue(edgeData.outputNodeGuid, out var outputNode) ||
|
||||
!nodeViewMap.TryGetValue(edgeData.inputNodeGuid, out var inputNode))
|
||||
{
|
||||
Debug.LogWarning($"Failed to find nodes for edge: {edgeData.outputNodeGuid} -> {edgeData.inputNodeGuid}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Port outputPort = outputNode.outputContainer.Q<Port>(name: edgeData.outputPortName);
|
||||
Port inputPort = inputNode.inputContainer.Q<Port>(name: edgeData.inputPortName);
|
||||
|
||||
if (outputPort == null || inputPort == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"Failed to find ports for edge: {outputNode.title} (Port: {edgeData.outputPortName}) -> {inputNode.title} (Port: {edgeData.inputPortName})");
|
||||
continue;
|
||||
}
|
||||
|
||||
Edge edge = outputPort.ConnectTo(inputPort);
|
||||
AddElement(edge);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveGraph()
|
||||
{
|
||||
if (graph == null) return;
|
||||
|
||||
graph.nodes.Clear();
|
||||
graph.edges.Clear();
|
||||
|
||||
// 1. 保存所有节点
|
||||
foreach (var nodeView in nodes.OfType<BaseGraphNode>())
|
||||
{
|
||||
// 更新节点数据的位置
|
||||
nodeView.NodeData.position = nodeView.GetPosition().position;
|
||||
graph.nodes.Add(nodeView.NodeData);
|
||||
}
|
||||
|
||||
// 2. 保存所有连接
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
var outputNode = (BaseGraphNode)edge.output.node;
|
||||
var inputNode = (BaseGraphNode)edge.input.node;
|
||||
|
||||
graph.edges.Add(new EdgeData
|
||||
{
|
||||
outputNodeGuid = outputNode.NodeData.guid,
|
||||
|
||||
// --- 关键修复 ---
|
||||
// 我们必须保存 port.name (内部ID/GUID),
|
||||
// 而不是 port.portName (可视标签, 可能是"")
|
||||
outputPortName = edge.output.name, // <-- 旧代码是: edge.output.portName
|
||||
inputNodeGuid = inputNode.NodeData.guid,
|
||||
inputPortName = edge.input.name // <-- 旧代码是: edge.input.portName
|
||||
// --- 修复结束 ---
|
||||
});
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(graph);
|
||||
AssetDatabase.SaveAssets();
|
||||
editorWindow.OnGraphUpdated();
|
||||
|
||||
Debug.Log($"Graph '{graph.name}' saved successfully!");
|
||||
}
|
||||
|
||||
// 泛型创建节点方法
|
||||
protected void CreateNode(BaseNodeData data, Vector2 position)
|
||||
{
|
||||
if (graph == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("No Graph", "Please select a Dialogue Graph asset first.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
data.guid = Guid.NewGuid().ToString();
|
||||
data.position = position;
|
||||
|
||||
// 创建节点视图
|
||||
var nodeView = CreateNodeView(data);
|
||||
|
||||
// 将节点添加到图表数据中 (保存时会再次保存,但在此处添加以便新建的节点可以立即连接)
|
||||
// _graph.nodes.Add(data); // 暂时不加,等SaveGraph统一处理
|
||||
|
||||
// 将节点视图添加到GraphView
|
||||
AddElement(nodeView);
|
||||
}
|
||||
|
||||
|
||||
protected virtual BaseGraphNode CreateNodeView(BaseNodeData data)
|
||||
{
|
||||
return null; // 由子类实现
|
||||
}
|
||||
|
||||
protected virtual GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
|
||||
{
|
||||
if (graph != null)
|
||||
{
|
||||
EditorUtility.SetDirty(graph);
|
||||
editorWindow.OnGraphUpdated();
|
||||
}
|
||||
|
||||
return graphViewChange;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class GraphViewBase
|
||||
{
|
||||
[Serializable]
|
||||
protected class CopyPasteData
|
||||
{
|
||||
[SerializeReference] // <-- 关键:确保多态性被正确序列化
|
||||
public List<BaseNodeData> nodes = new List<BaseNodeData>();
|
||||
|
||||
public List<EdgeData> edges = new List<EdgeData>();
|
||||
}
|
||||
|
||||
protected string OnSerializeGraphElements(IEnumerable<GraphElement> elements)
|
||||
{
|
||||
var nodesToCopy = new List<BaseNodeData>();
|
||||
var edgesToCopy = new List<EdgeData>();
|
||||
|
||||
var selectedNodeGuids = new HashSet<string>(
|
||||
elements.OfType<BaseGraphNode>().Select(n => n.NodeData.guid)
|
||||
);
|
||||
|
||||
// 遍历所有选中的节点
|
||||
foreach (var nodeView in elements.OfType<BaseGraphNode>())
|
||||
{
|
||||
// 使用 JsonUtility 创建一个深拷贝 (注意:这对于[SerializeReference]可能不完美,但对简单字段有效)
|
||||
// 一个更健壮的方法是使用System.Text.Json或Newtonsoft.Json,但我们先用Unity内置的
|
||||
string nodeJson = JsonUtility.ToJson(nodeView.NodeData);
|
||||
// 注意:JsonUtility 不支持直接反序列化到基类,我们需要知道具体类型
|
||||
// 这是一个简化,它可能无法正确深拷贝 [SerializeReference] 列表
|
||||
// 让我们改变策略:我们只复制数据,在粘贴时创建新实例。
|
||||
nodesToCopy.Add(nodeView.NodeData); // <-- 简化:直接添加引用
|
||||
}
|
||||
|
||||
// 遍历所有选中的边
|
||||
foreach (var edge in elements.OfType<Edge>())
|
||||
{
|
||||
var outputNode = (BaseGraphNode)edge.output.node;
|
||||
var inputNode = (BaseGraphNode)edge.input.node;
|
||||
|
||||
if (selectedNodeGuids.Contains(outputNode.NodeData.guid) &&
|
||||
selectedNodeGuids.Contains(inputNode.NodeData.guid))
|
||||
{
|
||||
edgesToCopy.Add(new EdgeData
|
||||
{
|
||||
outputNodeGuid = outputNode.NodeData.guid,
|
||||
outputPortName = edge.output.name,
|
||||
inputNodeGuid = inputNode.NodeData.guid,
|
||||
inputPortName = edge.input.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var copyData = new CopyPasteData
|
||||
{
|
||||
nodes = nodesToCopy,
|
||||
edges = edgesToCopy
|
||||
};
|
||||
|
||||
// 使用 JsonUtility 序列化
|
||||
return JsonUtility.ToJson(copyData, true); // 'true' for pretty print
|
||||
}
|
||||
|
||||
protected void OnUnserializeAndPaste(string operationName, string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CopyPasteData pastedData;
|
||||
try
|
||||
{
|
||||
pastedData = JsonUtility.FromJson<CopyPasteData>(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Failed to deserialize pasted data: {e.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pastedData == null)
|
||||
{
|
||||
Debug.LogError("Pasted data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var guidMap = new Dictionary<string, string>();
|
||||
var nodeViewMap = new Dictionary<string, BaseGraphNode>();
|
||||
|
||||
// 1. 创建新节点 (并重新生成GUID)
|
||||
foreach (var nodeData in pastedData.nodes)
|
||||
{
|
||||
var oldGuid = nodeData.guid;
|
||||
var newGuid = Guid.NewGuid().ToString();
|
||||
|
||||
guidMap[oldGuid] = newGuid;
|
||||
|
||||
// --- 关键:创建数据的深拷贝 ---
|
||||
// 我们必须创建一个新实例,否则粘贴的节点将引用与原始节点相同的数据
|
||||
string nodeJson = JsonUtility.ToJson(nodeData);
|
||||
BaseNodeData newData = (BaseNodeData)JsonUtility.FromJson(nodeJson, nodeData.GetType());
|
||||
// --- 结束 ---
|
||||
|
||||
newData.guid = newGuid;
|
||||
newData.position += new Vector2(20, 20);
|
||||
|
||||
var nodeView = CreateNodeView(newData);
|
||||
AddElement(nodeView);
|
||||
|
||||
nodeViewMap[newGuid] = nodeView;
|
||||
}
|
||||
|
||||
// 2. 创建新边
|
||||
foreach (var edgeData in pastedData.edges)
|
||||
{
|
||||
// 检查旧GUID是否存在于映射中 (防止只复制一个节点和它的边)
|
||||
if (!guidMap.ContainsKey(edgeData.outputNodeGuid) || !guidMap.ContainsKey(edgeData.inputNodeGuid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string newOutputGuid = guidMap[edgeData.outputNodeGuid];
|
||||
string newInputGuid = guidMap[edgeData.inputNodeGuid];
|
||||
|
||||
var outputNode = nodeViewMap[newOutputGuid];
|
||||
var inputNode = nodeViewMap[newInputGuid];
|
||||
|
||||
Port outputPort = outputNode.outputContainer.Q<Port>(name: edgeData.outputPortName);
|
||||
Port inputPort = inputNode.inputContainer.Q<Port>(name: edgeData.inputPortName);
|
||||
|
||||
if (outputPort == null || inputPort == null)
|
||||
{
|
||||
Debug.LogWarning($"Failed to find ports for pasted edge: {edgeData.outputPortName} -> {edgeData.inputPortName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Edge edge = outputPort.ConnectTo(inputPort);
|
||||
AddElement(edge);
|
||||
}
|
||||
|
||||
// OnGraphViewChanged 会自动处理 'dirty' 标记
|
||||
}
|
||||
|
||||
protected bool OnCanPasteSerializedData(string data)
|
||||
{
|
||||
// (简单的检查)
|
||||
return !string.IsNullOrEmpty(data) && data.Contains("nodes") && data.Contains("edges");
|
||||
}
|
||||
|
||||
// --- 修复结束 ---
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57749720ca7b80e479d90181d6499476
|
||||
Reference in New Issue
Block a user