using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using Sirenix.OdinInspector;
using UnityEngine;
using XNode;
namespace Stary.Evo.StoryEditor
{
[Serializable]
public class ScriptGraph : NodeGraph
{
///
/// 起始节点
///
[SerializeField, ReadOnly]
private BeginNode begin;
///
/// 结束节点
///
[SerializeField, ReadOnly]
private EndNode end;
///
/// 初始化标识
///
[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 初始化
///
/// 延迟初始化
///
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;
}
///
/// 初始化起始节点
///
private void InitBeginNode()
{
var existing = nodes.FirstOrDefault(n => n is BeginNode) as BeginNode;
if (existing != null)
{
begin = existing;
return;
}
begin = NodeBase.Create(this, "Begin", Vector2.zero);
}
///
/// 初始化结束节点
///
private void InitEndNode()
{
var existing = nodes.FirstOrDefault(n => n is EndNode) as EndNode;
if (existing != null)
{
end = existing;
return;
}
end = NodeBase.Create(this, "End", new Vector2(NodeBase.DefaultNodeXInterval, NodeBase.DefaultNodeYInterval)* 2);
}
///
/// 创建剧本段落样例
///
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 GetAllAssemblyNames() =>
AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
///
/// 获取继承 IResource 的所有类
///
private HashSet IResourceTypes
{
get
{
if (string.IsNullOrEmpty(assembly))
return new();
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assembly);
return asm == null ? new() : asm.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(i => i.Name == nameof(IResource))).Select(t => t.Name).ToHashSet();
}
}
#else
private HashSet _iResourceTypes = new();
#endif
///
/// 资源加载方式
///
[BoxGroup("Export", centerLabel:true)]
[LabelText("资源加载方式"), ValueDropdown(nameof(IResourceTypes)),SerializeField]
private string loaderType;
private IResource _loader;
///
/// 资源加载器
///
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 dataMatch = new();
Dictionary 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
}
}