跳到主要内容

对话框系统

对话框系统是引擎中所有模态UI的中心枢纽——从简单的文本框到全屏菜单,如背包、图鉴或宝可梦列表。它围绕 DialogManager 单例构建,使用基于队列的架构,包含打字机动画、本地化和分支选择。

DialogManager​

位置: Assets/OpenMon/Core/Runtime/UI/Dialogs/DialogManager.cs 命名空间: OpenMon.MCE.UI.Dialogs

DialogManager 继承 Singleton<DialogManager> 并实现 IInputReceiver。它提供一个完全静态的API——所有公共方法都作为 DialogManager.ShowDialog(...)、DialogManager.ShowChoiceMenu(...) 等调用。

通过Zenject工厂实例化​

DialogManager不会手动放置在场景中。它在运行时通过Zenject实例化:

// DialogManagerInstaller.cs
[CreateAssetMenu(menuName = "MCE/Dependency Injection/Dialog Manager Installer",
fileName = "DialogManagerInstaller")]
public class DialogManagerInstaller : ScriptableObjectInstaller
{
public DialogManager Prefab;

public override void InstallBindings() =>
Container.BindFactory<DialogManager, DialogManager.Factory>()
.FromComponentInNewPrefab(Prefab);
}

DialogManager.Factory 是一个 GameObjectFactory<DialogManager>,它创建预制件并让Zenject注入所有依赖项。

注入的依赖项​

[Inject] private IInputManager inputManager;
[Inject] private ILocalizer localizer;
[Inject] private Roster playerRoster;
[Inject] private EvolutionManager evolutionManager;
[Inject] private MapSceneLauncher mapSceneLauncher;

基本文本对话框​

显示简单对话框​

// 即发即忘:显示文本并等待玩家输入
DialogManager.ShowDialog("Dialogs/Welcome");

// 带有角色说话者
DialogManager.ShowDialog("Dialogs/ProfGreeting", character: professorData);

// 带有文本修饰符(通过本地化的字符串插值)
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });

显示并等待(协程)​

对于顺序对话流,使用协程变体:

yield return DialogManager.ShowDialogAndWait("Dialogs/Moves/Learnt",
localizableModifiers: false,
modifiers: new[] { monsterName, moveName });

这将阻塞协程,直到玩家关闭对话框。

ShowDialog完整签名​

public static void ShowDialog(
string localizationKey,
CharacterData character = null,
MonsterInstance monster = null,
bool acceptInput = true,
float typewriterSpeed = .01f,
float switchToNextAfterSeconds = -1, // -1 = 等待输入
bool localizableModifiers = true,
BasicDialogBackground background = BasicDialogBackground.Normal,
HorizontalAlignmentOptions horizontalAlignment = HorizontalAlignmentOptions.Left,
params string[] modifiers
);

参数说明​

参数用途
localizationKey本地化系统的键(例如 "Dialogs/Welcome")
character如果设置,在说话者面板中显示角色名称
monster如果设置,在说话者面板中显示宝可梦名称/昵称
acceptInput玩家是否可以推进/关闭对话框
typewriterSpeed打字机动画每个字符的秒数
switchToNextAfterSecondsN秒后自动推进(-1 = 手动)
localizableModifiers如果为true,修饰符字符串作为本地化键处理;如果为false,作为字面文本使用
backgroundNormal 或 Sign(不同的视觉背景)
modifiers替换到本地化字符串中 {0}、{1} 等的值

DialogText结构体​

每个对话框内部表示为一个只读结构体 DialogText:

public readonly struct DialogText
{
public readonly string LocalizationKey;
public readonly string[] Modifiers;
public readonly bool LocalizableModifiers;
public readonly CharacterData Character;
public readonly MonsterInstance Monster;
public readonly float TypewriterSpeed;
public readonly float SwitchToNextAfterSeconds;
public readonly BasicDialogBackground Background;
public readonly HorizontalAlignmentOptions HorizontalAlignment;
}

ToString(ILocalizer) 方法解析本地化键并替换修饰符:

public string ToString(ILocalizer localizer)
{
string text = localizer[LocalizationKey];
for (int i = 0; i < Modifiers.Length; i++)
{
string modifier = Modifiers[i];
text = text.Replace("{" + i + "}", LocalizableModifiers ? localizer[modifier] : modifier);
}
return text;
}

对话框队列和流程​

DialogManager使用内部队列处理多个顺序对话框:

ShowDialog("key1") ──► 加入队列 DialogText
ShowDialog("key2") ──► 加入队列 DialogText
ShowDialog("key3") ──► 加入队列 DialogText

玩家按下选择键:
├─ 如果打字机仍在动画中 → SkipTypewriter()
└─ 如果文本已完全显示 → NextDialogInternal()
├─ 如果当前文本还有更多页 → 前进到下一页
├─ 如果队列中还有更多对话框 → 出列并显示下一个
└─ 如果队列为空 → 释放输入,隐藏对话框,设置 active = false

等待对话框​

// WaitUntil 对话框队列为空
yield return DialogManager.WaitForDialog;

// WaitWhile 打字机仍在动画中
yield return DialogManager.WaitForTypewriter;

自动推进​

当 switchToNextAfterSeconds > 0 时,对话框在打字机完成且指定延迟过后自动推进:

private IEnumerator NextDialogAfterSeconds(float seconds)
{
yield return new WaitWhile(() => Text.IsTypewriting);
yield return new WaitForSeconds(seconds);
NextDialog();
}

打字机动画​

文本使用 LocalizedTypeWriterTMP 显示,它包装了 MCETextAnimator——一个基于补间的打字机。下面的 DOTween 类型是 MCE 自己的外观类 OpenMon.Foundation.Tweening.DOTween(随引擎内置),而不是 Demigiant 的资源:

// MCETextAnimator.ShowText()
textComponent.maxVisibleCharacters = 0;
typewriterTween = DOTween.To(
() => textComponent.maxVisibleCharacters,
x => textComponent.maxVisibleCharacters = x,
totalCharacters,
totalCharacters * waitForNormalChars
).SetEase(Ease.Linear)
.OnComplete(() => onTextShowed.Invoke());

速度控制​

LocalizedTypeWriterTMP.SetTypewriterSpeed(float) 设置三个时间层级:

层级变量用途
普通waitForNormalChars每字符基础延迟
中等waitMiddle逗号处延迟(默认为普通5倍)
长waitLong句号处延迟(默认为普通20倍)

文本效果​

MCETextAnimator 支持可选的顶点效果(默认禁用):

  • <wave> — 每个字符的正弦垂直振荡。
  • <shake> — 每帧每个字符的随机偏移。

在组件上设置 enableVertexEffects = true 启用。

跳过​

在打字机动画期间按下选择按钮会调用 SkipTypewriter(),立即显示所有字符。

说话者面板​

当传递 CharacterData 或 MonsterInstance 时,对话框会显示说话者姓名面板:

if (nextText.Character != null)
{
CharacterName.SetValue(nextText.Character.LocalizableName);
CharacterPanel.Show();
}
else if (nextText.Monster != null)
{
CharacterName.Text.SetText(nextText.Monster.GetNameOrNickName(localizer));
CharacterPanel.Show();
}
else
CharacterPanel.Show(false);

选项菜单​

选项对话框使用 ChoiceMenuController——一个 MenuSelector 子类,支持1-11个选项并自动调整大小:

DialogManager.ShowChoiceMenu(
options: new List<string> { "Common/True", "Common/False" },
callback: choice => { /* 0 = 是, 1 = 否 */ },
onBackCallback: () => { /* 视为取消 */ },
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);

ShowChoiceMenu完整签名​

public static void ShowChoiceMenu(
List<string> options, // 每个选项的本地化键
Action<int> callback, // 用选中的索引调用
Transform position = null, // 自定义位置(null = 默认)
Action onBackCallback = null, // 如果设置,返回按钮关闭并调用此回调
CharacterData character = null, // 说话者
MonsterInstance monster = null, // 说话者
bool showDialog = false, // 在选择旁边显示文本对话框
string localizationKey = "", // 对话框文本的本地化键
bool localizableModifiers = true,
params string[] modifiers
);

内部工作原理​

  1. ChoiceMenuController.SetNumberOfOptions(n) 启用前N个 ChoiceOption 项目并调整面板大小。
  2. 每个选项的 LocalizedTextMeshPro 被设置为提供的本地化键。
  3. 选项菜单在打字机完成后出现(当 showDialog 为true时)。
  4. 选择时,回调触发,选项菜单自动隐藏。
  5. 返回时(如果允许),onBackCallback 触发。

常见模式:带有协程的是/否​

int choice = -1;

DialogManager.ShowChoiceMenu(
new List<string> { "Common/True", "Common/False" },
playerChoice => choice = playerChoice,
onBackCallback: () => choice = 1,
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);

yield return new WaitUntil(() => choice != -1);

if (choice == 0)
{
// 玩家选择了"是"
}

本地化集成​

所有对话框文本都通过 ILocalizer 系统:

  • LocalizedTextMeshPro.SetValue(key, localizableModifiers, modifiers) 解析键并替换 {0}、{1} 等。
  • 当 localizableModifiers 为true时,修饰符字符串本身作为本地化键解析。
  • 当为false时,修饰符作为字面字符串插入(适用于玩家名称、数字等)。
// 可本地化的修饰符:"Dialogs/Caught" 和 "Monsters/Pikachu" 都会被解析
DialogManager.ShowDialog("Dialogs/Caught", modifiers: "Monsters/Pikachu");

// 字面修饰符:"500" 按原样插入
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });

复杂对话框(托管屏幕)​

除了基本文本之外,DialogManager 拥有每个主要屏幕的引用并提供静态启动方法:

方法打开
ShowGameMenu(...)游戏内暂停菜单
ShowPlayerRosterMenu(...)宝可梦队伍画面(带有选择/交换模式)
ShowBag(...)带标签页的物品栏(战斗模式、出售模式、选择模式)
ShowDexScreen(...)完整图鉴浏览器
ShowSingleMonsterDexScreen(...)单宝可梦图鉴条目
ShowShop(...)买卖商店
ShowOptionsMenu()设置画面
ShowProfileScreen(...)训练家卡
ShowQuestsScreen(...)任务日志
RequestTextInput(...)键盘输入对话框(命名等)
ShowMoveTutorDialog(...)招式传授选择
ShowMoveLearnPanel(...)招式学习/替换流程
ShowNewMonsterDialog(...)新宝可梦昵称 + 存储流程
ShowXPGainDialogLearnMoveAndEvolveForSingleMonsterInPlayerRoster(...)完整XP获取 -> 升级 -> 学习招式 -> 进化管道
CloseMenus()按顺序关闭所有打开的屏幕

通知​

DialogManager.Notifications 公开子 NotificationManager 用于toast风格的通知:

DialogManager.Notifications.StopAllNotifications();

加载指示器​

DialogManager.ShowLoadingIcon(true);
DialogManager.ShowLoadingText("Loading/MapData");
// ... 异步工作 ...
DialogManager.ClearLoadingText();
DialogManager.ShowLoadingIcon(false);

从CommandGraph使用对话框​

NPC行为在 CommandGraph(一个基于节点的可视化脚本系统)中创作。对话框节点内部调用相同的 DialogManager.ShowDialog(...) 和 DialogManager.ShowChoiceMenu(...) 静态API。CommandGraph节点处理:

  • 按顺序链式连接多个对话框文本
  • 根据选择结果分支
  • 按节点设置说话角色
  • 在推进图之前等待对话框完成

从代码层面,你与相同的静态方法交互——底层API没有区别。

背景变体​

BasicDialogBackground 是嵌套在 DialogManager 内部的枚举(即 DialogManager.BasicDialogBackground):

// 在 DialogManager 内部定义
public enum BasicDialogBackground
{
Normal, // 标准对话框
Sign // 标牌 / 公告板样式
}

将 background: DialogManager.BasicDialogBackground.Sign 传递给 ShowDialog() 以使用标牌变体。DialogManager在存储在 SerializableDictionary<BasicDialogBackground, HidableUiElement> 中的序列化背景 HidableUiElement 引用之间切换。