【a】可视化剧本编辑器 10.StoryEditor
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c081c2eb3009043870d3700881058e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52e52a16b8e597e48a3c645e65005b09
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class GraphData
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本名称
|
||||
/// </summary>
|
||||
public string name;
|
||||
/// <summary>
|
||||
/// 节点
|
||||
/// </summary>
|
||||
public List<NodeData> nodes = new();
|
||||
/// <summary>
|
||||
/// 起始节点索引
|
||||
/// </summary>
|
||||
public int startNodeIndex;
|
||||
/// <summary>
|
||||
/// 资源加载类型
|
||||
/// </summary>
|
||||
public string resourceType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c69fad22bb97abf42a50d1b2d0e74a42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77eaac9f599722345a3a3ae21a895fbd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
[Serializable]
|
||||
public class BeginNodeData : NodeData
|
||||
{
|
||||
public List<int> next = new();
|
||||
|
||||
public BeginNodeData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BeginNodeData(NodeData data)
|
||||
{
|
||||
name = data.name;
|
||||
type = NodeType.Begin;
|
||||
}
|
||||
|
||||
public new BeginNodePlayer GetPlayer(GraphPlayer graph)=>
|
||||
new(graph, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc62c71e8fb75c24eb096c74a651309d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
[Serializable]
|
||||
public class EndNodeData : NodeData
|
||||
{
|
||||
public List<int> pre = new();
|
||||
|
||||
public EndNodeData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public EndNodeData(NodeData data)
|
||||
{
|
||||
name = data.name;
|
||||
type = NodeType.End;
|
||||
}
|
||||
|
||||
public new EndNodePlayer GetPlayer(GraphPlayer graph) =>
|
||||
new(graph, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 407d564c2ca4c65499f8bf38949aec58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 空节点(流程节点)数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class FlowNodeData : NodeData
|
||||
{
|
||||
public List<int> pre = new();
|
||||
public List<int> next = new();
|
||||
|
||||
/// <summary>
|
||||
/// 后续连接执行类型
|
||||
/// </summary>
|
||||
public NodeExecuteType executeType = NodeExecuteType.Async;
|
||||
|
||||
public FlowNodeData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public FlowNodeData(NodeData data)
|
||||
{
|
||||
name = data.name;
|
||||
type = NodeType.Empty;
|
||||
}
|
||||
|
||||
public new FlowNodePlayer GetPlayer(GraphPlayer graph)=>
|
||||
new(graph, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0bafbcc38995f04c9189cc5a1491099
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NodeData
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点名称
|
||||
/// </summary>
|
||||
public string name = "Unnamed Node";
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
public NodeType type = NodeType.Empty;
|
||||
|
||||
public NodePlayer GetPlayer(GraphPlayer graph)=>
|
||||
new(graph, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d46e1dcd9310eed41a27d6e0c8c5066b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本段落节点数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ScriptParagraphNodeData : FlowNodeData
|
||||
{
|
||||
/// <summary>
|
||||
/// 字幕路径
|
||||
/// </summary>
|
||||
public ResourcePathData captionPath;
|
||||
/// <summary>
|
||||
/// 语音路径
|
||||
/// </summary>
|
||||
public ResourcePathData audioPath;
|
||||
|
||||
public ScriptParagraphNodeData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ScriptParagraphNodeData(FlowNodeData data) : base(data)
|
||||
{
|
||||
type = NodeType.Paragraph;
|
||||
executeType = data.executeType;
|
||||
}
|
||||
|
||||
public new ScriptParagraphNodePlayer GetPlayer(GraphPlayer graph) =>
|
||||
new(graph,this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2ddf1032a2ad2541ab9ddae9772d0d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public enum NodeType
|
||||
{
|
||||
/// <summary>
|
||||
/// 空节点(流程节点)
|
||||
/// </summary>
|
||||
Empty,
|
||||
/// <summary>
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
Begin,
|
||||
/// <summary>
|
||||
/// 结束节点
|
||||
/// </summary>
|
||||
End,
|
||||
/// <summary>
|
||||
/// 剧本段落
|
||||
/// </summary>
|
||||
Paragraph
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点执行类型
|
||||
/// </summary>
|
||||
public enum NodeExecuteType
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步
|
||||
/// </summary>
|
||||
[LabelText("异步"), Tooltip("异步执行意味着Exit连接的多条支路将同时进行")]
|
||||
Async,
|
||||
/// <summary>
|
||||
/// 同步
|
||||
/// </summary>
|
||||
[LabelText("同步"), Tooltip("同步执行意味着Exit连接的多条支路将依次进行")]
|
||||
Sync
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d4772e98c371834284ea14f4b641510
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edf21b4cd79b9cf459596d41bb2a1da2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a786bcde0f40a044b8e37ee058a125d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class GraphPlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本名称
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// 节点
|
||||
/// </summary>
|
||||
public List<NodePlayer> Nodes = new();
|
||||
|
||||
/// <summary>
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
public BeginNodePlayer BeginNode;
|
||||
/// <summary>
|
||||
/// 结束节点
|
||||
/// </summary>
|
||||
public EndNodePlayer EndNode;
|
||||
|
||||
/// <summary>
|
||||
/// 当前进行中的节点
|
||||
/// </summary>
|
||||
public List<NodePlayer> CurrentNodes = new();
|
||||
|
||||
/// <summary>
|
||||
/// 播放完成
|
||||
/// </summary>
|
||||
public bool Finished;
|
||||
|
||||
/// <summary>
|
||||
/// CTS
|
||||
/// </summary>
|
||||
public CancellationTokenSource Cts = new();
|
||||
|
||||
public GraphPlayer(GraphData data, IResource loader)
|
||||
{
|
||||
// 检查加载方式
|
||||
if (loader.GetType().ToString() != data.resourceType)
|
||||
{
|
||||
Debug.LogError($"加载方式不匹配,剧本无法加载:{data.name}\n需要的加载方式: {data.resourceType}");
|
||||
return;
|
||||
}
|
||||
|
||||
Name = data.name;
|
||||
data.nodes.ForEach(node =>
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case BeginNodeData beginNode:
|
||||
Nodes.Add(beginNode.GetPlayer(this));
|
||||
break;
|
||||
case EndNodeData endNode:
|
||||
Nodes.Add(endNode.GetPlayer(this));
|
||||
break;
|
||||
case ScriptParagraphNodeData paraNode:
|
||||
Nodes.Add(paraNode.GetPlayer(this));
|
||||
break;
|
||||
case FlowNodeData flowNode:
|
||||
Nodes.Add(flowNode.GetPlayer(this));
|
||||
break;
|
||||
}
|
||||
});
|
||||
BeginNode = (BeginNodePlayer)Nodes[data.startNodeIndex];
|
||||
BeginNode.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行剧本
|
||||
/// </summary>
|
||||
public async UniTask Execute()
|
||||
{
|
||||
// 初始化结束标志
|
||||
Finished = false;
|
||||
// 执行剧本
|
||||
_ = BeginNode.Execute();
|
||||
// 等待剧本完成
|
||||
await UniTask.WaitUntil(() => Finished);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止剧本
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
// 取消并重置CancelToken
|
||||
Cts.Cancel();
|
||||
Cts = new();
|
||||
// 标记剧本结束
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce92dbf7b238c4442b75ea500c804107
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adab23e0caf805e45b47a9a0c88ec076
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class BeginNodePlayer : NodePlayer
|
||||
{
|
||||
public new BeginNodeData Data;
|
||||
public List<NodePlayer> Next = new();
|
||||
|
||||
public BeginNodePlayer(GraphPlayer graph, BeginNodeData data) : base(graph, data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
Debug.Log("BeginNodePlayer: Connect");
|
||||
if (!base.Connect())
|
||||
return false;
|
||||
|
||||
Data.next.ForEach(index => Next.Add(Graph.Nodes[index]));
|
||||
Next.ForEach(node => node.Connect());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async UniTask Execute()
|
||||
{
|
||||
Init();
|
||||
Debug.Log($"开始执行剧本: {Graph.Name}");
|
||||
await base.Execute();
|
||||
await MoveNext();
|
||||
}
|
||||
|
||||
public override UniTask MoveNext()
|
||||
{
|
||||
Next.ForEach(node => node?.Execute());
|
||||
return base.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5fb5ecf3bf3ef64996bd3a7905884f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class EndNodePlayer : NodePlayer
|
||||
{
|
||||
public new EndNodeData Data;
|
||||
|
||||
public List<NodePlayer> Pre = new();
|
||||
|
||||
public EndNodePlayer(GraphPlayer graph, EndNodeData data) : base(graph, data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
if(!base.Connect())
|
||||
return false;
|
||||
|
||||
Data.pre.ForEach(index => Pre.Add(Graph.Nodes[index]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override UniTask Execute()
|
||||
{
|
||||
Init();
|
||||
Debug.Log($"剧本执行完成: {Graph.Name}");
|
||||
// 标记剧本完成
|
||||
Graph.Finished = true;
|
||||
ScriptPlayer.ReleaseGraph();
|
||||
return base.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6894cd8522d600447966dc11b2a13905
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class FlowNodePlayer : NodePlayer
|
||||
{
|
||||
public new FlowNodeData Data;
|
||||
|
||||
public List<NodePlayer> Pre = new();
|
||||
public List<NodePlayer> Next = new();
|
||||
|
||||
/// <summary>
|
||||
/// 后续连接执行类型
|
||||
/// </summary>
|
||||
public NodeExecuteType ExecuteType;
|
||||
|
||||
public FlowNodePlayer(GraphPlayer graph, FlowNodeData data) : base(graph, data)
|
||||
{
|
||||
Data = data;
|
||||
ExecuteType = data.executeType;
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
if(!base.Connect())
|
||||
return false;
|
||||
|
||||
Data.pre.ForEach(index => Pre.Add(Graph.Nodes[index]));
|
||||
Data.next.ForEach(index => Next.Add(Graph.Nodes[index]));
|
||||
Next.ForEach(node => node.Connect());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
public override async UniTask Execute()
|
||||
{
|
||||
await base.Execute();
|
||||
await MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向下继续执行
|
||||
/// </summary>
|
||||
public override async UniTask MoveNext()
|
||||
{
|
||||
// 异步执行(并行)
|
||||
if (ExecuteType == NodeExecuteType.Async)
|
||||
{
|
||||
Next.ForEach(node => node?.Execute());
|
||||
}
|
||||
// 同步执行(串行)
|
||||
else
|
||||
{
|
||||
foreach (var node in Next.Where(node => node != null))
|
||||
{
|
||||
await node.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58805d324b439d242a445138bc3a33a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class NodePlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点名称
|
||||
/// </summary>
|
||||
protected string Name;
|
||||
/// <summary>
|
||||
/// 图表
|
||||
/// </summary>
|
||||
protected GraphPlayer Graph;
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
protected NodeData Data;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化标志
|
||||
/// </summary>
|
||||
protected bool Initialized;
|
||||
|
||||
public NodePlayer(GraphPlayer graph, NodeData data)
|
||||
{
|
||||
Graph = graph;
|
||||
Name = data.name;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接节点
|
||||
/// </summary>
|
||||
public virtual bool Connect()
|
||||
{
|
||||
if(Initialized)
|
||||
return false;
|
||||
Initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
public virtual void Init() => Graph.CurrentNodes.Add(this);
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
public virtual UniTask Execute()
|
||||
{
|
||||
Graph.CurrentNodes.Remove(this);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
/// <summary>
|
||||
/// 向下继续执行
|
||||
/// </summary>
|
||||
public virtual UniTask MoveNext() => UniTask.CompletedTask;
|
||||
/// <summary>
|
||||
/// 停止执行
|
||||
/// </summary>
|
||||
public virtual UniTask Stop() => UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b76627cea0be464c864e744ae0f0b45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public class ScriptParagraphNodePlayer : FlowNodePlayer
|
||||
{
|
||||
public new ScriptParagraphNodeData Data;
|
||||
|
||||
/// <summary>
|
||||
/// 字幕路径
|
||||
/// </summary>
|
||||
public ResourcePathData CaptionPath;
|
||||
/// <summary>
|
||||
/// 语音路径
|
||||
/// </summary>
|
||||
public ResourcePathData AudioPath;
|
||||
|
||||
public ScriptParagraphNodePlayer(GraphPlayer graph, ScriptParagraphNodeData data) : base(graph, data)
|
||||
{
|
||||
Data = data;
|
||||
CaptionPath = data.captionPath;
|
||||
AudioPath = data.audioPath;
|
||||
}
|
||||
|
||||
public override async UniTask Execute()
|
||||
{
|
||||
Init();
|
||||
await ScriptPlayer.PlayScriptPara(this, Graph.Cts.Token);
|
||||
await base.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b263f1eba1f46094cab95e1c34ff2605
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DG.Tweening;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 剧本播放器
|
||||
/// </summary>
|
||||
public static class ScriptPlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源加载工具
|
||||
/// </summary>
|
||||
private static IResource _loader;
|
||||
|
||||
/// <summary>
|
||||
/// 动画单次时长
|
||||
/// </summary>
|
||||
private const float AnimationTime = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// 字幕组件
|
||||
/// </summary>
|
||||
private static SpriteRenderer _caption;
|
||||
/// <summary>
|
||||
/// 音频组件
|
||||
/// </summary>
|
||||
private static AudioSource _audio;
|
||||
|
||||
/// <summary>
|
||||
/// 当前执行的剧本
|
||||
/// </summary>
|
||||
private static GraphPlayer _graph;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化剧本播放器
|
||||
/// </summary>
|
||||
/// <param name="loader">资源加载工具</param>
|
||||
/// <param name="audio">音频组件</param>
|
||||
/// <param name="caption">字幕组件</param>
|
||||
public static void Init(IResource loader, AudioSource audio = null, SpriteRenderer caption = null)
|
||||
{
|
||||
_loader = loader;
|
||||
_caption = caption;
|
||||
_audio = audio;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public static void Release()
|
||||
{
|
||||
_loader = null;
|
||||
_caption = null;
|
||||
_audio = null;
|
||||
Resources.UnloadUnusedAssets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放剧本
|
||||
/// </summary>
|
||||
/// <param name="packageID">包体ID</param>
|
||||
/// <param name="scriptName">剧本名称(不用加_txt后缀)</param>
|
||||
public static async UniTask Play(string packageID, string scriptName)
|
||||
{
|
||||
// 资源加载工具排空
|
||||
if (_loader == null)
|
||||
{
|
||||
Debug.LogError("资源加载工具未准备好");
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载剧本
|
||||
ResourcePathData path = new(packageID,scriptName);
|
||||
var json = await _loader.Load<TextAsset>(path);
|
||||
var scriptData = JsonConvert.DeserializeObject<GraphData>(json.text, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
|
||||
if (scriptData == null)
|
||||
{
|
||||
Debug.LogError($"剧本加载失败:[{path.packageID}]{path.path}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析并执行剧本
|
||||
_graph = new(scriptData, _loader);
|
||||
await _graph.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放剧本段落
|
||||
/// </summary>
|
||||
/// <param name="para">段落数据</param>
|
||||
/// <param name="ct">CT</param>
|
||||
public static async UniTask PlayScriptPara(ScriptParagraphNodePlayer para, CancellationToken ct)
|
||||
{
|
||||
// 设置变量
|
||||
if (_caption)
|
||||
_caption.sprite = await _loader.Load<Sprite>(para.CaptionPath);
|
||||
if (_audio)
|
||||
_audio.clip = await _loader.Load<AudioClip>(para.AudioPath);
|
||||
|
||||
// 淡入
|
||||
_caption?.DOColor(Color.white, AnimationTime);
|
||||
_audio?.DOFade(1, AnimationTime);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(AnimationTime), cancellationToken:ct);
|
||||
|
||||
// 等音频播放完
|
||||
if (_audio)
|
||||
{
|
||||
_audio.Play();
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(_audio.clip.length), cancellationToken:ct);
|
||||
}
|
||||
|
||||
// 淡出
|
||||
_caption?.DOColor(Color.clear, AnimationTime);
|
||||
_audio?.DOFade(0, AnimationTime);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(AnimationTime), cancellationToken:ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止剧本
|
||||
/// </summary>
|
||||
public static async UniTask Stop()
|
||||
{
|
||||
_graph.Stop();
|
||||
|
||||
var hasAnim = false;
|
||||
if (_caption && _caption.color != Color.clear)
|
||||
{
|
||||
_caption?.DOColor(Color.clear, AnimationTime);
|
||||
hasAnim = true;
|
||||
}
|
||||
if (_audio && _audio.volume > 0)
|
||||
{
|
||||
_audio?.DOFade(0, AnimationTime);
|
||||
hasAnim = true;
|
||||
}
|
||||
if(hasAnim)
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(AnimationTime));
|
||||
|
||||
ReleaseGraph();
|
||||
}
|
||||
|
||||
public static void ReleaseGraph() => _graph = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9bd336685230b746bc67a3bb9dc2102
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e59434304b735b44b6f025c51615cfa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
public interface IResource
|
||||
{
|
||||
/// <summary>
|
||||
/// 加载资源
|
||||
/// Json => 资源
|
||||
/// </summary>
|
||||
/// <param name="pathData">资源路径</param>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
UniTask<T> Load<T>(ResourcePathData pathData) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 保存资源
|
||||
/// 资源 => Json
|
||||
/// </summary>
|
||||
/// <param name="asset">资源</param>
|
||||
/// <param name="packageID">包体ID</param>
|
||||
UniTask<ResourcePathData> Save<T>(T asset, string packageID = null) where T : Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68c399cab4c86fc4b961516a2802b94d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Stary.Evo.StoryEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源路径数据
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct ResourcePathData
|
||||
{
|
||||
/// <summary>
|
||||
/// 包体ID
|
||||
/// </summary>
|
||||
public string packageID;
|
||||
|
||||
/// <summary>
|
||||
/// 资源路径
|
||||
/// </summary>
|
||||
public string path;
|
||||
|
||||
public ResourcePathData(string packageID, string path)
|
||||
{
|
||||
this.packageID = packageID;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void AddPath(params string[] tail)
|
||||
{
|
||||
if(tail == null || tail.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var t in tail)
|
||||
{
|
||||
path = string.IsNullOrEmpty(path) ? t : System.IO.Path.Combine(path, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fd990a65a7964740b2d98d2557afbb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48bedbe80b198ef4d8811fac3b41d204
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f25ed558908de741b709bb8f3df2427
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90e4f51c40e7d9340a88df558072b353
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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