diff --git a/Assets/10.StoryEditor/CHANGELOG.md b/Assets/10.StoryEditor/CHANGELOG.md
index b452d97..0cb3913 100644
--- a/Assets/10.StoryEditor/CHANGELOG.md
+++ b/Assets/10.StoryEditor/CHANGELOG.md
@@ -1,6 +1,13 @@
# Changelog
此包的所有更新日志会被记录在此文件中
+## [1.1.0] - 2026-01-08
+### Added
+- 新增剧本设置窗口,在窗口中打开和导出剧本
+### Fixed
+- 修改Graph创建时的默认位置,防止热更分包导致脚本丢失报错
+- 修改Graph导出时对加载器的加载逻辑,解决跨包无法加载的问题
+
## [1.0.7] - 2026-01-08
### Fixed
- 处理IResource多程序集共存产生的无法选择加载器问题
diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs
index 1c22093..dbd7738 100644
--- a/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs
+++ b/Assets/10.StoryEditor/Editor/Graph/GraphCreateWindow.cs
@@ -28,7 +28,12 @@ namespace Stary.Evo.StoryEditor.Editor
private void OnEnable()
{
minSize = maxSize = new Vector2(300, 200);
- _options = GetPackages();
+
+ // 记录Package地址并返回选项
+ var packages = GraphEditorTools.GetPackages();
+ _paths.Clear();
+ packages.ForEach(path => _paths.Add(Path.GetFileName(path), path));
+ _options = _paths.Keys.ToArray();
}
private void OnGUI()
@@ -39,9 +44,7 @@ namespace Stary.Evo.StoryEditor.Editor
EditorGUILayout.BeginVertical();
// Package选项
- EditorGUILayout.LabelField( "请选择剧本所在的Package:" , EditorStyles.boldLabel ) ;
- var newIndex = EditorGUILayout.Popup( _selectedIndex , _options ) ;
- _selectedIndex = newIndex;
+ _selectedIndex = GraphEditorTools.Popup("请选择剧本所在的Package:", _selectedIndex, _options);
GUILayout.Space(15);
@@ -69,7 +72,7 @@ namespace Stary.Evo.StoryEditor.Editor
{
try
{
- CreateScriptGraph(_options[newIndex]);
+ CreateScriptGraph(_options[_selectedIndex]);
}
catch (Exception e)
{
@@ -82,68 +85,6 @@ namespace Stary.Evo.StoryEditor.Editor
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);
- }
- }
-
///
/// 创建剧本
///
@@ -161,14 +102,15 @@ namespace Stary.Evo.StoryEditor.Editor
if (_paths.TryGetValue(packageID, out var path))
{
// 包体目录排空
- var graphDir = Path.Combine(path, "Main","Res","Graphs");
+ var graphDir = Path.Combine(Application.dataPath, "StoryEditor", "Graphs",
+ $"{(string.IsNullOrEmpty(packageID) ? "default" : packageID)}");
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 graphFilePath = Path.Combine(graphDir.Replace(Application.dataPath,"Assets"), $"{_scriptName}.asset");
var existAsset = AssetDatabase.LoadAssetAtPath(graphFilePath);
if (existAsset)
{
@@ -178,6 +120,8 @@ namespace Stary.Evo.StoryEditor.Editor
}
// 创建剧本图表
+ var graphPath = Path.Combine(path, "Main","Res","Graphs", $"{_scriptName}.sg.json").Replace(Application.dataPath, "").Replace('\\', '/');
+ graphPath = graphPath[1..];
var graph = CreateInstance();
graph.packageID = packageID;
graph.graphPath = graphPath;
diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs b/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs
new file mode 100644
index 0000000..d5f85ff
--- /dev/null
+++ b/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace Stary.Evo.StoryEditor.Editor
+{
+ public static class GraphEditorTools
+ {
+ public static int Popup(string content, int currentIndex, string[] options)
+ {
+ EditorGUILayout.LabelField( "请选择剧本所在的Package:" , EditorStyles.boldLabel ) ;
+ return EditorGUILayout.Popup( currentIndex , options ) ;
+ }
+
+ ///
+ /// 获取所有Module Package
+ ///
+ public static List GetPackages()
+ {
+ // 查找Modules目录
+ List modules = new();
+ GetDirectoryPaths(Application.dataPath, modules, "Modules");
+ if (modules.Count == 0)
+ {
+ Debug.LogError("未找到任何Modules目录");
+ return new();
+ }
+
+ // 查找package
+ List packages = new();
+ GetDirectoryPaths(modules[0], packages, "com.");
+ if (packages.Count == 0)
+ {
+ Debug.LogError("未找到任何Package");
+ return new();
+ }
+
+ return packages;
+ }
+
+ ///
+ /// 获取符合条件的目录
+ ///
+ /// 查找起始目录
+ /// 查找结果
+ /// 筛选条件(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);
+ }
+ }
+
+ public static IEnumerable GetAllAssemblyNames() =>
+ AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
+
+ ///
+ /// 获取继承 IResource 的所有类
+ ///
+ public static HashSet IResourceTypes(string assembly)
+ {
+ if (string.IsNullOrEmpty(assembly))
+ return new();
+ var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assembly);
+ return asm == null ? new() : asm.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(i => i.Name == nameof(IResource))).Select(t => t.Name).ToHashSet();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs.meta b/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs.meta
new file mode 100644
index 0000000..7442c60
--- /dev/null
+++ b/Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7264316efc98e9e47b55b6dca12b08d6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs b/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs
new file mode 100644
index 0000000..e7b9ff0
--- /dev/null
+++ b/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using XNodeEditor;
+
+namespace Stary.Evo.StoryEditor.Editor
+{
+ public class GraphExportWindow : EditorWindow
+ {
+ private string[] _options = Array.Empty();
+ private Dictionary _paths = new();
+
+ private int _selectedIndex;
+ private int _selectedStoryIndex;
+ private ScriptGraph _selectedStory;
+ private int _selectedAssemblyIndex;
+ private int _selectedLoaderIndex;
+
+ [MenuItem("Evo/剧本编辑器/打开剧本")]
+ private static void Open()
+ {
+ // 打开一个浮动窗口
+ GetWindow( "剧本配置" ) ;
+ }
+
+ private void OnEnable()
+ {
+ minSize = maxSize = new Vector2(300, 400);
+
+ // 记录Package地址并返回选项
+ var packages = GraphEditorTools.GetPackages();
+ _paths.Clear();
+ packages.ForEach(path => _paths.Add(Path.GetFileName(path), path));
+ _options = _paths.Keys.ToArray();
+ }
+
+ private void OnGUI()
+ {
+ GUILayout.Space(15);
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(10);
+ EditorGUILayout.BeginVertical();
+
+ // Package选项
+ _selectedIndex = GraphEditorTools.Popup("请选择剧本所在的Package:", _selectedIndex, _options);
+
+ GUILayout.Space(15);
+
+ // 显示已有剧本
+ try
+ {
+ string[] storys;
+ var packageID = _options[_selectedIndex];
+ var graphDir = Path.Combine(Application.dataPath, "StoryEditor", "Graphs",
+ $"{(string.IsNullOrEmpty(packageID) ? "default" : packageID)}");
+ if (!Directory.Exists(graphDir))
+ {
+ storys = Array.Empty();
+ }
+ else
+ {
+ // 选择剧本
+ var guids = AssetDatabase.FindAssets($"t:{nameof(ScriptGraph)}");
+ var allAssets = guids.Select(AssetDatabase.GUIDToAssetPath)
+ .Select(AssetDatabase.LoadAssetAtPath)
+ .ToArray();
+ storys = allAssets.Select(asset => asset.name).ToArray();
+ _selectedStoryIndex = GraphEditorTools.Popup("请选择剧本:", _selectedStoryIndex, storys);
+ _selectedStory = allAssets[_selectedStoryIndex];
+ GUILayout.Space(15);
+
+ // 打开剧本按钮
+ if (GUILayout.Button("打开剧本"))
+ {
+ var win = GetWindow (false, _selectedStory.name);
+ win.graph = _selectedStory;
+ }
+ GUILayout.Space(15);
+
+ // 选择程序集
+ var assemblyNames = GraphEditorTools.GetAllAssemblyNames().ToArray();
+ _selectedAssemblyIndex = GraphEditorTools.Popup("请选择加载器所在的程序集:", _selectedAssemblyIndex, assemblyNames);
+ _selectedStory.assembly = assemblyNames.Length > 0 ? assemblyNames[_selectedAssemblyIndex] : null;
+ GUILayout.Space(15);
+
+ // 选择加载器
+ var loaders = GraphEditorTools.IResourceTypes(_selectedStory.assembly).ToArray();
+ _selectedLoaderIndex = GraphEditorTools.Popup("请选择加载器:", _selectedLoaderIndex, loaders);
+ _selectedStory.loaderType = loaders.Length > 0 ? loaders[_selectedLoaderIndex] : null;
+ GUILayout.Space(15);
+
+ // 导出剧本按钮
+ if (GUILayout.Button("导出剧本"))
+ {
+ _selectedStory.Export();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+
+ EditorGUILayout.EndVertical();
+ GUILayout.Space(10);
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs.meta b/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs.meta
new file mode 100644
index 0000000..7cd742d
--- /dev/null
+++ b/Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ba732ba5b1117f549b7d0f8cb6f88449
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
index 820e74b..fee0ee6 100644
--- a/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
+++ b/Assets/10.StoryEditor/RunTime/VisualEditor/Graph/ScriptGraph.cs
@@ -124,38 +124,17 @@ namespace Stary.Evo.StoryEditor
#endregion
#region 导出设置
-
-#if UNITY_EDITOR
-
- [BoxGroup("Export")]
- [SerializeField, ValueDropdown(nameof(GetAllAssemblyNames))]
- private string assembly;
- private IEnumerable GetAllAssemblyNames() =>
- AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
-
- ///
- /// 获取继承 IResource 的所有类
- ///
- private HashSet IResourceTypes
- {
- get
- {
- if (string.IsNullOrEmpty(assembly))
- return new();
- var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assembly);
- return asm == null ? new() : asm.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(i => i.Name == nameof(IResource))).Select(t => t.Name).ToHashSet();
- }
- }
-#else
- private HashSet _iResourceTypes = new();
-#endif
+
+ [BoxGroup("Export", centerLabel:true)]
+ [LabelText("程序集")]
+ public string assembly;
///
/// 资源加载方式
///
- [BoxGroup("Export", centerLabel:true)]
- [LabelText("资源加载方式"), ValueDropdown(nameof(IResourceTypes)),SerializeField]
- private string loaderType;
+ [BoxGroup("Export")]
+ [LabelText("资源加载方式")]
+ public string loaderType;
private IResource _loader;
///
@@ -165,10 +144,14 @@ namespace Stary.Evo.StoryEditor
{
get
{
- if (_loader == null)
+ if (_loader == null && !string.IsNullOrEmpty(loaderType))
{
- var type = Type.GetType(loaderType);
- _loader = type == null ? null : (IResource)Activator.CreateInstance(type);
+ var asm = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(loaderType)).FirstOrDefault(t => t != null) ?? AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(loaderType, false, false)).FirstOrDefault(t => t != null);
+
+ if (asm != null)
+ {
+ _loader = (IResource)Activator.CreateInstance(asm);
+ }
}
return _loader;
@@ -269,7 +252,7 @@ namespace Stary.Evo.StoryEditor
// 将转录完成的图表数据序列化为json
var json = JsonConvert.SerializeObject(graph, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
// 文件目录排空
- var graphFilePath = Path.Combine(Application.dataPath, graphPath.Replace(".asset", ".sg.json"));
+ var graphFilePath = Path.Combine(Application.dataPath, graphPath);
if (!Directory.Exists(Path.GetDirectoryName(graphFilePath)))
Directory.CreateDirectory(Path.GetDirectoryName(graphFilePath) ?? string.Empty);
// 写入json
diff --git a/Assets/10.StoryEditor/package.json b/Assets/10.StoryEditor/package.json
index 7c1c6c3..d336f9d 100644
--- a/Assets/10.StoryEditor/package.json
+++ b/Assets/10.StoryEditor/package.json
@@ -1,6 +1,6 @@
{
"name": "com.staryevo.storyeditor",
- "version": "1.0.7",
+ "version": "1.1.0",
"displayName": "10.StoryEditor",
"description": "可视化剧本编辑器\n1.通过可视化图表编辑剧本内容\n2.将剧本导出为json\n3.解析剧本并执行",
"unity": "2021.3",