阶段性完成
This commit is contained in:
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
@@ -147,9 +148,9 @@ namespace Ichni.Story
|
||||
|
||||
[FoldoutGroup("$EditorHeader/Text Content")]
|
||||
[ShowIf("blockType", StoryBlockType.Text)]
|
||||
[LabelText("Title Key")]
|
||||
[Tooltip("该 TextBlock 标题使用的 Unity Localization Entry Key;留空时视觉层可使用 Block ID 作为开发阶段回退。")]
|
||||
public string titleKey;
|
||||
[LabelText("Title")]
|
||||
[Tooltip("该 TextBlock 的 Unity Localization String Reference。请在此直接选择 String Table 与 Entry;留空时视觉层使用 Block ID 作为开发阶段回退。")]
|
||||
public LocalizedString title = new LocalizedString();
|
||||
|
||||
/// <summary>
|
||||
/// TextBlock 的视觉和叙事层级。Important 用于主线转折与后续 checkpoint;
|
||||
@@ -229,7 +230,9 @@ namespace Ichni.Story
|
||||
{
|
||||
string title = blockType switch
|
||||
{
|
||||
StoryBlockType.Text => titleKey,
|
||||
StoryBlockType.Text => this.title != null && !this.title.IsEmpty
|
||||
? this.title.TableEntryReference.ToString()
|
||||
: null,
|
||||
StoryBlockType.Song => songName,
|
||||
StoryBlockType.Tutorial => tutorialName,
|
||||
_ => null
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Localization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
@@ -146,10 +148,7 @@ namespace Ichni.Story
|
||||
issues.Add($"[Error] Timeline Marker '{marker.markerId}' 的 Anchor Block '{marker.anchorBlockId}' 不是 TextBlock。");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(marker.labelKey))
|
||||
{
|
||||
issues.Add($"[Warning] Timeline Marker '{marker.markerId}' 未配置 Label Key。");
|
||||
}
|
||||
ValidateLocalizedString(marker.label, $"Timeline Marker '{marker.markerId}' Label", issues);
|
||||
}
|
||||
|
||||
ValidateTimelineMarkerAnchors(reachableBlockIds, issues);
|
||||
@@ -203,8 +202,10 @@ namespace Ichni.Story
|
||||
|
||||
switch (block.blockType)
|
||||
{
|
||||
case StoryBlockType.Text when string.IsNullOrWhiteSpace(block.yarnNodeName):
|
||||
issues.Add($"[Warning] TextBlock '{block.blockId}' 未配置 Yarn Node。");
|
||||
case StoryBlockType.Text:
|
||||
if (string.IsNullOrWhiteSpace(block.yarnNodeName))
|
||||
issues.Add($"[Warning] TextBlock '{block.blockId}' 未配置 Yarn Node。");
|
||||
ValidateLocalizedString(block.title, $"TextBlock '{block.blockId}' Title", issues);
|
||||
break;
|
||||
case StoryBlockType.Song when string.IsNullOrWhiteSpace(block.songName):
|
||||
issues.Add($"[Warning] SongBlock '{block.blockId}' 未配置 Song ID。");
|
||||
@@ -295,6 +296,36 @@ namespace Ichni.Story
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查 LocalizedString 是否已选择有效的 String Table 与 Entry。
|
||||
/// 只读取 Unity Localization 的 Shared Table Data,不加载 Addressables,也不修改资产;
|
||||
/// 因此可在 StoryData Inspector 中尽早发现 CSV 尚未导入或引用错误。
|
||||
/// </summary>
|
||||
private static void ValidateLocalizedString(
|
||||
LocalizedString reference,
|
||||
string owner,
|
||||
List<string> issues)
|
||||
{
|
||||
if (reference == null || reference.IsEmpty)
|
||||
{
|
||||
issues.Add($"[Warning] {owner} 未配置 LocalizedString。");
|
||||
return;
|
||||
}
|
||||
|
||||
StringTableCollection tableCollection =
|
||||
LocalizationEditorSettings.GetStringTableCollection(reference.TableReference);
|
||||
if (tableCollection?.SharedData == null)
|
||||
{
|
||||
issues.Add($"[Error] {owner} 引用的 String Table '{reference.TableReference}' 不存在或无法读取 Shared Table Data。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableCollection.SharedData.GetEntryFromReference(reference.TableEntryReference) == null)
|
||||
{
|
||||
issues.Add($"[Error] {owner} 引用的 Entry '{reference.TableEntryReference}' 不存在于 String Table '{tableCollection.TableCollectionName}'。");
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<string> ValidateStoryGraph(HashSet<string> blockIds, List<string> issues)
|
||||
{
|
||||
Dictionary<string, List<string>> adjacency = new Dictionary<string, List<string>>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
@@ -8,8 +9,8 @@ namespace Ichni.Story
|
||||
/// 一个章节 Timeline 标记的静态定义。
|
||||
/// <para>它不保存独立的横坐标:运行时始终跟随锚定 Block 的 <c>gridColumn</c>,
|
||||
/// 使时间轴与剧情树在横向拖动时保持对齐。</para>
|
||||
/// <para>显示文本和 ID 使用 Unity Localization 的 Entry Key;本阶段不绑定具体 String Table,
|
||||
/// 以便后续统一迁移本地化系统时集中配置。</para>
|
||||
/// <para>显示文本直接使用 <see cref="LocalizedString"/>,因此可在 Inspector 中选择
|
||||
/// String Table 与 Entry,并预览各语言的值。</para>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[InlineProperty]
|
||||
@@ -37,13 +38,13 @@ namespace Ichni.Story
|
||||
public string anchorBlockId;
|
||||
|
||||
/// <summary>
|
||||
/// Timeline 上显示的本地化文本 Key。通常用于时间描述,也可用于特殊叙事文本。
|
||||
/// Timeline 上显示的本地化文本。通常用于时间描述,也可用于特殊叙事文本。
|
||||
/// </summary>
|
||||
[HorizontalGroup("Identity")]
|
||||
[LabelText("Label Key")]
|
||||
[LabelWidth(54)]
|
||||
[Tooltip("Timeline 上显示的 Unity Localization Entry Key,通常用于时间或特殊叙事文本。")]
|
||||
public string labelKey;
|
||||
[LabelText("Label")]
|
||||
[LabelWidth(40)]
|
||||
[Tooltip("Timeline 上显示的 Unity Localization String Reference。请在此直接选择对应 String Table 与 Entry。")]
|
||||
public LocalizedString label = new LocalizedString();
|
||||
|
||||
/// <summary>
|
||||
/// 是否参与章节主线进度百分比计算。支线或纯装饰性时间标记应关闭此项。
|
||||
|
||||
@@ -3,6 +3,8 @@ using Ichni.Menu.UI;
|
||||
using Ichni.Story.UI;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Tables;
|
||||
|
||||
namespace Ichni.Story
|
||||
{
|
||||
@@ -13,6 +15,9 @@ namespace Ichni.Story
|
||||
/// </summary>
|
||||
public static class TutorialFlowController
|
||||
{
|
||||
/// <summary>教程确认弹窗统一使用 Message String Table,避免业务代码内嵌可见文案。</summary>
|
||||
private static readonly TableReference MessageTable = "Message";
|
||||
|
||||
/// <summary>
|
||||
/// 由 <see cref="UI.TutorialBlockView"/> 点击时调用。配置无效或 MessageUIPage 未配置时只报错,不修改剧情进度。
|
||||
/// </summary>
|
||||
@@ -32,14 +37,18 @@ namespace Ichni.Story
|
||||
}
|
||||
|
||||
// TutorialFlowController 只描述“有哪些选择、各自触发什么效果”;
|
||||
// 具体按钮由通用 SelectionBox 在运行时生成,后续其它系统可复用同一 UI。
|
||||
messageUIPage.ShowSelectionBox(
|
||||
tutorialBlock.tutorialName,
|
||||
string.Empty,
|
||||
new List<SelectionBoxOption>
|
||||
// 具体按钮由通用 SelectionBox 在运行时生成,且按钮文案与标题均由 Message 表解析。
|
||||
messageUIPage.ShowLocalizedSelectionBox(
|
||||
new LocalizedPopupText(MessageTable, "system_tutorial_entry_title"),
|
||||
new LocalizedPopupText(MessageTable, "system_tutorial_entry_content"),
|
||||
new List<LocalizedSelectionBoxOption>
|
||||
{
|
||||
new("游玩教程", () => PlayTutorial(tutorialBlock, chapter, song, difficulty)),
|
||||
new("跳过教程", () => SkipTutorial(tutorialBlock))
|
||||
new(
|
||||
new LocalizedPopupText(MessageTable, "system_tutorial_enter"),
|
||||
() => PlayTutorial(tutorialBlock, chapter, song, difficulty)),
|
||||
new(
|
||||
new LocalizedPopupText(MessageTable, "system_tutorial_skip"),
|
||||
() => SkipTutorial(tutorialBlock))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Localization;
|
||||
using Ichni.Menu.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Settings;
|
||||
using UnityEngine.Localization.Tables;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
@@ -39,15 +36,13 @@ namespace Ichni.Story.UI
|
||||
[Tooltip("自动在左右各扩展一个刻度周期,保证刻度条循环平移时不会从 Timeline 边缘露出空白。")]
|
||||
public bool expandTickRepeatRect = true;
|
||||
|
||||
[Header("Localization")]
|
||||
[Tooltip("StoryTimeline 的 Unity Localization String Table。Marker.labelKey 在该 Table 中查找。")]
|
||||
public TableReference localizationTable;
|
||||
|
||||
private sealed class RuntimeMarker
|
||||
{
|
||||
public StoryTimelineMarkerDefinition definition;
|
||||
public StoryBlockView anchorView;
|
||||
public StoryTimelineMarkerView view;
|
||||
public string localizedLabel;
|
||||
public LocalizedString.ChangeHandler localizedLabelChanged;
|
||||
}
|
||||
|
||||
private readonly List<RuntimeMarker> _markers = new List<RuntimeMarker>();
|
||||
@@ -69,7 +64,6 @@ namespace Ichni.Story.UI
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
LocalizationSettings.SelectedLocaleChanged += OnSelectedLocaleChanged;
|
||||
SubscribeToStoryScrollRect();
|
||||
|
||||
// StoryPage 从对话界面返回后,Canvas 与 StoryTree 会在同一帧重新启用。
|
||||
@@ -79,7 +73,6 @@ namespace Ichni.Story.UI
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
LocalizationSettings.SelectedLocaleChanged -= OnSelectedLocaleChanged;
|
||||
UnsubscribeFromStoryScrollRect();
|
||||
}
|
||||
|
||||
@@ -124,6 +117,7 @@ namespace Ichni.Story.UI
|
||||
|
||||
foreach (RuntimeMarker marker in _markers)
|
||||
{
|
||||
UnbindLocalizedLabel(marker);
|
||||
if (marker.view != null)
|
||||
{
|
||||
Destroy(marker.view.gameObject);
|
||||
@@ -356,16 +350,19 @@ namespace Ichni.Story.UI
|
||||
|
||||
StoryTimelineMarkerView view = Instantiate(markerPrefab, markerContainer);
|
||||
view.name = $"StoryTimelineMarker_{marker.markerId}";
|
||||
view.SetLabel(GetLocalizedLabel(marker.labelKey));
|
||||
|
||||
RuntimeMarker runtimeMarker = new RuntimeMarker
|
||||
{
|
||||
definition = marker,
|
||||
anchorView = anchorView,
|
||||
view = view
|
||||
view = view,
|
||||
localizedLabel = marker.markerId
|
||||
};
|
||||
view.BindClick(() => RequestRollback(runtimeMarker));
|
||||
_markers.Add(runtimeMarker);
|
||||
// LocalizedString 首次读取是异步的;先显示稳定 Marker ID,避免 Prefab 默认文字短暂闪现。
|
||||
view.SetLabel(runtimeMarker.localizedLabel);
|
||||
BindLocalizedLabel(runtimeMarker);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -388,48 +385,52 @@ namespace Ichni.Story.UI
|
||||
return;
|
||||
}
|
||||
|
||||
string markerLabel = GetLocalizedLabel(marker.definition.labelKey);
|
||||
messageUIPage.ShowSelectionBox(
|
||||
"重开本章节",
|
||||
$"将重置“{markerLabel}”所在列及其之后的剧情选择与进度。歌曲成绩、解锁内容、设置和其它章节不会受到影响。",
|
||||
new List<SelectionBoxOption>
|
||||
string markerLabel = marker.localizedLabel;
|
||||
messageUIPage.ShowLocalizedSelectionBox(
|
||||
new LocalizedPopupText("Message", "system_rollback_chapter_title"),
|
||||
new LocalizedPopupText("Message", "system_rollback_chapter_content", markerLabel),
|
||||
new List<LocalizedSelectionBoxOption>
|
||||
{
|
||||
new SelectionBoxOption("重开此时间点", () => _treeController.TryRollbackToMarker(marker.definition.markerId)),
|
||||
new SelectionBoxOption("取消", () => { })
|
||||
new LocalizedSelectionBoxOption(
|
||||
new LocalizedPopupText("Message", "system_rollback_confirm"),
|
||||
() => _treeController.TryRollbackToMarker(marker.definition.markerId)),
|
||||
new LocalizedSelectionBoxOption(
|
||||
new LocalizedPopupText("UI", "ui_common_cancel"),
|
||||
() => { })
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 StoryTimeline 指定的 Unity Localization String Table 解析 Label Key。
|
||||
/// String Table 或条目尚未配置时回退显示 Key,确保开发阶段仍能从 UI 上定位缺失翻译。
|
||||
/// 订阅 Marker 自身的 LocalizedString。LocalizedString 会负责首次异步读取与 Locale
|
||||
/// 切换后的自动刷新,Timeline 不再维护独立的 Table / Key 解析逻辑。
|
||||
/// </summary>
|
||||
private string GetLocalizedLabel(string labelKey)
|
||||
private static void BindLocalizedLabel(RuntimeMarker marker)
|
||||
{
|
||||
if (string.IsNullOrEmpty(labelKey))
|
||||
if (marker?.definition?.label == null || marker.definition.label.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
marker?.view?.SetLabel(marker?.localizedLabel ?? string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
return LocalizationTextService.Resolve(localizationTable, labelKey, labelKey);
|
||||
marker.localizedLabelChanged = localizedText =>
|
||||
{
|
||||
marker.localizedLabel = string.IsNullOrEmpty(localizedText)
|
||||
? marker.definition.markerId
|
||||
: localizedText;
|
||||
|
||||
if (marker.view != null)
|
||||
marker.view.SetLabel(marker.localizedLabel);
|
||||
};
|
||||
marker.definition.label.StringChanged += marker.localizedLabelChanged;
|
||||
}
|
||||
|
||||
private async void OnSelectedLocaleChanged(Locale _)
|
||||
private static void UnbindLocalizedLabel(RuntimeMarker marker)
|
||||
{
|
||||
foreach (RuntimeMarker marker in _markers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(marker.definition.labelKey)) continue;
|
||||
if (marker?.definition?.label == null || marker.localizedLabelChanged == null)
|
||||
return;
|
||||
|
||||
string localizedLabel = await LocalizationTextService.ResolveAsync(
|
||||
localizationTable,
|
||||
marker.definition.labelKey,
|
||||
marker.definition.labelKey);
|
||||
|
||||
// 异步等待结束后,防范对象在此期间被销毁。
|
||||
if (marker.view != null)
|
||||
{
|
||||
marker.view.SetLabel(localizedLabel);
|
||||
}
|
||||
}
|
||||
marker.definition.label.StringChanged -= marker.localizedLabelChanged;
|
||||
marker.localizedLabelChanged = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using Ichni.Story.Dialogue;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
@@ -24,6 +25,21 @@ namespace Ichni.Story.UI
|
||||
// 当前已实例化的状态背景对象;状态切换时销毁并替换
|
||||
private GameObject _currentVisual;
|
||||
|
||||
// 直接持有 StoryData 的 LocalizedString,并订阅其 StringChanged 回调。
|
||||
// Unity Localization 会在首次订阅和语言切换时自行异步刷新文本。
|
||||
private LocalizedString _titleReference;
|
||||
private bool _isTitleLocalizationSubscribed;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
StartTitleLocalization();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
StopTitleLocalization();
|
||||
}
|
||||
|
||||
public override void Initialize(StoryBlockDefinition def, Vector2 position, StoryBlockState blockState)
|
||||
{
|
||||
// 注意:base.Initialize 末尾会调用 ApplyState(state),届时已生成对应背景
|
||||
@@ -34,9 +50,43 @@ namespace Ichni.Story.UI
|
||||
if (storyIdText != null)
|
||||
storyIdText.text = def.blockId;
|
||||
|
||||
// 当前仍直接显示 titleKey;正式内容接入时需要由 Unity Localization 解析玩家可见标题。
|
||||
StopTitleLocalization();
|
||||
_titleReference = def.title;
|
||||
StartTitleLocalization();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅当前 TextBlock 的 LocalizedString。无需手写 Table / Key 读取或监听全局 Locale,
|
||||
/// LocalizedString 会遵循 Unity 官方生命周期异步加载,并在语言变化后触发 StringChanged。
|
||||
/// </summary>
|
||||
private void StartTitleLocalization()
|
||||
{
|
||||
if (_isTitleLocalizationSubscribed || titleText == null)
|
||||
return;
|
||||
|
||||
if (_titleReference == null || _titleReference.IsEmpty)
|
||||
{
|
||||
titleText.text = blockId;
|
||||
return;
|
||||
}
|
||||
|
||||
_titleReference.StringChanged += ApplyLocalizedTitle;
|
||||
_isTitleLocalizationSubscribed = true;
|
||||
}
|
||||
|
||||
private void StopTitleLocalization()
|
||||
{
|
||||
if (!_isTitleLocalizationSubscribed || _titleReference == null)
|
||||
return;
|
||||
|
||||
_titleReference.StringChanged -= ApplyLocalizedTitle;
|
||||
_isTitleLocalizationSubscribed = false;
|
||||
}
|
||||
|
||||
private void ApplyLocalizedTitle(string localizedTitle)
|
||||
{
|
||||
if (titleText != null)
|
||||
titleText.text = def.titleKey;
|
||||
titleText.text = string.IsNullOrEmpty(localizedTitle) ? blockId : localizedTitle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ichni.Story.Dialogue;
|
||||
using Ichni.Story.UI;
|
||||
using Ichni.Localization;
|
||||
using Ichni.Menu;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
@@ -14,14 +15,17 @@ namespace Ichni.Story.YarnFunctions
|
||||
// ── Yarn Commands ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 兼容既有 Yarn 脚本:授予歌曲相关的解锁 Key,并在当前对话结束后弹出提示。
|
||||
/// 格式: <<unlock_song unlock_key>>
|
||||
/// 授予歌曲相关的解锁 Key,并在当前对话结束后弹出提示。
|
||||
/// 格式: <<unlock_song unlock_key "song_id">>
|
||||
/// <para>Key 必须使用小写字母、数字和下划线,例如 <c>story_ch0_prologue_completed</c>。</para>
|
||||
/// <para><c>song_id</c> 必须是 <see cref="SongItemData.songName"/> 的稳定标识,而不是显示名称或本地化文本。
|
||||
/// 该参数用于解析玩家可见的歌曲名称,绝不能将技术 Unlock Key 显示给玩家。</para>
|
||||
/// 新内容若不需要歌曲提示,优先使用 <see cref="GrantUnlock"/>。
|
||||
/// </summary>
|
||||
/// <param name="songUnlockKey">要授予的稳定解锁 Key,而不是歌曲显示名称。</param>
|
||||
/// <param name="songId">对应 <see cref="SongItemData.songName"/> 的稳定歌曲 ID。</param>
|
||||
[YarnCommand("unlock_song")]
|
||||
public static void UnlockSong(string songUnlockKey)
|
||||
public static void UnlockSong(string songUnlockKey, string songId)
|
||||
{
|
||||
ExecuteAfterDialogueCommit(() =>
|
||||
{
|
||||
@@ -31,7 +35,7 @@ namespace Ichni.Story.YarnFunctions
|
||||
}
|
||||
|
||||
if (MessageUIPage.instance != null)
|
||||
MessageUIPage.instance.ShowUnlockMessage(songUnlockKey);
|
||||
MessageUIPage.instance.ShowUnlockMessage(ResolveSongDisplayName(songId));
|
||||
else
|
||||
Debug.LogError("[StoryTreeCommands] 弹窗失败:场景中未找到 StoryMessageBoxUIPage 实例!");
|
||||
|
||||
@@ -57,7 +61,7 @@ namespace Ichni.Story.YarnFunctions
|
||||
|
||||
/// <summary>
|
||||
/// 在对话结束后,弹出一个自定义 MessageBox 提示。
|
||||
/// 会尝试使用 Unity Localization 进行翻译,如果找不到对应条目则回退显示原文本(Key)。
|
||||
/// 文本 Key 会交给 MessageUIPage 的 FIFO 队列,并在该弹窗真正显示前才按当前 Locale 解析。
|
||||
/// 格式: <<show_message titleKey contentKey [tableName]>>
|
||||
/// </summary>
|
||||
/// <param name="titleKey">标题文本或本地化键</param>
|
||||
@@ -66,13 +70,13 @@ namespace Ichni.Story.YarnFunctions
|
||||
[YarnCommand("show_message")]
|
||||
public static void ShowMessage(string titleKey, string contentKey, string tableName = "Message")
|
||||
{
|
||||
string translatedTitle = GetTranslatedText(tableName, titleKey);
|
||||
string translatedContent = GetTranslatedText(tableName, contentKey);
|
||||
Debug.Log($"[StoryTreeCommands] 准备在对话结束后显示自定义弹窗:标题='{translatedTitle}',内容='{translatedContent}'");
|
||||
Debug.Log($"[StoryTreeCommands] 准备在对话结束后显示本地化弹窗:Table='{tableName}',TitleKey='{titleKey}',ContentKey='{contentKey}'。");
|
||||
ExecuteAfterDialogueCommit(() =>
|
||||
{
|
||||
if (MessageUIPage.instance != null)
|
||||
MessageUIPage.instance.ShowCustomMessage(translatedTitle, translatedContent);
|
||||
MessageUIPage.instance.ShowLocalizedMessage(
|
||||
new LocalizedPopupText(tableName, titleKey),
|
||||
new LocalizedPopupText(tableName, contentKey));
|
||||
else
|
||||
Debug.LogError("[StoryTreeCommands] 无法显示自定义弹窗:未找到 MessageUIPage 实例。");
|
||||
});
|
||||
@@ -128,9 +132,36 @@ namespace Ichni.Story.YarnFunctions
|
||||
|
||||
// ── 内部辅助 ─────────────────────────────────────────────────────────────
|
||||
|
||||
private static string GetTranslatedText(string tableName, string key)
|
||||
/// <summary>
|
||||
/// 以稳定 Song ID 查找当前项目中配置的歌曲显示名。当前阶段使用 <see cref="SongItemData.displaySongName"/>;
|
||||
/// 将来歌曲标题接入 Chapter Content 本地化后,只需替换本函数内部的返回逻辑,Yarn 与弹窗 API 无需修改。
|
||||
/// </summary>
|
||||
private static string ResolveSongDisplayName(string songId)
|
||||
{
|
||||
return LocalizationTextService.Resolve(tableName, key, key);
|
||||
List<ChapterSelectionUnit> chapters = ChapterSelectionManager.instance?.chapters;
|
||||
if (chapters != null)
|
||||
{
|
||||
foreach (ChapterSelectionUnit chapter in chapters)
|
||||
{
|
||||
if (chapter?.songs == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (SongItemData song in chapter.songs)
|
||||
{
|
||||
if (song != null && song.songName == songId)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(song.displaySongName)
|
||||
? song.songName
|
||||
: song.displaySongName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[StoryTreeCommands] 无法为歌曲解锁提示解析 Song ID '{songId}',将显示该 ID。", ChapterSelectionManager.instance);
|
||||
return songId ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user