280 lines
9.5 KiB
C#
280 lines
9.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
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 导出设置
|
||
|
||
#if UNITY_EDITOR
|
||
|
||
[BoxGroup("Export")]
|
||
[SerializeField, ValueDropdown(nameof(GetAllAssemblyNames))]
|
||
private string assembly;
|
||
private IEnumerable<string> GetAllAssemblyNames() =>
|
||
AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
|
||
|
||
/// <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
|
||
|
||
/// <summary>
|
||
/// 资源加载方式
|
||
/// </summary>
|
||
[BoxGroup("Export", centerLabel:true)]
|
||
[LabelText("资源加载方式"), ValueDropdown(nameof(_iResourceTypes)),SerializeField]
|
||
private string loaderType;
|
||
|
||
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
|
||
}
|
||
} |