This commit is contained in:
SoulliesOfficial
2026-06-09 11:21:59 -04:00
parent 7c60c40d6b
commit 021e76efe7
493 changed files with 50500 additions and 2211 deletions

View File

@@ -0,0 +1,181 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace SLSUtilities.Narrative
{
[CreateAssetMenu(fileName = "Story Project Database", menuName = "SLSUtilities/Story System/Story Project Database")]
public class StoryProjectDatabase : SerializedScriptableObject
{
// 巧妙利用 Odin 的嵌套分组语法:"父分组/子分组"
// 这样就可以让 TitleGroup 作为父节点显示在最上方,而 BoxGroup 嵌套在其中,解决了 Title 被包裹在 Box 里的问题。
[TitleGroup("全局剧情数据库", "集中管理所有剧情相关数据的注册中心", Alignment = TitleAlignments.Centered)]
[ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "nameKey")]
[BoxGroup("全局剧情数据库/角色档案 (Characters)")]
[LabelText("角色档案列表 (Character Profiles)")]
public List<CharacterData> characters = new List<CharacterData>();
[ListDrawerSettings(ShowIndexLabels = true)]
[BoxGroup("全局剧情数据库/变量数据 (Variables)")]
[LabelText("变量数据组 (Variable Groups)")]
public List<VariableData> variables = new List<VariableData>();
[ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "keyword")]
[BoxGroup("全局剧情数据库/已注册的资产 (Registered Assets)")]
[LabelText("关键词词条 (Keywords)")]
public List<KeywordData> keywords = new List<KeywordData>();
[ListDrawerSettings(ShowIndexLabels = true, ListElementLabelName = "storyId")]
[BoxGroup("全局剧情数据库/剧情入口 (Narrative Entries)")]
[LabelText("剧情入口路由表 (Narrative Entries)")]
public List<NarrativeEntry> narrativeEntries = new List<NarrativeEntry>();
[TitleGroup("Yarn File Export Settings", "NPC 变量定义文件导出配置", Alignment = TitleAlignments.Centered)]
[FolderPath]
[Required("请指定 Yarn 文件的导出目标文件夹!")]
[Tooltip("生成的 NPC_IDs.yarn 文件的保存目录(建议放在 Yarn 脚本文件夹下)")]
public string exportFolder = "Assets/Story";
[Button("生成 NPC_IDs.yarn (Generate)", ButtonSizes.Medium)]
[GUIColor(0.2f, 0.8f, 0.4f)]
public void GenerateNpcIdsYarnFile()
{
if (string.IsNullOrEmpty(exportFolder))
{
Debug.LogError("[StoryProjectDatabase] 导出失败:未指定有效的导出文件夹路径!");
return;
}
if (characters == null || characters.Count == 0)
{
Debug.LogWarning("[StoryProjectDatabase] 角色列表为空,已取消生成。");
return;
}
// 确保目标导出目录存在
if (!System.IO.Directory.Exists(exportFolder))
{
try
{
System.IO.Directory.CreateDirectory(exportFolder);
}
catch (System.Exception ex)
{
Debug.LogError($"[StoryProjectDatabase] 无法创建目标文件夹 '{exportFolder}': {ex.Message}");
return;
}
}
string filePath = System.IO.Path.Combine(exportFolder, "NPC_IDs.yarn");
try
{
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(filePath, false, System.Text.Encoding.UTF8))
{
writer.WriteLine("// ===========================================================================");
writer.WriteLine("// 自动生成的 NPC 英文标准名 ID 变量定义文件。");
writer.WriteLine("// 供 VS Code Yarn Spinner 插件进行命令变量补全(例如:输入 $NPC_ 触发提示)。");
writer.WriteLine("// 警告:该文件为程序自动生成,请勿在此文件中手动编辑或添加内容。");
writer.WriteLine("// ===========================================================================");
writer.WriteLine();
// 必须将 declare 声明置于有效的 Node 结构内,防止编译器在解析无 Node 的文件时抛出 Token 识别错误token recognition error at: '<'
writer.WriteLine("title: NPC_IDs");
writer.WriteLine("---");
foreach (var charData in characters)
{
if (charData == null || string.IsNullOrWhiteSpace(charData.nameKey)) continue;
string trimmedId = charData.nameKey.Trim();
// 生成格式如:<<declare $NPC_OldMan = "OldMan">>
writer.WriteLine($"<<declare $NPC_{trimmedId} = \"{trimmedId}\">>");
}
writer.WriteLine("===");
}
#if UNITY_EDITOR
// 刷新 AssetDatabase让 Unity 编辑器立刻加载生成的 .yarn 资源
UnityEditor.AssetDatabase.Refresh();
#endif
Debug.Log($"[StoryProjectDatabase] 成功生成/更新 NPC 声明文件: '{filePath}'");
}
catch (System.Exception ex)
{
Debug.LogError($"[StoryProjectDatabase] 导出 NPC_IDs.yarn 失败: {ex.Message}");
}
}
[Button("自动扫描并注册数据 (Auto-Scan Directory)", ButtonSizes.Large, Icon = SdfIconType.Search)]
[GUIColor(0.4f, 0.8f, 1f)]
[PropertyTooltip("自动在当前数据库所在的文件夹(及其子文件夹)中寻找所有的 CharacterData、VariableData、KeywordData 和 NarrativeEntry并自动填入上方的列表中。")]
public void AutoPopulate()
{
#if UNITY_EDITOR
// 获取当前 Database 资产所在的目录路径
string dbPath = AssetDatabase.GetAssetPath(this);
if (string.IsNullOrEmpty(dbPath))
{
Debug.LogWarning("[StorySystem] 请先将数据库资产 (Database) 保存到项目目录中。");
return;
}
string searchDirectory = System.IO.Path.GetDirectoryName(dbPath);
// 搜索 CharacterData
string[] charGuids = AssetDatabase.FindAssets($"t:{nameof(CharacterData)}", new[] { searchDirectory });
characters.Clear();
foreach (var guid in charGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var charData = AssetDatabase.LoadAssetAtPath<CharacterData>(assetPath);
if (charData != null && !characters.Contains(charData))
characters.Add(charData);
}
// 搜索 VariableData
string[] varGuids = AssetDatabase.FindAssets($"t:{nameof(VariableData)}", new[] { searchDirectory });
variables.Clear();
foreach (var guid in varGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var varData = AssetDatabase.LoadAssetAtPath<VariableData>(assetPath);
if (varData != null && !variables.Contains(varData))
variables.Add(varData);
}
// 搜索 KeywordData
string[] kwGuids = AssetDatabase.FindAssets($"t:{nameof(KeywordData)}", new[] { searchDirectory });
keywords.Clear();
foreach (var guid in kwGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var kwData = AssetDatabase.LoadAssetAtPath<KeywordData>(assetPath);
if (kwData != null && !keywords.Contains(kwData))
keywords.Add(kwData);
}
// 搜索 NarrativeEntry
string[] entryGuids = AssetDatabase.FindAssets($"t:{nameof(NarrativeEntry)}", new[] { searchDirectory });
narrativeEntries.Clear();
foreach (var guid in entryGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var entryData = AssetDatabase.LoadAssetAtPath<NarrativeEntry>(assetPath);
if (entryData != null && !narrativeEntries.Contains(entryData))
narrativeEntries.Add(entryData);
}
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
Debug.Log($"[StorySystem] 自动扫描完成!共注册了 {characters.Count} 个角色档案、{variables.Count} 个变量数据组、{keywords.Count} 个关键词词条 和 {narrativeEntries.Count} 个剧情入口路由表。");
#else
Debug.LogWarning("自动扫描 (AutoPopulate) 只能在 Unity 编辑器环境下运行。");
#endif
}
}
}