【a】可视化剧本编辑器 10.StoryEditor
This commit is contained in:
8
Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta
Normal file
8
Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f25ed558908de741b709bb8f3df2427
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
272
Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
Normal file
272
Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Sirenix.OdinInspector;
|
||||
using Sirenix.Utilities;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
[Serializable]
|
||||
public class ScriptGraph : NodeGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
[SerializeField, ReadOnly]
|
||||
private BeginNode begin;
|
||||
/// <summary>
|
||||
/// 结束节点
|
||||
/// </summary>
|
||||
[SerializeField, ReadOnly]
|
||||
private EndNode end;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化标识
|
||||
/// </summary>
|
||||
[SerializeField, ReadOnly]
|
||||
private bool initialized;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (nodes == null)
|
||||
{
|
||||
Debug.LogError("Graph异常,无有效节点列表存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
if (!initialized)
|
||||
{
|
||||
_ = DelayInitialize();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
// 清理所有节点
|
||||
nodes.ForEach(DestroyImmediate);
|
||||
nodes.Clear();
|
||||
}
|
||||
|
||||
#region 初始化
|
||||
|
||||
/// <summary>
|
||||
/// 延迟初始化
|
||||
/// </summary>
|
||||
private async UniTask DelayInitialize()
|
||||
{
|
||||
// 等待Graph资产落盘
|
||||
#if UNITY_EDITOR
|
||||
while (string.IsNullOrEmpty(UnityEditor.AssetDatabase.GetAssetPath(this)))
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(0.01));
|
||||
}
|
||||
#endif
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(0.01));
|
||||
|
||||
// 创建初始节点
|
||||
InitBeginNode();
|
||||
InitEndNode();
|
||||
CreateSampleScriptParagraph();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化起始节点
|
||||
/// </summary>
|
||||
private void InitBeginNode()
|
||||
{
|
||||
var existing = nodes.FirstOrDefault(n => n is BeginNode) as BeginNode;
|
||||
if (existing != null)
|
||||
{
|
||||
begin = existing;
|
||||
return;
|
||||
}
|
||||
|
||||
begin = NodeBase.Create<BeginNode>(this, "Begin", Vector2.zero);
|
||||
}
|
||||
/// <summary>
|
||||
/// 初始化结束节点
|
||||
/// </summary>
|
||||
private void InitEndNode()
|
||||
{
|
||||
var existing = nodes.FirstOrDefault(n => n is EndNode) as EndNode;
|
||||
if (existing != null)
|
||||
{
|
||||
end = existing;
|
||||
return;
|
||||
}
|
||||
|
||||
end = NodeBase.Create<EndNode>(this, "End", new Vector2(NodeBase.DefaultNodeXInterval, NodeBase.DefaultNodeYInterval)* 2);
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建剧本段落样例
|
||||
/// </summary>
|
||||
private void CreateSampleScriptParagraph()
|
||||
{
|
||||
// 创建新的剧本段落
|
||||
var paragraph = ScriptParagraphNode.Create(this, $"{name}_para_sample", new Vector2(NodeBase.DefaultNodeXInterval, NodeBase.DefaultNodeYInterval));
|
||||
|
||||
// 将剧本与起始节点和结束节点相连
|
||||
begin.GetOutputPort("exit").Connect(paragraph.GetInputPort("enter"));
|
||||
paragraph.GetOutputPort("exit").Connect(end.GetInputPort("enter"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 导出设置
|
||||
|
||||
/// <summary>
|
||||
/// 资源加载方式
|
||||
/// </summary>
|
||||
[BoxGroup("Export", centerLabel:true)]
|
||||
[LabelText("资源加载方式"), ValueDropdown(nameof(_iResourceTypes)),SerializeField]
|
||||
private string loaderType;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 获取继承 IResource 的所有类
|
||||
/// </summary>
|
||||
private HashSet<string> _iResourceTypes = AssemblyUtilities.GetTypes(AssemblyCategory.Scripts)
|
||||
.Where(t => t.IsClass && typeof(IResource).IsAssignableFrom(t)).Select(t => t.ToString()).ToHashSet();
|
||||
#else
|
||||
private HashSet<string> _iResourceTypes = new();
|
||||
#endif
|
||||
|
||||
private IResource _loader;
|
||||
/// <summary>
|
||||
/// 资源加载器
|
||||
/// </summary>
|
||||
public IResource Loader
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_loader == null)
|
||||
{
|
||||
var type = Type.GetType(loaderType);
|
||||
_loader = type == null ? null : (IResource)Activator.CreateInstance(type);
|
||||
}
|
||||
|
||||
return _loader;
|
||||
}
|
||||
}
|
||||
|
||||
[BoxGroup("Export")]
|
||||
[LabelText("包体ID")]
|
||||
public string packageID;
|
||||
|
||||
[BoxGroup("Export")]
|
||||
[LabelText("Graph目录地址")]
|
||||
public string graphPath;
|
||||
|
||||
[BoxGroup("Export")]
|
||||
[Button("导出")]
|
||||
public async void Export()
|
||||
{
|
||||
// 资源加载方式排空
|
||||
if (Loader == null)
|
||||
{
|
||||
Debug.LogError("导出失败,没有选择资源加载方式");
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<Node, NodeData> dataMatch = new();
|
||||
Dictionary<NodeData, NodeBase> nodeMatch = new();
|
||||
|
||||
// 创建图表数据
|
||||
GraphData graph = new();
|
||||
graph.name = name;
|
||||
graph.resourceType = loaderType;
|
||||
|
||||
// 转录节点数据
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if(node is not NodeBase bNode)
|
||||
continue;
|
||||
|
||||
NodeData dNode = new();
|
||||
switch (bNode)
|
||||
{
|
||||
case BeginNode beginNode:
|
||||
dNode = await beginNode.Export();
|
||||
break;
|
||||
case EndNode endNode:
|
||||
dNode = await endNode.Export();
|
||||
break;
|
||||
case ScriptParagraphNode paraNode:
|
||||
dNode = await paraNode.Export();
|
||||
break;
|
||||
case FlowNode flowNode:
|
||||
dNode = await flowNode.Export();
|
||||
break;
|
||||
default:
|
||||
Debug.LogError($"节点{node.name}[type:{node.GetType()}]没有定义对应的数据转换逻辑,该节点将被跳过,此举将可能导致graph出现流程断裂或其他未知异常");
|
||||
break;
|
||||
}
|
||||
|
||||
dataMatch.Add(bNode, dNode);
|
||||
nodeMatch.Add(dNode, bNode);
|
||||
graph.nodes.Add(dNode);
|
||||
}
|
||||
|
||||
// 转录节点连接
|
||||
foreach (var data in nodeMatch.Keys)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
// 起始节点
|
||||
case BeginNodeData beginData:
|
||||
// 记录图表首个节点的索引
|
||||
graph.startNodeIndex = graph.nodes.IndexOf(beginData);
|
||||
// 连接后部节点
|
||||
nodeMatch[beginData].GetOutputPort("exit").GetConnections().ForEach(oNode => beginData.next.Add(graph.nodes.IndexOf(dataMatch[oNode.node])));
|
||||
break;
|
||||
// 结束节点
|
||||
case EndNodeData endData:
|
||||
// 连接前部节点
|
||||
nodeMatch[endData].GetInputPort("enter").GetConnections().ForEach(oNode => endData.pre.Add(graph.nodes.IndexOf(dataMatch[oNode.node])));
|
||||
break;
|
||||
// 空节点(流程节点)
|
||||
case FlowNodeData flowData:
|
||||
// 连接前部节点
|
||||
nodeMatch[flowData].GetInputPort("enter").GetConnections().ForEach(oNode => flowData.pre.Add(graph.nodes.IndexOf(dataMatch[oNode.node])));
|
||||
// 连接后部节点
|
||||
nodeMatch[flowData].GetOutputPort("exit").GetConnections().ForEach(oNode => flowData.next.Add(graph.nodes.IndexOf(dataMatch[oNode.node])));
|
||||
break;
|
||||
// 处理异常情况
|
||||
default:
|
||||
Debug.LogError($"节点{data.name}[type:{data.GetType()}]没有定义对应的数据转换逻辑,该节点将被跳过,此举将可能导致graph出现流程断裂或其他未知异常");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("转录完成");
|
||||
|
||||
// 将转录完成的图表数据序列化为json
|
||||
var json = JsonConvert.SerializeObject(graph, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
|
||||
// 文件目录排空
|
||||
var graphFilePath = Path.Combine(Application.dataPath, graphPath.Replace(".asset", ".sg.json"));
|
||||
if (!Directory.Exists(Path.GetDirectoryName(graphFilePath)))
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(graphFilePath) ?? string.Empty);
|
||||
// 写入json
|
||||
await File.WriteAllTextAsync(graphFilePath, json);
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.AssetDatabase.ImportAsset(graphFilePath.Replace(Application.dataPath, "Assets"));
|
||||
#endif
|
||||
|
||||
Debug.Log("剧本导出完成");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96f13bed6a3bb274abdf828cac308231
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta
Normal file
8
Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90e4f51c40e7d9340a88df558072b353
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs
Normal file
20
Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
[Serializable, CreateNodeMenu("")]
|
||||
public class BeginNode : NodeBase
|
||||
{
|
||||
[Output]
|
||||
public Exit exit;
|
||||
|
||||
/// <summary>
|
||||
/// 导出
|
||||
/// </summary>
|
||||
public new async UniTask<BeginNodeData> Export() => new(await base.Export());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7bdd4ad134525043830b54f6a2391f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs
Normal file
20
Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 结束节点
|
||||
/// </summary>
|
||||
[Serializable, CreateNodeMenu("")]
|
||||
public class EndNode : NodeBase
|
||||
{
|
||||
[Input]
|
||||
public Enter enter;
|
||||
|
||||
/// <summary>
|
||||
/// 导出
|
||||
/// </summary>
|
||||
public new async UniTask<EndNodeData> Export() => new(await base.Export());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e8d1b2bbe7b8154c8822cf7c0b6e064
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
128
Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs
Normal file
128
Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
[Serializable,CreateNodeMenu("")]
|
||||
public class FlowNode : NodeBase
|
||||
{
|
||||
[HorizontalGroup("Port", order:0)]
|
||||
[Input]
|
||||
public Enter enter;
|
||||
[HorizontalGroup("Port")]
|
||||
[Output]
|
||||
public Exit exit;
|
||||
|
||||
/// <summary>
|
||||
/// Exit端口连接的节点的执行类型
|
||||
/// </summary>
|
||||
[BoxGroup("Config", order:5, showLabel:false)]
|
||||
[LabelText("后续执行")]
|
||||
public NodeExecuteType exitNodeExecuteType = NodeExecuteType.Async;
|
||||
|
||||
/// <summary>
|
||||
/// 创建新的流程节点
|
||||
/// </summary>
|
||||
/// <param name="graph">所在的块</param>
|
||||
/// <param name="name">节点名称</param>
|
||||
/// <param name="position">节点位置</param>
|
||||
/// <param name="prePorts">节点前部连接</param>
|
||||
/// <param name="nextPorts">节点后部连接</param>
|
||||
public static T Create<T>(NodeGraph graph, string name = null, Vector2 position = default,
|
||||
List<NodePort> prePorts = null, List<NodePort> nextPorts = null) where T:FlowNode
|
||||
{
|
||||
// 创建节点
|
||||
var node = NodeBase.Create<T>(graph, name, position);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
// 将剧本与前部节点和后部节点相连
|
||||
var enterPort = node.GetInputPort("enter");
|
||||
if (enterPort != null && prePorts != null)
|
||||
prePorts.ForEach(port => port.Connect(enterPort));
|
||||
var exitPort = node.GetInputPort("exit");
|
||||
if (exitPort != null && nextPorts != null)
|
||||
nextPorts.ForEach(port => port.Connect(exitPort));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
#region 工具方法
|
||||
|
||||
/// <summary>
|
||||
/// 将自身的入口连接传递给指定端口
|
||||
/// </summary>
|
||||
/// <param name="otherPort">指定端口</param>
|
||||
/// <param name="deleteSelf">传递后删除自身连接</param>
|
||||
public void GiveEnterPortToOtherPort(NodePort otherPort, bool deleteSelf)
|
||||
=> GiveConnectionToOtherPort(GetInputPort("enter"), otherPort, deleteSelf);
|
||||
|
||||
/// <summary>
|
||||
/// 将自身的出口连接传递给指定端口
|
||||
/// </summary>
|
||||
/// <param name="otherPort">指定端口</param>
|
||||
/// <param name="deleteSelf">传递后删除自身连接</param>
|
||||
public void GiveExitPortToOtherPort(NodePort otherPort, bool deleteSelf = false)
|
||||
=> GiveConnectionToOtherPort(GetOutputPort("exit"), otherPort, deleteSelf);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 节点操作
|
||||
|
||||
/// <summary>
|
||||
/// 向前插入节点
|
||||
/// </summary>
|
||||
[HorizontalGroup("Insert", order:1)]
|
||||
[Button("(←)插入节点")]
|
||||
public void InsertForward()
|
||||
{
|
||||
// 生成新的空节点
|
||||
var newNode = SelectionNode.Create(graph);
|
||||
// 将本节点的Enter连接给到新的节点的Enter
|
||||
GiveEnterPortToOtherPort(newNode.GetInputPort("enter"), true);
|
||||
// 将新的节点的Exit连接到本节点的Enter
|
||||
GetInputPort("enter").Connect(newNode.GetOutputPort("exit"));
|
||||
// 将新节点设定为本节点位置
|
||||
newNode.position = position;
|
||||
|
||||
// 将包括自身在内的所有后续节点向后移动一个默认间隔
|
||||
newNode.DoActionToAllNodesAfter((thisNode, otherNode) => otherNode.position = thisNode.position + new Vector2(DefaultNodeXInterval, DefaultNodeYInterval));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向后插入节点
|
||||
/// </summary>
|
||||
[HorizontalGroup("Insert")]
|
||||
[Button("插入节点(→)")]
|
||||
public void InsertBackward()
|
||||
{
|
||||
// 生成新的空节点
|
||||
var newNode = SelectionNode.Create(graph);
|
||||
// 将本节点的Exit连接给到新的节点的Exit
|
||||
GiveExitPortToOtherPort(newNode.GetOutputPort("exit"), true);
|
||||
// 将新的节点的Enter连接到本节点的Exit
|
||||
GetOutputPort("exit").Connect(newNode.GetInputPort("enter"));
|
||||
// 将新节点设置为本节点位置后移一格
|
||||
newNode.position = position + new Vector2(DefaultNodeXInterval, DefaultNodeYInterval);
|
||||
|
||||
// 将所有后续节点向后移动一个默认间隔
|
||||
newNode.DoActionToAllNodesAfter((thisNode, otherNode) => otherNode.position = thisNode.position + new Vector2(DefaultNodeXInterval, DefaultNodeYInterval));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 导出
|
||||
/// </summary>
|
||||
public new async UniTask<FlowNodeData> Export()
|
||||
{
|
||||
FlowNodeData node = new(await base.Export());
|
||||
node.executeType = exitNodeExecuteType;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e85b8c018e8b68b418fff4c389089665
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
134
Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs
Normal file
134
Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
[Serializable,CreateNodeMenu("")]
|
||||
public class NodeBase : Node
|
||||
{
|
||||
[Serializable] public class Enter { }
|
||||
[Serializable] public class Exit { }
|
||||
|
||||
/// <summary>
|
||||
/// 默认节点位置间隔(x轴)
|
||||
/// </summary>
|
||||
public const float DefaultNodeXInterval = 250;
|
||||
/// <summary>
|
||||
/// 默认节点位置间隔(y轴)
|
||||
/// </summary>
|
||||
public const float DefaultNodeYInterval = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 创建新的节点
|
||||
/// </summary>
|
||||
/// <param name="graph">所在的块</param>
|
||||
/// <param name="name">节点名称</param>
|
||||
/// <param name="position">节点位置</param>
|
||||
public static T Create<T>(NodeGraph graph, string name = null, Vector2 position = default) where T : NodeBase
|
||||
{
|
||||
// 创建节点
|
||||
var node = graph.AddNode<T>();
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
Debug.LogError("节点创建失败");
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// 将节点落盘
|
||||
UnityEditor.AssetDatabase.AddObjectToAsset(node, graph);
|
||||
UnityEditor.EditorUtility.SetDirty(graph);
|
||||
#endif
|
||||
|
||||
// 设置节点变量
|
||||
node.position = position;
|
||||
node.name = name ?? $"{name}_para_sample";
|
||||
|
||||
// 初始化节点
|
||||
node.Init();
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除自身
|
||||
/// </summary>
|
||||
public void DestroySelf()
|
||||
{
|
||||
// 清理连接
|
||||
ClearConnections();
|
||||
// 移除与Graph之间的联系
|
||||
if (graph)
|
||||
{
|
||||
graph.nodes.Remove(this);
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.AssetDatabase.RemoveObjectFromAsset(this);
|
||||
#endif
|
||||
}
|
||||
// 销毁自身
|
||||
DestroyImmediate(this);
|
||||
}
|
||||
|
||||
public UniTask<NodeData> Export()
|
||||
{
|
||||
NodeData nodeData = new();
|
||||
nodeData.name = name;
|
||||
return UniTask.FromResult(nodeData);
|
||||
}
|
||||
|
||||
#region 工具方法
|
||||
|
||||
/// <summary>
|
||||
/// 将端口A的连接传递给端口B
|
||||
/// </summary>
|
||||
/// <param name="portA">端口A</param>
|
||||
/// <param name="portB">端口B</param>
|
||||
/// <param name="deleteA">移除端口A的连接</param>
|
||||
public static void GiveConnectionToOtherPort(NodePort portA, NodePort portB, bool deleteA = false)
|
||||
{
|
||||
// 排除端口A与端口B重合的情况
|
||||
if (portA == portB)
|
||||
return;
|
||||
|
||||
// 获取当前Enter端口的所有连接
|
||||
var enterPorts = portA.GetConnections();
|
||||
|
||||
enterPorts.ForEach(port =>
|
||||
{
|
||||
// 将Enter端口与该端口断开
|
||||
if (deleteA)
|
||||
port.Disconnect(portA);
|
||||
// 将指定的端口与该端口连接
|
||||
port.Connect(portB);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对所有之后的节点执行动作
|
||||
/// </summary>
|
||||
/// <param name="action">指定动作</param>
|
||||
public void DoActionToAllNodesAfter(Action<NodeBase, NodeBase> action)
|
||||
{
|
||||
// 所有出口端口
|
||||
Ports.Where(p => p.IsOutput).ToList().ForEach(port =>
|
||||
{
|
||||
// 所有出口端口连接的所有端口
|
||||
port.GetConnections().ForEach(otherSide =>
|
||||
{
|
||||
// 所有出口端口连接的所有端口的是NodeBase的节点
|
||||
if (otherSide.node is NodeBase baseNode)
|
||||
{
|
||||
action?.Invoke(this, baseNode);
|
||||
baseNode.DoActionToAllNodesAfter(action);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 128757ef2e3a23c48b67ade15852c36c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本自然段
|
||||
/// </summary>
|
||||
[Serializable, CreateNodeMenu("创建剧本自然段", order = 5)]
|
||||
public class ScriptParagraphNode : FlowNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// (仅供辨识,不起实际作用)
|
||||
/// </summary>
|
||||
[BoxGroup("Info", order:10, showLabel:false)]
|
||||
[LabelText("名称"),LabelWidth(30)]
|
||||
public string displayName;
|
||||
|
||||
/// <summary>
|
||||
/// 字幕
|
||||
/// </summary>
|
||||
[BoxGroup("Info")]
|
||||
[LabelText("字幕"),LabelWidth(30)]
|
||||
public Sprite caption;
|
||||
/// <summary>
|
||||
/// 语音
|
||||
/// </summary>
|
||||
[BoxGroup("Info")]
|
||||
[LabelText("音频"),LabelWidth(30)]
|
||||
public AudioClip audio;
|
||||
|
||||
/// <summary>
|
||||
/// 创建新的剧本段落节点
|
||||
/// </summary>
|
||||
/// <param name="graph">所在的块</param>
|
||||
/// <param name="name">节点名称</param>
|
||||
/// <param name="position">节点位置</param>
|
||||
/// <param name="prePorts">节点前部连接</param>
|
||||
/// <param name="nextPorts">节点后部连接</param>
|
||||
/// <param name="caption">字幕</param>
|
||||
/// <param name="audio">音频</param>
|
||||
public static ScriptParagraphNode Create(NodeGraph graph, string name = null, Vector2 position = default, List<NodePort> prePorts = null, List<NodePort> nextPorts = null, Sprite caption = null, AudioClip audio = null)
|
||||
{
|
||||
// 创建节点
|
||||
var node = Create<ScriptParagraphNode>(graph,name,position, prePorts, nextPorts);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
// 设置变量
|
||||
node.caption = caption;
|
||||
node.audio = audio;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
base.Init();
|
||||
UpdateName();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (graph == null)
|
||||
{
|
||||
Debug.LogWarning($"项目中存在幽灵节点:{name}");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateName();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新名称
|
||||
/// </summary>
|
||||
private void UpdateName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(displayName))
|
||||
{
|
||||
displayName = graph.name + "_para";
|
||||
}
|
||||
|
||||
name = displayName;
|
||||
}
|
||||
|
||||
public new async UniTask<ScriptParagraphNodeData> Export()
|
||||
{
|
||||
var node = new ScriptParagraphNodeData(await base.Export());
|
||||
var sGraph = (ScriptGraph)graph;
|
||||
|
||||
// 记录音频资源地址
|
||||
if (audio)
|
||||
node.audioPath = await sGraph.Loader.Save(audio,sGraph.packageID);
|
||||
|
||||
// 记录字幕资源地址
|
||||
if (caption)
|
||||
node.captionPath = await sGraph.Loader.Save(caption,sGraph.packageID);
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e0dc65de1614514883efb2a6848e5a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 空节点
|
||||
/// (可选择应用哪种节点)
|
||||
/// </summary>
|
||||
[Serializable, CreateNodeMenu("创建空节点", order = 0)]
|
||||
public class SelectionNode : FlowNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建新的空节点
|
||||
/// </summary>
|
||||
/// <param name="graph">所在的块</param>
|
||||
/// <param name="name">节点名称</param>
|
||||
/// <param name="position">节点位置</param>
|
||||
/// <param name="prePorts">节点前部连接</param>
|
||||
/// <param name="nextPorts">节点后部连接</param>
|
||||
public static SelectionNode Create(NodeGraph graph, string name = null, Vector2 position = default,
|
||||
List<NodePort> prePorts = null, List<NodePort> nextPorts = null)
|
||||
{
|
||||
var node = Create<SelectionNode>(graph, name, position, prePorts, nextPorts);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建剧本段落
|
||||
/// </summary>
|
||||
[BoxGroup("Option", order:20, showLabel:false)]
|
||||
[Button("剧本段落")]
|
||||
public void ChooseScriptParagraph()
|
||||
{
|
||||
// 记录动作缓存
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.Undo.RecordObject(graph, "Delete Node");
|
||||
#endif
|
||||
|
||||
// 在本节点位置创建剧本段落节点
|
||||
var node = ScriptParagraphNode.Create(graph, position: position);
|
||||
GiveEnterPortToOtherPort(node.GetInputPort("enter"), true);
|
||||
GiveExitPortToOtherPort(node.GetOutputPort("exit"), true);
|
||||
|
||||
// 移除本节点
|
||||
DestroySelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79ef9a28ecfd1384a96569da56b2022b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user