Files
ichni_Official/Assets/Scripts/SLSUtilities/Narrative/Base/Data/StoryProjectDatabase.cs
SoulliesOfficial 021e76efe7 同步
2026-06-09 11:21:59 -04:00

181 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.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
}
}
}