164 lines
6.5 KiB
C#
164 lines
6.5 KiB
C#
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using Ichni.Story.UI;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using Yarn.Unity;
|
||
using Yarn.Unity.UnityLocalization;
|
||
|
||
namespace Ichni.Story.Dialogue
|
||
{
|
||
/// <summary>
|
||
/// 故事树 block 与 Yarn <see cref="DialogueRunner"/> 之间的桥接控制器。
|
||
/// <para>点击文本块 → 启动对应 Yarn 节点;对话结束 → 持久化剧情变量、标记 block 完成、
|
||
/// 重算解锁并存档、依次执行结束回调(如歌曲解锁提示,阶段 4 使用)。</para>
|
||
/// </summary>
|
||
public class StoryDialogueController : MonoBehaviour
|
||
{
|
||
public static StoryDialogueController instance;
|
||
|
||
[Header("References")]
|
||
[Tooltip("驱动 Yarn 对话的 DialogueRunner。")]
|
||
public DialogueRunner dialogueRunner;
|
||
|
||
[Tooltip("故事树页面:对话进行时淡出、结束后淡入(可选)。")]
|
||
public StoryUIPage storyUIPage;
|
||
|
||
/// <summary>
|
||
/// 对话结束后依次执行并清空的回调队列。供 <c>unlock_song</c> 等 Yarn 命令(阶段 4)
|
||
/// 把"解锁提示"排队到对话结束后弹出。
|
||
/// </summary>
|
||
[System.NonSerialized] public List<UnityAction> dialogueEndActions = new List<UnityAction>();
|
||
|
||
// 当前正在播放对话的 block id;对话结束时据此标记完成
|
||
private string _activeBlockId;
|
||
|
||
private void Awake()
|
||
{
|
||
instance = this;
|
||
EnsureUnityLocalisedLineProvider();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
if (dialogueRunner != null && dialogueRunner.onDialogueComplete != null)
|
||
dialogueRunner.onDialogueComplete.AddListener(HandleDialogueComplete);
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (dialogueRunner != null && dialogueRunner.onDialogueComplete != null)
|
||
dialogueRunner.onDialogueComplete.RemoveListener(HandleDialogueComplete);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放某文本块对应的 Yarn 节点。若对话已在进行、引用缺失或节点名为空则忽略。
|
||
/// </summary>
|
||
public void PlayBlock(TextBlockView block)
|
||
{
|
||
if (block == null) return;
|
||
|
||
if (dialogueRunner == null)
|
||
{
|
||
Debug.LogError("[StoryDialogueController] 未配置 DialogueRunner,无法开始对话。");
|
||
return;
|
||
}
|
||
|
||
if (dialogueRunner.IsDialogueRunning)
|
||
{
|
||
Debug.LogWarning("[StoryDialogueController] 已有对话在进行中,忽略本次点击。");
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(block.YarnNodeName))
|
||
{
|
||
Debug.LogWarning($"[StoryDialogueController] block '{block.blockId}' 未配置 yarnNodeName,无法开始对话。");
|
||
return;
|
||
}
|
||
|
||
// 将 DialogueRunner 的 YarnProject 对齐到当前章节(支持多章节共用一个 Runner)
|
||
EnsureProjectForCurrentChapter();
|
||
|
||
_activeBlockId = block.blockId;
|
||
|
||
if (storyUIPage != null)
|
||
storyUIPage.FadeOut();
|
||
|
||
// StartDialogue 返回 YarnTask;此处即发即忘,完成由 onDialogueComplete 处理
|
||
dialogueRunner.StartDialogue(block.YarnNodeName).Forget();
|
||
}
|
||
|
||
// 将 DialogueRunner 的 YarnProject 切换为当前已构建章节的 StoryData.yarnProject(若不同且未在运行)
|
||
private void EnsureProjectForCurrentChapter()
|
||
{
|
||
StoryTreeController tree = StoryManager.instance != null ? StoryManager.instance.treeController : null;
|
||
StoryData data = tree != null ? tree.ActiveStoryData : null;
|
||
|
||
if (data == null || data.yarnProject == null) return;
|
||
|
||
if (dialogueRunner.YarnProject != data.yarnProject && !dialogueRunner.IsDialogueRunning)
|
||
dialogueRunner.SetProject(data.yarnProject);
|
||
}
|
||
|
||
/// <summary>
|
||
/// DialogueRunner.lineProvider 为 [SerializeReference] internal 字段。
|
||
/// Inspector 的 "Use Unity Localised Line Provider" 按钮有时无法将 MonoBehaviour
|
||
/// 引用正确持久化到该字段;此方法在运行时用反射补救,确保使用正确的 Provider。
|
||
/// </summary>
|
||
private void EnsureUnityLocalisedLineProvider()
|
||
{
|
||
if (dialogueRunner == null) return;
|
||
|
||
FieldInfo field = typeof(DialogueRunner).GetField(
|
||
"lineProvider",
|
||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||
|
||
if (field == null) return;
|
||
|
||
// 已经是 UnityLocalisedLineProvider 则无需处理
|
||
if (field.GetValue(dialogueRunner) is UnityLocalisedLineProvider) return;
|
||
|
||
UnityLocalisedLineProvider provider =
|
||
dialogueRunner.GetComponent<UnityLocalisedLineProvider>();
|
||
|
||
if (provider != null)
|
||
{
|
||
field.SetValue(dialogueRunner, provider);
|
||
Debug.Log("[StoryDialogueController] Assigned UnityLocalisedLineProvider to DialogueRunner.lineProvider.");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("[StoryDialogueController] UnityLocalisedLineProvider not found on DialogueRunner's GameObject. Localized line text will be unavailable.");
|
||
}
|
||
}
|
||
|
||
private void HandleDialogueComplete()
|
||
{
|
||
// 持久化对话过程中写入的全局剧情变量
|
||
if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null)
|
||
GameSaveManager.instance.StorySaveModule.SaveVariables();
|
||
|
||
// 标记 block 完成 → 重算解锁 → 存档(OnBlockCompleted 内部已去重并保存章节进度)
|
||
if (!string.IsNullOrEmpty(_activeBlockId) &&
|
||
StoryManager.instance != null && StoryManager.instance.treeController != null)
|
||
{
|
||
StoryManager.instance.treeController.OnBlockCompleted(_activeBlockId);
|
||
}
|
||
|
||
_activeBlockId = null;
|
||
|
||
// 依次执行并清空结束回调(如歌曲解锁提示)
|
||
if (dialogueEndActions.Count > 0)
|
||
{
|
||
List<UnityAction> pending = new List<UnityAction>(dialogueEndActions);
|
||
dialogueEndActions.Clear();
|
||
foreach (UnityAction action in pending)
|
||
action?.Invoke();
|
||
}
|
||
|
||
if (storyUIPage != null)
|
||
storyUIPage.FadeIn();
|
||
}
|
||
}
|
||
}
|