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 } }