Files
plugin-library/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
mzh f5a79aabf8 【m】10. StoryEditor [1.1.0] - 2026-01-08
### Added
- 新增剧本设置窗口,在窗口中打开和导出剧本
### Fixed
- 修改Graph创建时的默认位置,防止热更分包导致脚本丢失报错
- 修改Graph导出时对加载器的加载逻辑,解决跨包无法加载的问题
2026-01-08 17:31:26 +08:00

269 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <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
[BoxGroup("Export", centerLabel:true)]
[LabelText("程序集")]
public string assembly;
/// <summary>
/// 资源加载方式
/// </summary>
[BoxGroup("Export")]
[LabelText("资源加载方式")]
public string loaderType;
private IResource _loader;
/// <summary>
/// 资源加载器
/// </summary>
public IResource Loader
{
get
{
if (_loader == null && !string.IsNullOrEmpty(loaderType))
{
var asm = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(loaderType)).FirstOrDefault(t => t != null) ?? AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(loaderType, false, false)).FirstOrDefault(t => t != null);
if (asm != null)
{
_loader = (IResource)Activator.CreateInstance(asm);
}
}
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);
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
}
}