From 2e8accfed8e2a2833f4d8cb241621e2a5bf12b59 Mon Sep 17 00:00:00 2001 From: mzh <3213885650@qq.com> Date: Tue, 6 Jan 2026 14:24:23 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90a=E3=80=91=E5=8F=AF=E8=A7=86=E5=8C=96?= =?UTF-8?q?=E5=89=A7=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8=2010.StoryEditor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 6 + Assets/10.StoryEditor/Editor.meta | 8 + Assets/10.StoryEditor/Editor/Graph.meta | 8 + .../Editor/Graph/GraphCreateWindow.cs | 193 +++++ .../Editor/Graph/GraphCreateWindow.cs.meta | 11 + .../Editor/Graph/ScriptGraphEditor.cs | 14 + .../Editor/Graph/ScriptGraphEditor.cs.meta | 11 + Assets/10.StoryEditor/Editor/Node.meta | 8 + .../Editor/Node/BeginNodeEditor.cs | 19 + .../Editor/Node/BeginNodeEditor.cs.meta | 11 + .../Editor/Node/EndNodeEditor.cs | 19 + .../Editor/Node/EndNodeEditor.cs.meta | 11 + Assets/10.StoryEditor/README.md | 30 + Assets/10.StoryEditor/README.md.meta | 7 + Assets/10.StoryEditor/RunTime.meta | 8 + Assets/10.StoryEditor/RunTime/Data.meta | 8 + Assets/10.StoryEditor/RunTime/Data/Graph.meta | 8 + .../RunTime/Data/Graph/GraphData.cs | 29 + .../RunTime/Data/Graph/GraphData.cs.meta | 11 + Assets/10.StoryEditor/RunTime/Data/Node.meta | 8 + .../RunTime/Data/Node/BeginNodeData.cs | 25 + .../RunTime/Data/Node/BeginNodeData.cs.meta | 11 + .../RunTime/Data/Node/EndNodeData.cs | 25 + .../RunTime/Data/Node/EndNodeData.cs.meta | 11 + .../RunTime/Data/Node/FlowNodeData.cs | 34 + .../RunTime/Data/Node/FlowNodeData.cs.meta | 11 + .../RunTime/Data/Node/NodeData.cs | 23 + .../RunTime/Data/Node/NodeData.cs.meta | 11 + .../Data/Node/ScriptParagraphNodeData.cs | 34 + .../Data/Node/ScriptParagraphNodeData.cs.meta | 11 + Assets/10.StoryEditor/RunTime/NodeEnum.cs | 42 ++ .../10.StoryEditor/RunTime/NodeEnum.cs.meta | 11 + Assets/10.StoryEditor/RunTime/Player.meta | 8 + .../10.StoryEditor/RunTime/Player/Graph.meta | 8 + .../RunTime/Player/Graph/GraphPlayer.cs | 101 +++ .../RunTime/Player/Graph/GraphPlayer.cs.meta | 11 + .../10.StoryEditor/RunTime/Player/Node.meta | 8 + .../RunTime/Player/Node/BeginNodePlayer.cs | 43 ++ .../Player/Node/BeginNodePlayer.cs.meta | 11 + .../RunTime/Player/Node/EndNodePlayer.cs | 38 + .../RunTime/Player/Node/EndNodePlayer.cs.meta | 11 + .../RunTime/Player/Node/FlowNodePlayer.cs | 66 ++ .../Player/Node/FlowNodePlayer.cs.meta | 11 + .../RunTime/Player/Node/NodePlayer.cs | 65 ++ .../RunTime/Player/Node/NodePlayer.cs.meta | 11 + .../Player/Node/ScriptParagraphNodePlayer.cs | 32 + .../Node/ScriptParagraphNodePlayer.cs.meta | 11 + .../RunTime/Player/ScriptPlayer.cs | 149 ++++ .../RunTime/Player/ScriptPlayer.cs.meta | 11 + Assets/10.StoryEditor/RunTime/Resource.meta | 8 + .../RunTime/Resource/IResource.cs | 24 + .../RunTime/Resource/IResource.cs.meta | 11 + .../RunTime/Resource/ResourcePathData.cs | 38 + .../RunTime/Resource/ResourcePathData.cs.meta | 11 + .../10.StoryEditor/RunTime/VisualEditor.meta | 8 + .../RunTime/VisualEditor/Graph.meta | 8 + .../RunTime/VisualEditor/Graph/ScriptGraph.cs | 272 +++++++ .../VisualEditor/Graph/ScriptGraph.cs.meta | 11 + .../RunTime/VisualEditor/Node.meta | 8 + .../RunTime/VisualEditor/Node/BeginNode.cs | 20 + .../VisualEditor/Node/BeginNode.cs.meta | 11 + .../RunTime/VisualEditor/Node/EndNode.cs | 20 + .../RunTime/VisualEditor/Node/EndNode.cs.meta | 11 + .../RunTime/VisualEditor/Node/FlowNode.cs | 128 ++++ .../VisualEditor/Node/FlowNode.cs.meta | 11 + .../RunTime/VisualEditor/Node/NodeBase.cs | 134 ++++ .../VisualEditor/Node/NodeBase.cs.meta | 11 + .../VisualEditor/Node/ScriptParagraphNode.cs | 107 +++ .../Node/ScriptParagraphNode.cs.meta | 11 + .../VisualEditor/Node/SelectionNode.cs | 52 ++ .../VisualEditor/Node/SelectionNode.cs.meta | 11 + Assets/10.StoryEditor/Sample~/Sample.unity | 693 ++++++++++++++++++ .../10.StoryEditor/Sample~/Sample.unity.meta | 7 + Assets/10.StoryEditor/Sample~/Script.meta | 8 + .../Sample~/Script/ResourceLoader.cs | 20 + .../Sample~/Script/ResourceLoader.cs.meta | 11 + .../Sample~/Script/TestScriptPlayer.cs | 166 +++++ .../Sample~/Script/TestScriptPlayer.cs.meta | 11 + Assets/10.StoryEditor/package.json | 24 + Assets/10.StoryEditor/package.json.meta | 7 + 80 files changed, 3145 insertions(+) create mode 100644 Assets/10.StoryEditor/Editor.meta create mode 100644 Assets/10.StoryEditor/Editor/Graph.meta create mode 100644 Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs create mode 100644 Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs.meta create mode 100644 Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs create mode 100644 Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs.meta create mode 100644 Assets/10.StoryEditor/Editor/Node.meta create mode 100644 Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs create mode 100644 Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs.meta create mode 100644 Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs create mode 100644 Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs.meta create mode 100644 Assets/10.StoryEditor/README.md create mode 100644 Assets/10.StoryEditor/README.md.meta create mode 100644 Assets/10.StoryEditor/RunTime.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Graph.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/NodeEnum.cs create mode 100644 Assets/10.StoryEditor/RunTime/NodeEnum.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Graph.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs create mode 100644 Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Resource.meta create mode 100644 Assets/10.StoryEditor/RunTime/Resource/IResource.cs create mode 100644 Assets/10.StoryEditor/RunTime/Resource/IResource.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs create mode 100644 Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs.meta create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs create mode 100644 Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs.meta create mode 100644 Assets/10.StoryEditor/Sample~/Sample.unity create mode 100644 Assets/10.StoryEditor/Sample~/Sample.unity.meta create mode 100644 Assets/10.StoryEditor/Sample~/Script.meta create mode 100644 Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs create mode 100644 Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs.meta create mode 100644 Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs create mode 100644 Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs.meta create mode 100644 Assets/10.StoryEditor/package.json create mode 100644 Assets/10.StoryEditor/package.json.meta diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f23d08a..efd917f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,7 @@ stages: - 07.RKTools - 08.UniTask - 09.CodeChecker + - 10.StoryEditor - 11.PointCloudTools .template_job: &template_job @@ -90,6 +91,11 @@ job_RKTools: variables: MODULE_NAME: "07.RKTools" # 定义模块名称变量 stage: 07.RKTools +job_StoryEditor: + <<: *template_job + variables: + MODULE_NAME: "10.StoryEditor" # 定义模块名称变量 + stage: 10.StoryEditor job_PointCloudTools: <<: *template_job variables: diff --git a/Assets/10.StoryEditor/Editor.meta b/Assets/10.StoryEditor/Editor.meta new file mode 100644 index 0000000..2cfdb6e --- /dev/null +++ b/Assets/10.StoryEditor/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c40af5c425ac0874aabc6979f2a6e289 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Graph.meta b/Assets/10.StoryEditor/Editor/Graph.meta new file mode 100644 index 0000000..20a13dd --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Graph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7418c79b3f95ac9428a8836bccd4cb7a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs new file mode 100644 index 0000000..1c22093 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +// ReSharper disable Unity.PerformanceCriticalCodeInvocation + +namespace Stary.Evo.StoryEditor.Editor +{ + public class GraphCreateWindow : EditorWindow + { + private int _selectedIndex; + private string _scriptName = "graph"; + private bool _showTip; + private string _tip; + + private string[] _options = Array.Empty(); + private Dictionary _paths = new(); + + [MenuItem("Evo/剧本编辑器/创建剧本")] + private static void Open() + { + // 打开一个浮动窗口 + GetWindow( "创建配置" ) ; + } + + private void OnEnable() + { + minSize = maxSize = new Vector2(300, 200); + _options = GetPackages(); + } + + private void OnGUI() + { + GUILayout.Space(15); + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(10); + EditorGUILayout.BeginVertical(); + + // Package选项 + EditorGUILayout.LabelField( "请选择剧本所在的Package:" , EditorStyles.boldLabel ) ; + var newIndex = EditorGUILayout.Popup( _selectedIndex , _options ) ; + _selectedIndex = newIndex; + + GUILayout.Space(15); + + EditorGUILayout.LabelField( "请输入剧本名称" , EditorStyles.boldLabel ) ; + var newName = EditorGUILayout.TextField(_scriptName); + if (_scriptName != newName) + { + _scriptName = newName; + _showTip = false; + } + + if (_showTip) + { + GUIStyle redBold = new GUIStyle(EditorStyles.boldLabel) + { + normal = { textColor = Color.red } + }; + EditorGUILayout.LabelField(_tip, redBold) ; + } + + GUILayout.Space(15); + + // 创建按钮 + if (GUILayout.Button("创建剧本")) + { + try + { + CreateScriptGraph(_options[newIndex]); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + EditorGUILayout.EndVertical(); + GUILayout.Space(10); + EditorGUILayout.EndHorizontal(); + } + + /// + /// 获取所有Module Package + /// + private string[] GetPackages() + { + // 查找Modules目录 + List modules = new(); + GetDirectoryPaths(Application.dataPath, modules, "Modules"); + if (modules.Count == 0) + { + Debug.LogError("未找到任何Modules目录"); + return Array.Empty(); + } + + // 查找package + List packages = new(); + GetDirectoryPaths(modules[0], packages, "com."); + if (packages.Count == 0) + { + Debug.LogError("未找到任何Package"); + return Array.Empty(); + } + + // 记录Package地址并返回选项 + _paths.Clear(); + packages.ForEach(path => _paths.Add(Path.GetFileName(path), path)); + return _paths.Keys.ToArray(); + } + + /// + /// 获取符合条件的目录 + /// + /// 查找起始目录 + /// 查找结果 + /// 筛选条件(title) + /// 筛选条件(tail) + private static void GetDirectoryPaths(string root, List result, string title = null, string tail = null) + { + foreach (var dir in Directory.GetDirectories(root)) + { + // ReSharper disable once ReplaceWithSingleAssignment.True + var check = true; + + // 匹配文件头 + if (!string.IsNullOrEmpty(title) && !Path.GetFileName(dir).StartsWith(title)) + { + check = false; + } + // 匹配文件尾 + if (!string.IsNullOrEmpty(tail) && !Path.GetFileName(dir).EndsWith(tail)) + { + check = false; + } + + if(check) + result.Add(dir.Replace('\\', '/')); + + // 继续往下找 + GetDirectoryPaths(dir, result, title); + } + } + + /// + /// 创建剧本 + /// + /// 包体ID + private void CreateScriptGraph(string packageID) + { + // 检查剧本名称是否有效 + if (string.IsNullOrEmpty(_scriptName)) + { + _tip = "剧本名称不能为空"; + _showTip = true; + return; + } + + if (_paths.TryGetValue(packageID, out var path)) + { + // 包体目录排空 + var graphDir = Path.Combine(path, "Main","Res","Graphs"); + if (!Directory.Exists(graphDir)) + Directory.CreateDirectory(graphDir); + + // 检查资源存在性 + var graphPath = Path.Combine(graphDir, $"{_scriptName}.asset").Replace(Application.dataPath, "").Replace('\\', '/'); + graphPath = graphPath[1..]; + var graphFilePath = Path.Combine("Assets", graphPath); + var existAsset = AssetDatabase.LoadAssetAtPath(graphFilePath); + if (existAsset) + { + _tip = "该名称的剧本已存在"; + _showTip = true; + return; + } + + // 创建剧本图表 + var graph = CreateInstance(); + graph.packageID = packageID; + graph.graphPath = graphPath; + + AssetDatabase.CreateAsset(graph, graphFilePath); + } + else + { + Debug.LogError($"未记录Package路径:{packageID}"); + } + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs.meta b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs.meta new file mode 100644 index 0000000..724479c --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7ef12db2a2d4ab4d869971023fcd175 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs b/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs new file mode 100644 index 0000000..a0c9b32 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs @@ -0,0 +1,14 @@ +using XNodeEditor; + +namespace Stary.Evo.StoryEditor.Editor +{ + [CustomNodeGraphEditor(typeof(ScriptGraph))] + public class ScriptGraphEditor : NodeGraphEditor + { + public override void OnOpen() + { + base.OnOpen(); + NodeEditorWindow.current.panOffset = new(-350, -75); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs.meta b/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs.meta new file mode 100644 index 0000000..e038f94 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Graph/ScriptGraphEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0280f1b09f736a549a585dfe16f0b7da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Node.meta b/Assets/10.StoryEditor/Editor/Node.meta new file mode 100644 index 0000000..8e71320 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Node.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b06721b22b647a540a75c6f31a275c04 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs b/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs new file mode 100644 index 0000000..80249db --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs @@ -0,0 +1,19 @@ +using UnityEditor; +using XNodeEditor; + +namespace Stary.Evo.StoryEditor.Editor +{ + [CustomNodeEditor(typeof(BeginNode))] + public class BeginNodeEditor : NodeEditor + { + public override void OnBodyGUI() + { + serializedObject.Update(); + + EditorGUILayout.LabelField("剧本开始"); + base.OnBodyGUI(); + + serializedObject.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs.meta b/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs.meta new file mode 100644 index 0000000..2df26e4 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Node/BeginNodeEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ec8ed12f5fca194db8bf75ce458fb23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs b/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs new file mode 100644 index 0000000..c82994d --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs @@ -0,0 +1,19 @@ +using UnityEditor; +using XNodeEditor; + +namespace Stary.Evo.StoryEditor.Editor +{ + [CustomNodeEditor(typeof(EndNode))] + public class EndNodeEditor : NodeEditor + { + public override void OnBodyGUI() + { + serializedObject.Update(); + + EditorGUILayout.LabelField("剧本结束"); + base.OnBodyGUI(); + + serializedObject.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs.meta b/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs.meta new file mode 100644 index 0000000..3a9b246 --- /dev/null +++ b/Assets/10.StoryEditor/Editor/Node/EndNodeEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3651de4913e805c459198efe5e22e685 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/README.md b/Assets/10.StoryEditor/README.md new file mode 100644 index 0000000..929e0a4 --- /dev/null +++ b/Assets/10.StoryEditor/README.md @@ -0,0 +1,30 @@ +

可视化剧本编辑器

+ +
+ +

创建剧本

+ +1. 点击Unity顶部菜单栏`Evo/剧本编辑器/创建剧本` +2. 在打开的小窗口中选择要创建剧本的包,然后点击`创建剧本` +3. 新创建的剧本会在`*/Modules/[包体ID]/Main/Res/Graphs`路径下生成 + +

编辑剧本

+ +1. 双击剧本打开可视化编辑窗口 +2. 右键空白处可以新建节点(节点类型详见后续附录) +3. 按住Exit端口(或其他Output端口)可以拖拽出一条连接线,将其拖到任一节点的Enter端口(或其他Input端口)可以在两个节点之间建立连接,原则上禁止将一个节点的Exit端口与其自身的Enter端口相连,其造成的死循环问题自负 +4. 原则上剧本需要从`Begin节点`连接到`End节点` +5. 剧本会自动保存,但建议在关闭窗口前Ctrl+S进行手动保存以防万一 + +

导出剧本

+ +1. 导出剧本前需要先选择`资源加载方式`,为此需要创建一个继承`IResource`接口的类,实现接口后选中剧本,即可在Inspector面板选择资源加载方式 +2. 选择资源加载方式后,点击`导出`即可在同级目录下生成剧本的json文件 + +

使用剧本

+ +1. 使用前需要先调用ScriptPlayer.Init初始化剧本执行模块,并根据需要将字幕组件(SpriteRenderer)和音频组件(AudioSource)传入,加载器(IResource)是必传项 +2. 确保剧本的json文件能够被加载到(例如热更环境下需要将其打包到对应的AB包中),随后调用ScriptPlayer.Play即可播放剧本 +3. 在场景结束时需要调用ScriptPlayer.Release释放资源,以防止内存泄漏 + +
\ No newline at end of file diff --git a/Assets/10.StoryEditor/README.md.meta b/Assets/10.StoryEditor/README.md.meta new file mode 100644 index 0000000..9eb38c4 --- /dev/null +++ b/Assets/10.StoryEditor/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c353f6ac45228214bad134444f74f490 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime.meta b/Assets/10.StoryEditor/RunTime.meta new file mode 100644 index 0000000..5c6dfc2 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9652a98bfb5f6774ebed08cc80657c5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data.meta b/Assets/10.StoryEditor/RunTime/Data.meta new file mode 100644 index 0000000..11675c8 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 63c081c2eb3009043870d3700881058e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Graph.meta b/Assets/10.StoryEditor/RunTime/Data/Graph.meta new file mode 100644 index 0000000..77771a4 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Graph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52e52a16b8e597e48a3c645e65005b09 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs b/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs new file mode 100644 index 0000000..f5f7f3c --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 剧本数据 + /// + [Serializable] + public class GraphData + { + /// + /// 剧本名称 + /// + public string name; + /// + /// 节点 + /// + public List nodes = new(); + /// + /// 起始节点索引 + /// + public int startNodeIndex; + /// + /// 资源加载类型 + /// + public string resourceType; + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs.meta new file mode 100644 index 0000000..e632263 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Graph/GraphData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c69fad22bb97abf42a50d1b2d0e74a42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node.meta b/Assets/10.StoryEditor/RunTime/Data/Node.meta new file mode 100644 index 0000000..f4364ae --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77eaac9f599722345a3a3ae21a895fbd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs b/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs new file mode 100644 index 0000000..5e0113e --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Stary.Evo.StoryEditor +{ + [Serializable] + public class BeginNodeData : NodeData + { + public List next = new(); + + public BeginNodeData() + { + + } + + public BeginNodeData(NodeData data) + { + name = data.name; + type = NodeType.Begin; + } + + public new BeginNodePlayer GetPlayer(GraphPlayer graph)=> + new(graph, this); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs.meta new file mode 100644 index 0000000..9cc871b --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/BeginNodeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc62c71e8fb75c24eb096c74a651309d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs b/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs new file mode 100644 index 0000000..6300dfd --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Stary.Evo.StoryEditor +{ + [Serializable] + public class EndNodeData : NodeData + { + public List pre = new(); + + public EndNodeData() + { + + } + + public EndNodeData(NodeData data) + { + name = data.name; + type = NodeType.End; + } + + public new EndNodePlayer GetPlayer(GraphPlayer graph) => + new(graph, this); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs.meta new file mode 100644 index 0000000..4d5919f --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/EndNodeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 407d564c2ca4c65499f8bf38949aec58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs b/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs new file mode 100644 index 0000000..6b6d201 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 空节点(流程节点)数据 + /// + [Serializable] + public class FlowNodeData : NodeData + { + public List pre = new(); + public List next = new(); + + /// + /// 后续连接执行类型 + /// + 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); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs.meta new file mode 100644 index 0000000..612de59 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/FlowNodeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0bafbcc38995f04c9189cc5a1491099 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs b/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs new file mode 100644 index 0000000..930ba8d --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs @@ -0,0 +1,23 @@ +using System; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 节点数据 + /// + [Serializable] + public class NodeData + { + /// + /// 节点名称 + /// + public string name = "Unnamed Node"; + /// + /// 节点类型 + /// + public NodeType type = NodeType.Empty; + + public NodePlayer GetPlayer(GraphPlayer graph)=> + new(graph, this); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs.meta new file mode 100644 index 0000000..379eec4 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/NodeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d46e1dcd9310eed41a27d6e0c8c5066b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs b/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs new file mode 100644 index 0000000..5c65d34 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs @@ -0,0 +1,34 @@ +using System; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 剧本段落节点数据 + /// + [Serializable] + public class ScriptParagraphNodeData : FlowNodeData + { + /// + /// 字幕路径 + /// + public ResourcePathData captionPath; + /// + /// 语音路径 + /// + 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); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs.meta b/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs.meta new file mode 100644 index 0000000..ceaaec2 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Data/Node/ScriptParagraphNodeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2ddf1032a2ad2541ab9ddae9772d0d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/NodeEnum.cs b/Assets/10.StoryEditor/RunTime/NodeEnum.cs new file mode 100644 index 0000000..250ea26 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/NodeEnum.cs @@ -0,0 +1,42 @@ +using Sirenix.OdinInspector; +using UnityEngine; + +namespace Stary.Evo.StoryEditor +{ + public enum NodeType + { + /// + /// 空节点(流程节点) + /// + Empty, + /// + /// 起始节点 + /// + Begin, + /// + /// 结束节点 + /// + End, + /// + /// 剧本段落 + /// + Paragraph + } + + /// + /// 节点执行类型 + /// + public enum NodeExecuteType + { + /// + /// 异步 + /// + [LabelText("异步"), Tooltip("异步执行意味着Exit连接的多条支路将同时进行")] + Async, + /// + /// 同步 + /// + [LabelText("同步"), Tooltip("同步执行意味着Exit连接的多条支路将依次进行")] + Sync + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/NodeEnum.cs.meta b/Assets/10.StoryEditor/RunTime/NodeEnum.cs.meta new file mode 100644 index 0000000..99af5b1 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/NodeEnum.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d4772e98c371834284ea14f4b641510 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player.meta b/Assets/10.StoryEditor/RunTime/Player.meta new file mode 100644 index 0000000..54952e7 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: edf21b4cd79b9cf459596d41bb2a1da2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Graph.meta b/Assets/10.StoryEditor/RunTime/Player/Graph.meta new file mode 100644 index 0000000..24e0687 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Graph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a786bcde0f40a044b8e37ee058a125d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs new file mode 100644 index 0000000..da9a64d --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Threading; +using Cysharp.Threading.Tasks; +using UnityEngine; + +namespace Stary.Evo.StoryEditor +{ + public class GraphPlayer + { + /// + /// 剧本名称 + /// + public string Name; + + /// + /// 节点 + /// + public List Nodes = new(); + + /// + /// 起始节点 + /// + public BeginNodePlayer BeginNode; + /// + /// 结束节点 + /// + public EndNodePlayer EndNode; + + /// + /// 当前进行中的节点 + /// + public List CurrentNodes = new(); + + /// + /// 播放完成 + /// + public bool Finished; + + /// + /// CTS + /// + 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(); + } + + /// + /// 执行剧本 + /// + public async UniTask Execute() + { + // 初始化结束标志 + Finished = false; + // 执行剧本 + _ = BeginNode.Execute(); + // 等待剧本完成 + await UniTask.WaitUntil(() => Finished); + } + + /// + /// 停止剧本 + /// + public void Stop() + { + // 取消并重置CancelToken + Cts.Cancel(); + Cts = new(); + // 标记剧本结束 + Finished = true; + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs.meta new file mode 100644 index 0000000..96afb80 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Graph/GraphPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce92dbf7b238c4442b75ea500c804107 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node.meta b/Assets/10.StoryEditor/RunTime/Player/Node.meta new file mode 100644 index 0000000..8c26d26 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: adab23e0caf805e45b47a9a0c88ec076 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs new file mode 100644 index 0000000..42a5f77 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs @@ -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 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(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs.meta new file mode 100644 index 0000000..01ce4e4 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/BeginNodePlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5fb5ecf3bf3ef64996bd3a7905884f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs new file mode 100644 index 0000000..ba19a24 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs @@ -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 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(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs.meta new file mode 100644 index 0000000..b8d8ee3 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/EndNodePlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6894cd8522d600447966dc11b2a13905 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs new file mode 100644 index 0000000..5dc3dad --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs @@ -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 Pre = new(); + public List Next = new(); + + /// + /// 后续连接执行类型 + /// + 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; + } + + /// + /// 开始执行 + /// + public override async UniTask Execute() + { + await base.Execute(); + await MoveNext(); + } + + /// + /// 向下继续执行 + /// + 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(); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs.meta new file mode 100644 index 0000000..2e9d28e --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/FlowNodePlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58805d324b439d242a445138bc3a33a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs new file mode 100644 index 0000000..b7bf028 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs @@ -0,0 +1,65 @@ +using Cysharp.Threading.Tasks; + +namespace Stary.Evo.StoryEditor +{ + public class NodePlayer + { + /// + /// 节点名称 + /// + protected string Name; + /// + /// 图表 + /// + protected GraphPlayer Graph; + /// + /// 数据 + /// + protected NodeData Data; + + /// + /// 初始化标志 + /// + protected bool Initialized; + + public NodePlayer(GraphPlayer graph, NodeData data) + { + Graph = graph; + Name = data.name; + Data = data; + } + + /// + /// 连接节点 + /// + public virtual bool Connect() + { + if(Initialized) + return false; + Initialized = true; + return true; + } + + /// + /// 初始化 + /// + public virtual void Init() => Graph.CurrentNodes.Add(this); + + /// + /// 开始执行 + /// + public virtual UniTask Execute() + { + Graph.CurrentNodes.Remove(this); + return UniTask.CompletedTask; + } + /// + /// 向下继续执行 + /// + public virtual UniTask MoveNext() => UniTask.CompletedTask; + /// + /// 停止执行 + /// + public virtual UniTask Stop() => UniTask.CompletedTask; + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs.meta new file mode 100644 index 0000000..7278ab0 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/NodePlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b76627cea0be464c864e744ae0f0b45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs b/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs new file mode 100644 index 0000000..31c2ec7 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs @@ -0,0 +1,32 @@ +using Cysharp.Threading.Tasks; + +namespace Stary.Evo.StoryEditor +{ + public class ScriptParagraphNodePlayer : FlowNodePlayer + { + public new ScriptParagraphNodeData Data; + + /// + /// 字幕路径 + /// + public ResourcePathData CaptionPath; + /// + /// 语音路径 + /// + 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(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs.meta new file mode 100644 index 0000000..66876a1 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/Node/ScriptParagraphNodePlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b263f1eba1f46094cab95e1c34ff2605 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs b/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs new file mode 100644 index 0000000..b7e45be --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs @@ -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 +{ + /// + /// 剧本播放器 + /// + public static class ScriptPlayer + { + /// + /// 资源加载工具 + /// + private static IResource _loader; + + /// + /// 动画单次时长 + /// + private const float AnimationTime = 0.2f; + + /// + /// 字幕组件 + /// + private static SpriteRenderer _caption; + /// + /// 音频组件 + /// + private static AudioSource _audio; + + /// + /// 当前执行的剧本 + /// + private static GraphPlayer _graph; + + /// + /// 初始化剧本播放器 + /// + /// 资源加载工具 + /// 音频组件 + /// 字幕组件 + public static void Init(IResource loader, AudioSource audio = null, SpriteRenderer caption = null) + { + _loader = loader; + _caption = caption; + _audio = audio; + } + + /// + /// 释放资源 + /// + public static void Release() + { + _loader = null; + _caption = null; + _audio = null; + Resources.UnloadUnusedAssets(); + } + + /// + /// 播放剧本 + /// + /// 包体ID + /// 剧本名称(不用加_txt后缀) + 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(path); + var scriptData = JsonConvert.DeserializeObject(json.text, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); + if (scriptData == null) + { + Debug.LogError($"剧本加载失败:[{path.packageID}]{path.path}"); + return; + } + + // 解析并执行剧本 + _graph = new(scriptData, _loader); + await _graph.Execute(); + } + + /// + /// 播放剧本段落 + /// + /// 段落数据 + /// CT + public static async UniTask PlayScriptPara(ScriptParagraphNodePlayer para, CancellationToken ct) + { + // 设置变量 + if (_caption) + _caption.sprite = await _loader.Load(para.CaptionPath); + if (_audio) + _audio.clip = await _loader.Load(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); + } + + /// + /// 停止剧本 + /// + 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; + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs.meta b/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs.meta new file mode 100644 index 0000000..d12106b --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Player/ScriptPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9bd336685230b746bc67a3bb9dc2102 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Resource.meta b/Assets/10.StoryEditor/RunTime/Resource.meta new file mode 100644 index 0000000..6ae0229 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Resource.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6e59434304b735b44b6f025c51615cfa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Resource/IResource.cs b/Assets/10.StoryEditor/RunTime/Resource/IResource.cs new file mode 100644 index 0000000..772a864 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Resource/IResource.cs @@ -0,0 +1,24 @@ +using Cysharp.Threading.Tasks; +using UnityEngine; + +namespace Stary.Evo.StoryEditor +{ + public interface IResource + { + /// + /// 加载资源 + /// Json => 资源 + /// + /// 资源路径 + /// 资源类型 + UniTask Load(ResourcePathData pathData) where T : Object; + + /// + /// 保存资源 + /// 资源 => Json + /// + /// 资源 + /// 包体ID + UniTask Save(T asset, string packageID = null) where T : Object; + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Resource/IResource.cs.meta b/Assets/10.StoryEditor/RunTime/Resource/IResource.cs.meta new file mode 100644 index 0000000..128077c --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Resource/IResource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68c399cab4c86fc4b961516a2802b94d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs b/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs new file mode 100644 index 0000000..8dad6fa --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs @@ -0,0 +1,38 @@ +using System; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 资源路径数据 + /// + [Serializable] + public struct ResourcePathData + { + /// + /// 包体ID + /// + public string packageID; + + /// + /// 资源路径 + /// + 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); + } + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs.meta b/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs.meta new file mode 100644 index 0000000..1f06c4b --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/Resource/ResourcePathData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fd990a65a7964740b2d98d2557afbb8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor.meta b/Assets/10.StoryEditor/RunTime/VisualEditor.meta new file mode 100644 index 0000000..5c2b563 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 48bedbe80b198ef4d8811fac3b41d204 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta new file mode 100644 index 0000000..068cc39 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4f25ed558908de741b709bb8f3df2427 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs new file mode 100644 index 0000000..83192c7 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs @@ -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 + { + /// + /// 起始节点 + /// + [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 导出设置 + + /// + /// 资源加载方式 + /// + [BoxGroup("Export", centerLabel:true)] + [LabelText("资源加载方式"), ValueDropdown(nameof(_iResourceTypes)),SerializeField] + private string loaderType; + +#if UNITY_EDITOR + /// + /// 获取继承 IResource 的所有类 + /// + private HashSet _iResourceTypes = AssemblyUtilities.GetTypes(AssemblyCategory.Scripts) + .Where(t => t.IsClass && typeof(IResource).IsAssignableFrom(t)).Select(t => t.ToString()).ToHashSet(); +#else + private HashSet _iResourceTypes = new(); +#endif + + 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 + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs.meta new file mode 100644 index 0000000..48e52bc --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96f13bed6a3bb274abdf828cac308231 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta new file mode 100644 index 0000000..a364fbf --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90e4f51c40e7d9340a88df558072b353 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs new file mode 100644 index 0000000..612c01e --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs @@ -0,0 +1,20 @@ +using System; +using Cysharp.Threading.Tasks; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 起始节点 + /// + [Serializable, CreateNodeMenu("")] + public class BeginNode : NodeBase + { + [Output] + public Exit exit; + + /// + /// 导出 + /// + public new async UniTask Export() => new(await base.Export()); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs.meta new file mode 100644 index 0000000..d959cce --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/BeginNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7bdd4ad134525043830b54f6a2391f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs new file mode 100644 index 0000000..5fa744a --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs @@ -0,0 +1,20 @@ +using System; +using Cysharp.Threading.Tasks; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 结束节点 + /// + [Serializable, CreateNodeMenu("")] + public class EndNode : NodeBase + { + [Input] + public Enter enter; + + /// + /// 导出 + /// + public new async UniTask Export() => new(await base.Export()); + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs.meta new file mode 100644 index 0000000..d4e0b98 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/EndNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e8d1b2bbe7b8154c8822cf7c0b6e064 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs new file mode 100644 index 0000000..b9a8ca1 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs @@ -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; + + /// + /// Exit端口连接的节点的执行类型 + /// + [BoxGroup("Config", order:5, showLabel:false)] + [LabelText("后续执行")] + public NodeExecuteType exitNodeExecuteType = NodeExecuteType.Async; + + /// + /// 创建新的流程节点 + /// + /// 所在的块 + /// 节点名称 + /// 节点位置 + /// 节点前部连接 + /// 节点后部连接 + public static T Create(NodeGraph graph, string name = null, Vector2 position = default, + List prePorts = null, List nextPorts = null) where T:FlowNode + { + // 创建节点 + var node = NodeBase.Create(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 工具方法 + + /// + /// 将自身的入口连接传递给指定端口 + /// + /// 指定端口 + /// 传递后删除自身连接 + public void GiveEnterPortToOtherPort(NodePort otherPort, bool deleteSelf) + => GiveConnectionToOtherPort(GetInputPort("enter"), otherPort, deleteSelf); + + /// + /// 将自身的出口连接传递给指定端口 + /// + /// 指定端口 + /// 传递后删除自身连接 + public void GiveExitPortToOtherPort(NodePort otherPort, bool deleteSelf = false) + => GiveConnectionToOtherPort(GetOutputPort("exit"), otherPort, deleteSelf); + + #endregion + + #region 节点操作 + + /// + /// 向前插入节点 + /// + [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)); + } + + /// + /// 向后插入节点 + /// + [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 + + /// + /// 导出 + /// + public new async UniTask Export() + { + FlowNodeData node = new(await base.Export()); + node.executeType = exitNodeExecuteType; + return node; + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs.meta new file mode 100644 index 0000000..17935e2 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/FlowNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e85b8c018e8b68b418fff4c389089665 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs new file mode 100644 index 0000000..ce3062f --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs @@ -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 { } + + /// + /// 默认节点位置间隔(x轴) + /// + public const float DefaultNodeXInterval = 250; + /// + /// 默认节点位置间隔(y轴) + /// + public const float DefaultNodeYInterval = 0; + + /// + /// 创建新的节点 + /// + /// 所在的块 + /// 节点名称 + /// 节点位置 + public static T Create(NodeGraph graph, string name = null, Vector2 position = default) where T : NodeBase + { + // 创建节点 + var node = graph.AddNode(); + + 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; + } + + /// + /// 移除自身 + /// + public void DestroySelf() + { + // 清理连接 + ClearConnections(); + // 移除与Graph之间的联系 + if (graph) + { + graph.nodes.Remove(this); +#if UNITY_EDITOR + UnityEditor.AssetDatabase.RemoveObjectFromAsset(this); +#endif + } + // 销毁自身 + DestroyImmediate(this); + } + + public UniTask Export() + { + NodeData nodeData = new(); + nodeData.name = name; + return UniTask.FromResult(nodeData); + } + + #region 工具方法 + + /// + /// 将端口A的连接传递给端口B + /// + /// 端口A + /// 端口B + /// 移除端口A的连接 + 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); + }); + } + + /// + /// 对所有之后的节点执行动作 + /// + /// 指定动作 + public void DoActionToAllNodesAfter(Action 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 + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs.meta new file mode 100644 index 0000000..5031b37 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/NodeBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 128757ef2e3a23c48b67ade15852c36c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs new file mode 100644 index 0000000..81be9de --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs @@ -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 +{ + /// + /// 剧本自然段 + /// + [Serializable, CreateNodeMenu("创建剧本自然段", order = 5)] + public class ScriptParagraphNode : FlowNode + { + /// + /// 名称 + /// (仅供辨识,不起实际作用) + /// + [BoxGroup("Info", order:10, showLabel:false)] + [LabelText("名称"),LabelWidth(30)] + public string displayName; + + /// + /// 字幕 + /// + [BoxGroup("Info")] + [LabelText("字幕"),LabelWidth(30)] + public Sprite caption; + /// + /// 语音 + /// + [BoxGroup("Info")] + [LabelText("音频"),LabelWidth(30)] + public AudioClip audio; + + /// + /// 创建新的剧本段落节点 + /// + /// 所在的块 + /// 节点名称 + /// 节点位置 + /// 节点前部连接 + /// 节点后部连接 + /// 字幕 + /// 音频 + public static ScriptParagraphNode Create(NodeGraph graph, string name = null, Vector2 position = default, List prePorts = null, List nextPorts = null, Sprite caption = null, AudioClip audio = null) + { + // 创建节点 + var node = Create(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(); + } + + /// + /// 更新名称 + /// + private void UpdateName() + { + if (string.IsNullOrEmpty(displayName)) + { + displayName = graph.name + "_para"; + } + + name = displayName; + } + + public new async UniTask 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; + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs.meta new file mode 100644 index 0000000..a1c2f31 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/ScriptParagraphNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e0dc65de1614514883efb2a6848e5a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs new file mode 100644 index 0000000..d60fd25 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Sirenix.OdinInspector; +using UnityEngine; +using XNode; + +namespace Stary.Evo.StoryEditor +{ + /// + /// 空节点 + /// (可选择应用哪种节点) + /// + [Serializable, CreateNodeMenu("创建空节点", order = 0)] + public class SelectionNode : FlowNode + { + /// + /// 创建新的空节点 + /// + /// 所在的块 + /// 节点名称 + /// 节点位置 + /// 节点前部连接 + /// 节点后部连接 + public static SelectionNode Create(NodeGraph graph, string name = null, Vector2 position = default, + List prePorts = null, List nextPorts = null) + { + var node = Create(graph, name, position, prePorts, nextPorts); + return node; + } + + /// + /// 创建剧本段落 + /// + [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(); + } + } +} \ No newline at end of file diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs.meta b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs.meta new file mode 100644 index 0000000..a1029f8 --- /dev/null +++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Node/SelectionNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79ef9a28ecfd1384a96569da56b2022b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Sample~/Sample.unity b/Assets/10.StoryEditor/Sample~/Sample.unity new file mode 100644 index 0000000..b970ab7 --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Sample.unity @@ -0,0 +1,693 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &354453078 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 354453079} + - component: {fileID: 354453080} + m_Layer: 0 + m_Name: Audio + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &354453079 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 354453078} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1732823841} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!82 &354453080 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 354453078} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!1 &658829530 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 658829532} + - component: {fileID: 658829531} + - component: {fileID: 658829533} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &658829531 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 658829530} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &658829532 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 658829530} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &658829533 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 658829530} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 3 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_RenderingLayers: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_ShadowRenderingLayers: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 0 +--- !u!1 &948603383 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 948603387} + - component: {fileID: 948603386} + - component: {fileID: 948603385} + - component: {fileID: 948603384} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &948603384 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 948603383} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 +--- !u!81 &948603385 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 948603383} + m_Enabled: 1 +--- !u!20 &948603386 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 948603383} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &948603387 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 948603383} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &962199775 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 962199776} + - component: {fileID: 962199777} + m_Layer: 0 + m_Name: HybridClREntrance + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &962199776 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 962199775} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.6535072, y: -0.08580719, z: 1.3690248} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &962199777 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 962199775} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3397e2d382924ffea94a9ecbd16ab745, type: 3} + m_Name: + m_EditorClassIdentifier: + moduleName: com.sxkjg.main + m_moduleRootTrans: {fileID: 0} +--- !u!1 &1212764680 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1212764681} + - component: {fileID: 1212764682} + m_Layer: 0 + m_Name: Caption + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1212764681 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1212764680} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1732823841} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!212 &1212764682 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1212764680} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 0 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!1 &1732823839 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1732823841} + - component: {fileID: 1732823840} + m_Layer: 0 + m_Name: ScriptPlayer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1732823840 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1732823839} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 543f5e9055e16f045806981c5725e910, type: 3} + m_Name: + m_EditorClassIdentifier: + packageID: com.zjkjg.daopian + scriptName: graph.sg +--- !u!4 &1732823841 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1732823839} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1212764681} + - {fileID: 354453079} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 658829532} + - {fileID: 948603387} + - {fileID: 962199776} + - {fileID: 1732823841} diff --git a/Assets/10.StoryEditor/Sample~/Sample.unity.meta b/Assets/10.StoryEditor/Sample~/Sample.unity.meta new file mode 100644 index 0000000..9c3e2ba --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Sample.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9dbefcb0c8b734346a6bf5cab23d09db +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Sample~/Script.meta b/Assets/10.StoryEditor/Sample~/Script.meta new file mode 100644 index 0000000..9e043d5 --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Script.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7861aace72bef144da5e5349d49d214d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs b/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs new file mode 100644 index 0000000..0945262 --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs @@ -0,0 +1,20 @@ +using Cysharp.Threading.Tasks; +using rokid.armaz.module; +using Stary.Evo.StoryEditor; +using UnityEngine; + +public class ResourceLoader : IResource +{ + public UniTask Load(ResourcePathData pathData) where T : Object + { + return AMP.ResourceLoader.LoadAssetAsync(pathData.packageID, pathData.path); + } + + public UniTask Save(T asset, string packageID) where T : Object + { + ResourcePathData pathData = new(); + pathData.packageID = packageID; + pathData.AddPath(asset.name); + return UniTask.FromResult(pathData); + } +} diff --git a/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs.meta b/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs.meta new file mode 100644 index 0000000..19de180 --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Script/ResourceLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43ce2e09f0a37f64e8dac4036d588160 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs b/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs new file mode 100644 index 0000000..470d420 --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs @@ -0,0 +1,166 @@ +using Array = System.Array; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Stary.Evo.StoryEditor; +using Sirenix.OdinInspector; +using UnityEngine; + +public class TestScriptPlayer : MonoBehaviour +{ + [LabelText("指定包体ID"), SerializeField, ValueDropdown(nameof(GetPackages))] + private string packageID; + [LabelText("指定剧本名称"), SerializeField, ValueDropdown(nameof(GetScripts))] + private string scriptName; + + private void Start() + { + var caption = GetComponentInChildren(); + caption.color = Color.clear; + var audioClip = GetComponentInChildren(); + audioClip.volume = 0; + + ScriptPlayer.Init(new ResourceLoader(), audioClip, caption); + } + + private void OnDestroy() + { + ScriptPlayer.Release(); + } + + [Button] + public void Play() + { + _ = ScriptPlayer.Play(packageID, scriptName); + } + + [Button] + public void Stop() + { + _ = ScriptPlayer.Stop(); + } + + #region 非主要内容,忽略即可 + + private Dictionary _paths = new(); + + /// + /// 获取所有Module Package + /// + private string[] GetPackages() + { + // 查找Modules目录 + List modules = new(); + GetDirectoryPaths(Application.dataPath, modules, "Modules"); + if (modules.Count == 0) + { + Debug.LogError("未找到任何Modules目录"); + return Array.Empty(); + } + + // 查找package + List packages = new(); + GetDirectoryPaths(modules[0], packages, "com."); + if (packages.Count == 0) + { + return Array.Empty(); + } + + // 记录Package地址并返回选项 + _paths.Clear(); + packages.ForEach(path => _paths.Add(Path.GetFileName(path), path)); + return _paths.Keys.ToArray(); + } + + /// + /// 获取Package中所有Script + /// + private string[] GetScripts() + { + // 未选Package + if (string.IsNullOrEmpty(packageID) || !_paths.ContainsKey(packageID)) + return Array.Empty(); + + List scripts = new(); + GetFilePaths(_paths[packageID], scripts, tail: ".sg.json"); + return scripts.Count == 0 ? Array.Empty() : scripts.Select(path => Path.GetFileName(path).Replace(".json", "")).ToArray(); + } + + /// + /// 获取符合条件的目录 + /// + /// 查找起始目录 + /// 查找结果 + /// 筛选条件(title) + /// 筛选条件(tail) + private static void GetDirectoryPaths(string root, List result, string title = null, string tail = null) + { + foreach (var dir in Directory.GetDirectories(root)) + { + // ReSharper disable once ReplaceWithSingleAssignment.True + var check = true; + + // 匹配文件头 + if (!string.IsNullOrEmpty(title) && !Path.GetFileName(dir).StartsWith(title)) + { + check = false; + } + // 匹配文件尾 + if (!string.IsNullOrEmpty(tail) && !Path.GetFileName(dir).EndsWith(tail)) + { + check = false; + } + + if(check) + result.Add(dir.Replace('\\', '/')); + + // 继续往下找 + GetDirectoryPaths(dir, result, title); + } + } + + /// + /// 获取符合条件的文件路径 + /// + /// 查找起始目录 + /// 查找结果 + /// 筛选条件(title) + /// 筛选条件(tail) + private static void GetFilePaths(string root, List result, string title = null, string tail = null) + { + foreach (var file in Directory.GetFiles(root)) + { + // ReSharper disable once ReplaceWithSingleAssignment.True + var check = true; + + // 匹配文件头 + if (!string.IsNullOrEmpty(title)) + { + if(!Path.GetFileName(file).StartsWith(title)) + check = false; + } + + // 匹配文件尾 + if (!string.IsNullOrEmpty(tail)) + { + if (!Path.GetFileName(file).EndsWith(tail)) + { + check = false; + } + } + + if (check) + { + result.Add(file.Replace('\\', '/')); + } + } + + // 继续往下找 + foreach (var dir in Directory.GetDirectories(root)) + { + GetFilePaths(dir, result, title, tail); + } + } + + #endregion +} diff --git a/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs.meta b/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs.meta new file mode 100644 index 0000000..aab089d --- /dev/null +++ b/Assets/10.StoryEditor/Sample~/Script/TestScriptPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 543f5e9055e16f045806981c5725e910 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/10.StoryEditor/package.json b/Assets/10.StoryEditor/package.json new file mode 100644 index 0000000..1b23e63 --- /dev/null +++ b/Assets/10.StoryEditor/package.json @@ -0,0 +1,24 @@ +{ + "name": "com.staryevo.storyeditor", + "version": "1.0.0", + "displayName": "10.StoryEditor", + "description": "可视化剧本编辑器\n1.通过可视化图表编辑剧本内容\n2.将剧本导出为json\n3.解析剧本并执行", + "unity": "2021.3", + "unityRelease": "44f1c1", + "keywords": [ + "unity", + "story", + "visual", + "editor", + "graph" + ], + "author": { + "name": "staryEvo", + "url": "https://www.unity3d.com" + }, + "dependencies": { + "com.github.siccity.xnode": "1.8.0", + "com.cysharp.unitask": "2.5.10", + "com.unity.nuget.newtonsoft-json": "3.2.1" + } +} diff --git a/Assets/10.StoryEditor/package.json.meta b/Assets/10.StoryEditor/package.json.meta new file mode 100644 index 0000000..6c9a9d9 --- /dev/null +++ b/Assets/10.StoryEditor/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e3e21fe172f8611499ecc724ca256dca +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: