DataEditor & StorySystem Graph
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f526df7c127191c4395a96ab46da1de6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem.Dialog
|
||||
{
|
||||
public class ChoiceGraphNode : BaseGraphNode
|
||||
{
|
||||
private ChoiceNodeData _data;
|
||||
private DialogGraphView _graphView;
|
||||
|
||||
public ChoiceGraphNode(ChoiceNodeData data, DialogGraphView graphView) : base(data)
|
||||
{
|
||||
_data = data;
|
||||
_graphView = graphView;
|
||||
|
||||
var titleContainer = this.Q("title");
|
||||
titleContainer.style.backgroundColor = new StyleColor(new Color(0.7f, 0.5f, 0.1f));
|
||||
|
||||
var inputPort = CreatePort(Direction.Input, Port.Capacity.Multi, "Previous");
|
||||
inputContainer.Add(inputPort);
|
||||
|
||||
var addButton = new Button(() => AddChoice(null))
|
||||
{
|
||||
text = "Add Choice"
|
||||
};
|
||||
extensionContainer.Add(addButton);
|
||||
|
||||
foreach (var choice in _data.choices)
|
||||
{
|
||||
AddChoice(choice);
|
||||
}
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
|
||||
|
||||
// 核心方法:添加一个选项 (UI + 端口)
|
||||
private void AddChoice(ChoiceData choiceData)
|
||||
{
|
||||
if (choiceData == null)
|
||||
{
|
||||
choiceData = new ChoiceData
|
||||
{
|
||||
guid = Guid.NewGuid().ToString(),
|
||||
choiceText = "New Choice"
|
||||
};
|
||||
_data.choices.Add(choiceData);
|
||||
}
|
||||
|
||||
// A. 创建端口
|
||||
var outputPort = CreatePort(Direction.Output, Port.Capacity.Single, choiceData.guid);
|
||||
outputPort.portName = "";
|
||||
|
||||
// B. 创建选项的UI控件
|
||||
|
||||
// 1. 总的水平根容器
|
||||
var choiceUIContainer = new VisualElement();
|
||||
choiceUIContainer.style.flexDirection = FlexDirection.Row;
|
||||
choiceUIContainer.style.alignItems = Align.Center;
|
||||
choiceUIContainer.style.paddingLeft = 10;
|
||||
choiceUIContainer.style.paddingRight = 10;
|
||||
|
||||
// 2. 左侧的主内容容器 (垂直)
|
||||
var mainContentContainer = new VisualElement();
|
||||
mainContentContainer.style.flexDirection = FlexDirection.Column;
|
||||
mainContentContainer.style.flexGrow = 1;
|
||||
mainContentContainer.style.marginRight = 5;
|
||||
|
||||
// 2a. 顶部:选项文本 (单行)
|
||||
var textField = new TextField();
|
||||
textField.SetValueWithoutNotify(choiceData.choiceText);
|
||||
textField.RegisterValueChangedCallback(evt => choiceData.choiceText = evt.newValue);
|
||||
|
||||
// --- 1. 修复:移除多行文本框样式 ---
|
||||
// (移除了 textField.Q("unity-text-input").style.minHeight = 40;)
|
||||
// --- 修复结束 ---
|
||||
|
||||
mainContentContainer.Add(textField);
|
||||
|
||||
// 2b. 底部:并排容器 (水平)
|
||||
var bottomRowContainer = new VisualElement();
|
||||
bottomRowContainer.style.flexDirection = FlexDirection.Row;
|
||||
bottomRowContainer.style.alignItems = Align.Center;
|
||||
bottomRowContainer.style.marginTop = 5;
|
||||
mainContentContainer.Add(bottomRowContainer);
|
||||
|
||||
// --- 2. 修复:重构 "Default" 和 "Condition" 的布局 ---
|
||||
|
||||
// 2b-1: "Default" 标签
|
||||
var defaultLabel = new Label("Default");
|
||||
defaultLabel.style.alignItems = Align.Center; // 垂直居中
|
||||
defaultLabel.style.marginRight = 5;
|
||||
bottomRowContainer.Add(defaultLabel);
|
||||
|
||||
// 2b-2: "Default" 复选框 (无标签)
|
||||
var defaultToggle = new Toggle(); // <-- 移除标签
|
||||
defaultToggle.SetValueWithoutNotify(choiceData.isDefault);
|
||||
defaultToggle.RegisterValueChangedCallback(evt => choiceData.isDefault = evt.newValue);
|
||||
defaultToggle.style.marginRight = 10; // 与条件框保持距离
|
||||
bottomRowContainer.Add(defaultToggle);
|
||||
|
||||
var conditionLabel = new Label("Condition");
|
||||
conditionLabel.style.alignItems = Align.Center; // 垂直居中
|
||||
conditionLabel.style.marginRight = 5;
|
||||
bottomRowContainer.Add(conditionLabel);
|
||||
|
||||
// 2b-3: 条件语句输入框 (将占据所有剩余空间)
|
||||
var conditionField = new TextField();
|
||||
conditionField.SetValueWithoutNotify(choiceData.conditionString);
|
||||
conditionField.RegisterValueChangedCallback(evt => choiceData.conditionString = evt.newValue);
|
||||
conditionField.style.minWidth = 100;
|
||||
conditionField.style.flexGrow = 1; // <-- 关键:使其填充
|
||||
//conditionField.placeholder.Set("Condition (optional)");
|
||||
bottomRowContainer.Add(conditionField);
|
||||
|
||||
// --- 修复结束 ---
|
||||
|
||||
// 3. 右侧的端口和按钮容器 (垂直)
|
||||
var portAndButtonContainer = new VisualElement();
|
||||
portAndButtonContainer.style.flexDirection = FlexDirection.Column;
|
||||
portAndButtonContainer.style.alignItems = Align.Center;
|
||||
portAndButtonContainer.style.width = 40;
|
||||
|
||||
// 3a. 端口
|
||||
portAndButtonContainer.Add(outputPort);
|
||||
|
||||
// 3b. "X" 删除按钮
|
||||
var removeButton = new Button(() => RemoveChoice(choiceData, outputPort, choiceUIContainer))
|
||||
{
|
||||
text = "X"
|
||||
};
|
||||
removeButton.style.width = 20;
|
||||
removeButton.style.height = 20;
|
||||
removeButton.style.marginTop = 5;
|
||||
portAndButtonContainer.Add(removeButton);
|
||||
|
||||
// 4. 组装
|
||||
choiceUIContainer.Add(mainContentContainer);
|
||||
choiceUIContainer.Add(portAndButtonContainer);
|
||||
|
||||
// 5. 添加分割线
|
||||
var separator = new VisualElement();
|
||||
separator.style.height = 1;
|
||||
separator.style.backgroundColor = new StyleColor(Color.grey);
|
||||
separator.style.marginTop = 5;
|
||||
separator.style.marginBottom = 5;
|
||||
|
||||
var containerWithSeparator = new VisualElement();
|
||||
containerWithSeparator.Add(separator);
|
||||
containerWithSeparator.Add(choiceUIContainer);
|
||||
|
||||
// 6. 添加到节点
|
||||
outputContainer.Add(containerWithSeparator);
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
|
||||
// 核心方法:移除一个选项 (此方法保持不变)
|
||||
private void RemoveChoice(ChoiceData choiceData, Port port, VisualElement container)
|
||||
{
|
||||
_data.choices.Remove(choiceData);
|
||||
|
||||
if (port.connected)
|
||||
{
|
||||
_graphView.DeleteElements(port.connections.ToList());
|
||||
}
|
||||
|
||||
port.DisconnectAll();
|
||||
|
||||
if(container.parent != null)
|
||||
{
|
||||
outputContainer.Remove(container.parent);
|
||||
}
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cde0007c641b2c4a80b7642ae55ff73
|
||||
@@ -0,0 +1,45 @@
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem.Dialog
|
||||
{
|
||||
public class CompoundDialogNode : BaseGraphNode
|
||||
{
|
||||
private CompoundDialogNodeData _data;
|
||||
|
||||
public CompoundDialogNode(CompoundDialogNodeData data) : base(data)
|
||||
{
|
||||
_data = data;
|
||||
|
||||
// 1. 设置节点颜色 (选择一个灰色)
|
||||
var titleContainer = this.Q("title");
|
||||
titleContainer.style.backgroundColor = new StyleColor(new Color(0.6f, 0.6f, 0.6f));
|
||||
|
||||
// 2. 添加端口 (像DialogNode一样,有输入和输出)
|
||||
var inputPort = CreatePort(Direction.Input, Port.Capacity.Multi, "Previous");
|
||||
var outputPort = CreatePort(Direction.Output, Port.Capacity.Single, "Next");
|
||||
|
||||
inputContainer.Add(inputPort);
|
||||
outputContainer.Add(outputPort);
|
||||
|
||||
// 3. 添加自定义UI (一个 ObjectField 用于 TextAsset)
|
||||
var assetField = new ObjectField("TextAsset")
|
||||
{
|
||||
objectType = typeof(TextAsset),
|
||||
allowSceneObjects = false
|
||||
};
|
||||
assetField.SetValueWithoutNotify(_data.compoundDialogAsset);
|
||||
assetField.RegisterValueChangedCallback(evt => {
|
||||
_data.compoundDialogAsset = evt.newValue as TextAsset;
|
||||
});
|
||||
|
||||
extensionContainer.Add(assetField);
|
||||
|
||||
// 刷新
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f39fd5f93076c404ab92b10fe4c9b46f
|
||||
@@ -0,0 +1,149 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem.Dialog
|
||||
{
|
||||
public class DialogGraphNode : BaseGraphNode
|
||||
{
|
||||
private DialogNodeData _data;
|
||||
|
||||
private ObjectField _characterField;
|
||||
private DropdownField _expressionField;
|
||||
|
||||
public DialogGraphNode(DialogNodeData data) : base(data)
|
||||
{
|
||||
_data = data;
|
||||
|
||||
var titleContainer = this.Q("title");
|
||||
titleContainer.style.backgroundColor = new StyleColor(new Color(0.2f, 0.3f, 0.6f)); // 蓝色
|
||||
|
||||
// 1. 添加端口
|
||||
var inputPort = CreatePort(Direction.Input, Port.Capacity.Multi, "Previous");
|
||||
var outputPort = CreatePort(Direction.Output, Port.Capacity.Single, "Next");
|
||||
|
||||
inputContainer.Add(inputPort);
|
||||
outputContainer.Add(outputPort);
|
||||
|
||||
// 2. 添加自定义UI字段
|
||||
// (我们使用 extensionContainer,它位于 mainContainer 下方,用于放置自定义UI)
|
||||
|
||||
// -- 角色名 --
|
||||
_characterField = new ObjectField("Character")
|
||||
{
|
||||
objectType = typeof(CharacterData),
|
||||
allowSceneObjects = false
|
||||
};
|
||||
_characterField.SetValueWithoutNotify(_data.characterData);
|
||||
_characterField.RegisterValueChangedCallback(OnCharacterDataChanged);
|
||||
extensionContainer.Add(_characterField);
|
||||
|
||||
// -- 表情 (DropdownField) --
|
||||
_expressionField = new DropdownField("Expression");
|
||||
_expressionField.RegisterValueChangedCallback(OnExpressionChanged);
|
||||
extensionContainer.Add(_expressionField);
|
||||
|
||||
// -- 位置 --
|
||||
var positionField = new Vector2Field("Position");
|
||||
positionField.SetValueWithoutNotify(_data.characterPosition);
|
||||
var posFieldInput = positionField.Q(className: "unity-base-field__input");
|
||||
posFieldInput.style.minWidth = 150;
|
||||
positionField.RegisterValueChangedCallback(evt => {
|
||||
_data.characterPosition = evt.newValue;
|
||||
});
|
||||
extensionContainer.Add(positionField);
|
||||
|
||||
// -- 对话内容 (TextArea) --
|
||||
var dialogueField = new TextField("Dialogue") { multiline = true };
|
||||
dialogueField.SetValueWithoutNotify(_data.dialogueText);
|
||||
dialogueField.RegisterValueChangedCallback(evt => {
|
||||
_data.dialogueText = evt.newValue;
|
||||
});
|
||||
// 调整TextArea的样式,使其看起来像一个多行文本框
|
||||
dialogueField.Q("unity-text-input").style.minWidth = 150;
|
||||
extensionContainer.Add(dialogueField);
|
||||
|
||||
// -- 语音 (AudioClip) --
|
||||
var audioClipField = new ObjectField("AudioClip")
|
||||
{
|
||||
objectType = typeof(AudioClip),
|
||||
allowSceneObjects = false // 我们只接受 Project 中的资源
|
||||
};
|
||||
audioClipField.SetValueWithoutNotify(_data.audioClip);
|
||||
audioClipField.RegisterValueChangedCallback(evt => {
|
||||
_data.audioClip = evt.newValue as AudioClip;
|
||||
});
|
||||
extensionContainer.Add(audioClipField);
|
||||
|
||||
// 刷新布局
|
||||
PopulateExpressionDropdown();
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
|
||||
// --- 6. 核心逻辑:当 ObjectField 变化时调用 ---
|
||||
private void OnCharacterDataChanged(ChangeEvent<Object> evt)
|
||||
{
|
||||
_data.characterData = evt.newValue as CharacterData;
|
||||
|
||||
// 清空旧的表情
|
||||
_data.expressionKey = null;
|
||||
|
||||
// 重新填充下拉框
|
||||
PopulateExpressionDropdown();
|
||||
}
|
||||
|
||||
// --- 7. 核心逻辑:当 DropdownField 变化时调用 ---
|
||||
private void OnExpressionChanged(ChangeEvent<string> evt)
|
||||
{
|
||||
// 保存新的表情key
|
||||
_data.expressionKey = evt.newValue;
|
||||
}
|
||||
|
||||
// --- 8. 核心逻辑:填充/更新表情下拉框 ---
|
||||
private void PopulateExpressionDropdown()
|
||||
{
|
||||
// 清空旧选项
|
||||
_expressionField.choices.Clear();
|
||||
|
||||
if (_data.characterData == null)
|
||||
{
|
||||
// 如果没有角色,禁用下拉框
|
||||
_expressionField.SetValueWithoutNotify(null);
|
||||
_expressionField.SetEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从 CharacterData 中提取所有表情的 "name"
|
||||
List<string> expressionKeys = _data.characterData.expressions
|
||||
.Select(expr => expr.key)
|
||||
.ToList();
|
||||
|
||||
if (expressionKeys.Count == 0)
|
||||
{
|
||||
// 如果有角色但没表情,禁用下拉框
|
||||
_expressionField.SetValueWithoutNotify(null);
|
||||
_expressionField.SetEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 填充下拉框
|
||||
_expressionField.choices = expressionKeys;
|
||||
_expressionField.SetEnabled(true);
|
||||
|
||||
// 检查旧的key是否还存在于新列表中
|
||||
if (string.IsNullOrEmpty(_data.expressionKey) || !expressionKeys.Contains(_data.expressionKey))
|
||||
{
|
||||
// 如果不存在 (或为空),自动选择第一个作为默认值
|
||||
_data.expressionKey = expressionKeys[0];
|
||||
}
|
||||
|
||||
// 设置下拉框的当前值
|
||||
_expressionField.SetValueWithoutNotify(_data.expressionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1de4576f57992f4abdbf42cf5d93a5d
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c21355e0c16093d40a6e8a5871c75c5e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem.Dialog
|
||||
{
|
||||
public class DialogGraphEditorWindow : EditorWindowBase
|
||||
{
|
||||
private DialogGraphView _graphView => graphView as DialogGraphView;
|
||||
private DialogGraph _currentGraph => currentGraph as DialogGraph;
|
||||
|
||||
[MenuItem("Dialog/Dialog Graph Editor")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
var window = GetWindow<DialogGraphEditorWindow>();
|
||||
window.titleContent = new GUIContent("Dialog Graph Editor");
|
||||
}
|
||||
|
||||
[OnOpenAsset(1)]
|
||||
public static bool OnOpenAsset(int instanceID, int line)
|
||||
{
|
||||
if (EditorUtility.InstanceIDToObject(instanceID) is DialogGraph graph)
|
||||
{
|
||||
var window = GetWindow<DialogGraphEditorWindow>();
|
||||
// OnOpenAsset 可能会在 OnEnable 之前运行
|
||||
// 我们只设置数据,加载操作交给 OnEnable 或 SetGraph
|
||||
window.SetGraph(graph);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- *** 关键修复:重构生命周期 *** ---
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// --- 1. 新增:创建工具栏 ---
|
||||
var toolbar = new Toolbar();
|
||||
var saveButton = new Button() { text = "Save Dialog" };
|
||||
|
||||
// 将按钮添加到工具栏
|
||||
toolbar.Add(saveButton);
|
||||
|
||||
// 将工具栏添加到窗口的根
|
||||
rootVisualElement.Add(toolbar);
|
||||
// --- 结束新增 ---
|
||||
|
||||
|
||||
// 2. 创建 GraphView (保持不变)
|
||||
CreateGraphView();
|
||||
|
||||
// --- 3. 新增:将按钮的点击事件
|
||||
// 此时 _graphView 必定已被创建
|
||||
saveButton.clicked += () => _graphView.SaveGraph();
|
||||
// --- 结束新增 ---
|
||||
|
||||
|
||||
// 4. 加载图表 (保持不变)
|
||||
if (_currentGraph != null)
|
||||
{
|
||||
_graphView.schedule.Execute(() => _graphView.LoadGraph(_currentGraph));
|
||||
}
|
||||
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// OnDisable 时移除UI元素
|
||||
if (_graphView != null)
|
||||
{
|
||||
rootVisualElement.Remove(_graphView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateGraphView()
|
||||
{
|
||||
// 确保我们不会意外地创建和添加多个 GraphView
|
||||
if (_graphView != null)
|
||||
{
|
||||
rootVisualElement.Remove(_graphView);
|
||||
}
|
||||
|
||||
graphView = new DialogGraphView(this)
|
||||
{
|
||||
name = "Dialog Graph View",
|
||||
style = { flexGrow = 1 }
|
||||
};
|
||||
|
||||
rootVisualElement.Add(_graphView);
|
||||
}
|
||||
|
||||
public override void SetGraph(GraphBase graph)
|
||||
{
|
||||
currentGraph = graph;
|
||||
UpdateWindowTitle();
|
||||
|
||||
// 如果 GraphView 已经存在 (OnEnable 已经运行),
|
||||
// 立即安排加载新图表
|
||||
if (_graphView != null)
|
||||
{
|
||||
_graphView.schedule.Execute(() => _graphView.LoadGraph(_currentGraph));
|
||||
}
|
||||
// 如果 GraphView 尚不存在 (OnEnable 还没运行),
|
||||
// OnEnable 自己会处理加载
|
||||
}
|
||||
|
||||
// --- *** 修复结束 *** ---
|
||||
|
||||
public override void OnGraphUpdated()
|
||||
{
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
private void UpdateWindowTitle()
|
||||
{
|
||||
windowTitle = "Dialog Graph Editor";
|
||||
string graphName = _currentGraph != null ? _currentGraph.name : " (No Graph Loaded)";
|
||||
bool isDirty = _currentGraph != null && EditorUtility.IsDirty(_currentGraph);
|
||||
titleContent = new GUIContent($"{windowTitle} - {graphName}{(isDirty ? "*" : "")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edb12263f493f38458ff5b9fbdca659f
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace SLSFramework.StorySystem.Dialog
|
||||
{
|
||||
public partial class DialogGraphView : GraphViewBase
|
||||
{
|
||||
private DialogGraph _graph => graph as DialogGraph;
|
||||
private DialogGraphEditorWindow _editorWindow => editorWindow as DialogGraphEditorWindow;
|
||||
|
||||
public DialogGraphView(DialogGraphEditorWindow window)
|
||||
{
|
||||
editorWindow = window;
|
||||
|
||||
// 启用缩放、拖拽、选择等操作
|
||||
this.AddManipulator(new ContentZoomer());
|
||||
this.AddManipulator(new ContentDragger());
|
||||
this.AddManipulator(new SelectionDragger());
|
||||
this.AddManipulator(new RectangleSelector());
|
||||
|
||||
// 添加网格背景
|
||||
var grid = new GridBackground();
|
||||
Insert(0, grid);
|
||||
grid.StretchToParentSize();
|
||||
|
||||
// 注册GraphView改变的回调
|
||||
graphViewChanged = OnGraphViewChanged;
|
||||
|
||||
// 添加右键菜单
|
||||
AddContextMenuManipulator();
|
||||
|
||||
serializeGraphElements = OnSerializeGraphElements;
|
||||
unserializeAndPaste = OnUnserializeAndPaste;
|
||||
canPasteSerializedData = OnCanPasteSerializedData;
|
||||
}
|
||||
|
||||
// 构建右键菜单
|
||||
private void AddContextMenuManipulator()
|
||||
{
|
||||
var manipulator = new ContextualMenuManipulator(evt =>
|
||||
{
|
||||
Vector2 pos = evt.localMousePosition;
|
||||
|
||||
evt.menu.AppendAction("Create Dialog Node", (a) => CreateNode(new DialogNodeData(), pos));
|
||||
evt.menu.AppendAction("Create Compound Dialog Node", (a) => CreateNode(new CompoundDialogNodeData(), pos));
|
||||
evt.menu.AppendAction("Create Choice Node", (a) => CreateNode(new ChoiceNodeData(), pos));
|
||||
evt.menu.AppendAction("Create Condition Node", (a) => CreateNode(new ConditionNodeData(), pos));
|
||||
evt.menu.AppendAction("Create Event Node", (a) => CreateNode(new EventNodeData(), pos));
|
||||
evt.menu.AppendSeparator();
|
||||
evt.menu.AppendAction("Create Start Node", (a) => CreateNode(new StartNodeData(), pos));
|
||||
evt.menu.AppendAction("Create End Node", (a) => CreateNode(new EndNodeData(), pos));
|
||||
evt.menu.AppendSeparator();
|
||||
evt.menu.AppendAction("Save Asset", (a) => SaveGraph(),
|
||||
(a) => _graph != null ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
|
||||
});
|
||||
|
||||
this.AddManipulator(manipulator);
|
||||
}
|
||||
|
||||
// GraphView发生变化时的回调
|
||||
protected override GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
|
||||
{
|
||||
// 处理连接
|
||||
if (graphViewChange.edgesToCreate != null)
|
||||
{
|
||||
foreach (var edge in graphViewChange.edgesToCreate)
|
||||
{
|
||||
// (可选) 在此处添加额外的连接验证逻辑
|
||||
// Debug.Log($"Edge created from {edge.output.node.title} to {edge.input.node.title}");
|
||||
}
|
||||
}
|
||||
|
||||
// (可选) 处理删除
|
||||
if (graphViewChange.elementsToRemove != null)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
base.OnGraphViewChanged(graphViewChange);
|
||||
|
||||
return graphViewChange;
|
||||
}
|
||||
|
||||
|
||||
// 节点视图的工厂方法
|
||||
protected override BaseGraphNode CreateNodeView(BaseNodeData data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
case StartNodeData startData:
|
||||
return new StartGraphNode(startData);
|
||||
case EndNodeData endData:
|
||||
return new EndGraphNode(endData);
|
||||
case ChoiceNodeData choiceData:
|
||||
return new ChoiceGraphNode(choiceData, this);
|
||||
case DialogNodeData dialogData:
|
||||
return new DialogGraphNode(dialogData);
|
||||
case CompoundDialogNodeData compoundData:
|
||||
return new CompoundDialogNode(compoundData);
|
||||
case ConditionNodeData conditionData:
|
||||
return new ConditionGraphNode(conditionData);
|
||||
case EventNodeData eventData:
|
||||
return new EventGraphNode(eventData);
|
||||
default:
|
||||
Debug.LogError($"Unknown node data type: {data.GetType()}");
|
||||
throw new ArgumentException($"Unknown node data type: {data.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
// (必需) 重写 GetCompatiblePorts 以允许连接
|
||||
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
|
||||
{
|
||||
var compatiblePorts = new List<Port>();
|
||||
ports.ForEach((port) =>
|
||||
{
|
||||
if (startPort != port && startPort.node != port.node && startPort.direction != port.direction)
|
||||
{
|
||||
compatiblePorts.Add(port);
|
||||
}
|
||||
});
|
||||
return compatiblePorts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f694b29c3d1202a4cba7f81a0e308b48
|
||||
Reference in New Issue
Block a user