【m】10. StoryEditor [1.1.0] - 2026-01-08
### Added - 新增剧本设置窗口,在窗口中打开和导出剧本 ### Fixed - 修改Graph创建时的默认位置,防止热更分包导致脚本丢失报错 - 修改Graph导出时对加载器的加载逻辑,解决跨包无法加载的问题
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
# Changelog
|
||||
此包的所有更新日志会被记录在此文件中
|
||||
|
||||
## [1.1.0] - 2026-01-08
|
||||
### Added
|
||||
- 新增剧本设置窗口,在窗口中打开和导出剧本
|
||||
### Fixed
|
||||
- 修改Graph创建时的默认位置,防止热更分包导致脚本丢失报错
|
||||
- 修改Graph导出时对加载器的加载逻辑,解决跨包无法加载的问题
|
||||
|
||||
## [1.0.7] - 2026-01-08
|
||||
### Fixed
|
||||
- 处理IResource多程序集共存产生的无法选择加载器问题
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有Module Package
|
||||
/// </summary>
|
||||
private string[] GetPackages()
|
||||
{
|
||||
// 查找Modules目录
|
||||
List<string> modules = new();
|
||||
GetDirectoryPaths(Application.dataPath, modules, "Modules");
|
||||
if (modules.Count == 0)
|
||||
{
|
||||
Debug.LogError("未找到任何Modules目录");
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// 查找package
|
||||
List<string> packages = new();
|
||||
GetDirectoryPaths(modules[0], packages, "com.");
|
||||
if (packages.Count == 0)
|
||||
{
|
||||
Debug.LogError("未找到任何Package");
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// 记录Package地址并返回选项
|
||||
_paths.Clear();
|
||||
packages.ForEach(path => _paths.Add(Path.GetFileName(path), path));
|
||||
return _paths.Keys.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取符合条件的目录
|
||||
/// </summary>
|
||||
/// <param name="root">查找起始目录</param>
|
||||
/// <param name="result">查找结果</param>
|
||||
/// <param name="title">筛选条件(title)</param>
|
||||
/// <param name="tail">筛选条件(tail)</param>
|
||||
private static void GetDirectoryPaths(string root, List<string> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建剧本
|
||||
/// </summary>
|
||||
@@ -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<ScriptGraph>(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<ScriptGraph>();
|
||||
graph.packageID = packageID;
|
||||
graph.graphPath = graphPath;
|
||||
|
||||
92
Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs
Normal file
92
Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs
Normal file
@@ -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 ) ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有Module Package
|
||||
/// </summary>
|
||||
public static List<string> GetPackages()
|
||||
{
|
||||
// 查找Modules目录
|
||||
List<string> modules = new();
|
||||
GetDirectoryPaths(Application.dataPath, modules, "Modules");
|
||||
if (modules.Count == 0)
|
||||
{
|
||||
Debug.LogError("未找到任何Modules目录");
|
||||
return new();
|
||||
}
|
||||
|
||||
// 查找package
|
||||
List<string> packages = new();
|
||||
GetDirectoryPaths(modules[0], packages, "com.");
|
||||
if (packages.Count == 0)
|
||||
{
|
||||
Debug.LogError("未找到任何Package");
|
||||
return new();
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取符合条件的目录
|
||||
/// </summary>
|
||||
/// <param name="root">查找起始目录</param>
|
||||
/// <param name="result">查找结果</param>
|
||||
/// <param name="title">筛选条件(title)</param>
|
||||
/// <param name="tail">筛选条件(tail)</param>
|
||||
private static void GetDirectoryPaths(string root, List<string> 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<string> GetAllAssemblyNames() =>
|
||||
AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
|
||||
|
||||
/// <summary>
|
||||
/// 获取继承 IResource 的所有类
|
||||
/// </summary>
|
||||
public static HashSet<string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs.meta
Normal file
11
Assets/10.StoryEditor/Editor/Graph/GraphEditorTools.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7264316efc98e9e47b55b6dca12b08d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
112
Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs
Normal file
112
Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs
Normal file
@@ -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<string>();
|
||||
private Dictionary<string, string> _paths = new();
|
||||
|
||||
private int _selectedIndex;
|
||||
private int _selectedStoryIndex;
|
||||
private ScriptGraph _selectedStory;
|
||||
private int _selectedAssemblyIndex;
|
||||
private int _selectedLoaderIndex;
|
||||
|
||||
[MenuItem("Evo/剧本编辑器/打开剧本")]
|
||||
private static void Open()
|
||||
{
|
||||
// 打开一个浮动窗口
|
||||
GetWindow<GraphExportWindow>( "剧本配置" ) ;
|
||||
}
|
||||
|
||||
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<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 选择剧本
|
||||
var guids = AssetDatabase.FindAssets($"t:{nameof(ScriptGraph)}");
|
||||
var allAssets = guids.Select(AssetDatabase.GUIDToAssetPath)
|
||||
.Select(AssetDatabase.LoadAssetAtPath<ScriptGraph>)
|
||||
.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<NodeEditorWindow> (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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs.meta
Normal file
11
Assets/10.StoryEditor/Editor/Graph/GraphExportWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba732ba5b1117f549b7d0f8cb6f88449
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -124,38 +124,17 @@ namespace Stary.Evo.StoryEditor
|
||||
#endregion
|
||||
|
||||
#region 导出设置
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[BoxGroup("Export")]
|
||||
[SerializeField, ValueDropdown(nameof(GetAllAssemblyNames))]
|
||||
private string assembly;
|
||||
private IEnumerable<string> GetAllAssemblyNames() =>
|
||||
AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).OrderBy(n => n);
|
||||
|
||||
/// <summary>
|
||||
/// 获取继承 IResource 的所有类
|
||||
/// </summary>
|
||||
private HashSet<string> 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<string> _iResourceTypes = new();
|
||||
#endif
|
||||
|
||||
[BoxGroup("Export", centerLabel:true)]
|
||||
[LabelText("程序集")]
|
||||
public string assembly;
|
||||
|
||||
/// <summary>
|
||||
/// 资源加载方式
|
||||
/// </summary>
|
||||
[BoxGroup("Export", centerLabel:true)]
|
||||
[LabelText("资源加载方式"), ValueDropdown(nameof(IResourceTypes)),SerializeField]
|
||||
private string loaderType;
|
||||
[BoxGroup("Export")]
|
||||
[LabelText("资源加载方式")]
|
||||
public string loaderType;
|
||||
|
||||
private IResource _loader;
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user