Dialog System
The dialog system is the engine's central hub for all modal UI -- from simple text boxes to full-screen menus like the Bag, Dex, or Monster Roster. It is built around the DialogManager singleton and uses a queue-based architecture with typewriter animation, localization, and branching choices.
DialogManager
Location: Assets/OpenMon/Core/Runtime/UI/Dialogs/DialogManager.cs
Namespace: OpenMon.MCE.UI.Dialogs
DialogManager extends Singleton<DialogManager> and implements IInputReceiver. It provides an entirely static API -- all public methods are called as DialogManager.ShowDialog(...), DialogManager.ShowChoiceMenu(...), etc.
Instantiation via Zenject Factory
The DialogManager is NOT placed in a scene manually. It is instantiated at runtime through 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);
}
The DialogManager.Factory is a GameObjectFactory<DialogManager> that creates the prefab and lets Zenject inject all dependencies.
Injected Dependencies
[Inject] private IInputManager inputManager;
[Inject] private ILocalizer localizer;
[Inject] private Roster playerRoster;
[Inject] private EvolutionManager evolutionManager;
[Inject] private MapSceneLauncher mapSceneLauncher;
Basic Text Dialogs
Showing a Simple Dialog
// Fire-and-forget: shows text and waits for player input
DialogManager.ShowDialog("Dialogs/Welcome");
// With a character speaker
DialogManager.ShowDialog("Dialogs/ProfGreeting", character: professorData);
// With text modifiers (string interpolation via localization)
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });
Show and Wait (Coroutine)
For sequential dialog flows, use the coroutine variant:
yield return DialogManager.ShowDialogAndWait("Dialogs/Moves/Learnt",
localizableModifiers: false,
modifiers: new[] { monsterName, moveName });
This blocks the coroutine until the player dismisses the dialog.
Full ShowDialog Signature
public static void ShowDialog(
string localizationKey,
CharacterData character = null,
MonsterInstance monster = null,
bool acceptInput = true,
float typewriterSpeed = .01f,
float switchToNextAfterSeconds = -1, // -1 = wait for input
bool localizableModifiers = true,
BasicDialogBackground background = BasicDialogBackground.Normal,
HorizontalAlignmentOptions horizontalAlignment = HorizontalAlignmentOptions.Left,
params string[] modifiers
);
Parameters Explained
| Parameter | Purpose |
|---|---|
localizationKey | Key into the localization system (e.g. "Dialogs/Welcome") |
character | If set, shows the character's name in a speaker panel |
monster | If set, shows the monster's name/nickname in the speaker panel |
acceptInput | Whether the player can advance/dismiss the dialog |
typewriterSpeed | Seconds per character for the typewriter animation |
switchToNextAfterSeconds | Auto-advance after N seconds (-1 = manual) |
localizableModifiers | If true, modifier strings are treated as localization keys; if false, used as literal text |
background | Normal or Sign (different visual backgrounds) |
modifiers | Values substituted into {0}, {1}, etc. in the localized string |
DialogText Struct
Each dialog is internally represented as a DialogText readonly struct:
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;
}
The ToString(ILocalizer) method resolves the localization key and substitutes modifiers:
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;
}
Dialog Queue and Flow
DialogManager uses an internal queue to handle multiple sequential dialogs:
ShowDialog("key1") ──► Enqueue DialogText
ShowDialog("key2") ──► Enqueue DialogText
ShowDialog("key3") ──► Enqueue DialogText
Player presses Select:
├─ If typewriter is still animating → SkipTypewriter()
└─ If text is fully shown → NextDialogInternal()
├─ If more pages in current text → advance page
├─ If more dialogs in queue → dequeue and display next
└─ If queue empty → release input, hide dialog, set active = false
Waiting for Dialogs
// WaitUntil the dialog queue is empty
yield return DialogManager.WaitForDialog;
// WaitWhile the typewriter is still animating
yield return DialogManager.WaitForTypewriter;
Auto-Advance
When switchToNextAfterSeconds > 0, the dialog auto-advances after the typewriter finishes and the specified delay elapses:
private IEnumerator NextDialogAfterSeconds(float seconds)
{
yield return new WaitWhile(() => Text.IsTypewriting);
yield return new WaitForSeconds(seconds);
NextDialog();
}
Typewriter Animation
Text is displayed using LocalizedTypeWriterTMP, which wraps MCETextAnimator -- a DOTween-based typewriter:
// MCETextAnimator.ShowText()
textComponent.maxVisibleCharacters = 0;
typewriterTween = DOTween.To(
() => textComponent.maxVisibleCharacters,
x => textComponent.maxVisibleCharacters = x,
totalCharacters,
totalCharacters * waitForNormalChars
).SetEase(Ease.Linear)
.OnComplete(() => onTextShowed.Invoke());
Speed Control
LocalizedTypeWriterTMP.SetTypewriterSpeed(float) sets three timing tiers:
| Tier | Variable | Purpose |
|---|---|---|
| Normal | waitForNormalChars | Base delay per character |
| Middle | waitMiddle | Delay at commas (5x normal by default) |
| Long | waitLong | Delay at periods (20x normal by default) |
Text Effects
MCETextAnimator supports optional vertex effects (disabled by default):
<wave>-- sinusoidal vertical oscillation per character.<shake>-- random offset per character each frame.
Enable with enableVertexEffects = true on the component.
Skipping
Pressing the select button while the typewriter is animating calls SkipTypewriter(), which instantly reveals all characters.
Speaker Panel
When a CharacterData or MonsterInstance is passed, the dialog shows a speaker name panel:
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);
Choice Menus
Choice dialogs use the ChoiceMenuController -- a MenuSelector subclass that supports 1-11 options with auto-sizing:
DialogManager.ShowChoiceMenu(
options: new List<string> { "Common/True", "Common/False" },
callback: choice => { /* 0 = Yes, 1 = No */ },
onBackCallback: () => { /* Treat as cancel */ },
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);
Full ShowChoiceMenu Signature
public static void ShowChoiceMenu(
List<string> options, // Localization keys for each option
Action<int> callback, // Called with the selected index
Transform position = null, // Custom position (null = default)
Action onBackCallback = null, // If set, back button closes and calls this
CharacterData character = null, // Speaker
MonsterInstance monster = null, // Speaker
bool showDialog = false, // Show a text dialog alongside the choice
string localizationKey = "", // Dialog text localization key
bool localizableModifiers = true,
params string[] modifiers
);
How It Works Internally
ChoiceMenuController.SetNumberOfOptions(n)enables the first NChoiceOptionitems and resizes the panel.- Each option's
LocalizedTextMeshProis set to the provided localization key. - The choice menu appears AFTER the typewriter finishes (when
showDialogis true). - On selection, the callback fires and the choice menu auto-hides.
- On back (if allowed),
onBackCallbackfires.
Common Pattern: Yes/No with Coroutine
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)
{
// Player chose Yes
}
Localization Integration
All dialog text goes through the ILocalizer system:
LocalizedTextMeshPro.SetValue(key, localizableModifiers, modifiers)resolves the key and substitutes{0},{1}, etc.- When
localizableModifiersis true, modifier strings are themselves resolved as localization keys. - When false, modifiers are inserted as literal strings (useful for player names, numbers, etc.).
// Localized modifier: both "Dialogs/Caught" and "Monsters/Pikachu" are resolved
DialogManager.ShowDialog("Dialogs/Caught", modifiers: "Monsters/Pikachu");
// Literal modifier: "500" is inserted as-is
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });
Complex Dialogs (Managed Screens)
Beyond basic text, DialogManager owns references to every major screen and provides static launcher methods:
| Method | Opens |
|---|---|
ShowGameMenu(...) | In-game pause menu |
ShowPlayerRosterMenu(...) | Monster party screen (with choosing/swap modes) |
ShowBag(...) | Inventory with tabs (battle mode, selling mode, selection mode) |
ShowDexScreen(...) | Full dex browser |
ShowSingleMonsterDexScreen(...) | Single monster dex entry |
ShowShop(...) | Buy/sell shop |
ShowOptionsMenu() | Settings screen |
ShowProfileScreen(...) | Trainer card |
ShowQuestsScreen(...) | Quest log |
RequestTextInput(...) | Keyboard input dialog (naming, etc.) |
ShowMoveTutorDialog(...) | Move tutor selection |
ShowMoveLearnPanel(...) | Move learn/replace flow |
ShowNewMonsterDialog(...) | New monster nickname + storage flow |
ShowXPGainDialogLearnMoveAndEvolveForSingleMonsterInPlayerRoster(...) | Full XP gain -> level up -> move learn -> evolution pipeline |
CloseMenus() | Closes ALL open screens in sequence |
Notifications
DialogManager.Notifications exposes the child NotificationManager for toast-style notifications:
DialogManager.Notifications.StopAllNotifications();
Loading Indicator
DialogManager.ShowLoadingIcon(true);
DialogManager.ShowLoadingText("Loading/MapData");
// ... async work ...
DialogManager.ClearLoadingText();
DialogManager.ShowLoadingIcon(false);
Using Dialogs from CommandGraph
NPC behavior is authored in CommandGraph (a node-based visual scripting system). Dialog nodes internally call the same DialogManager.ShowDialog(...) and DialogManager.ShowChoiceMenu(...) static API. The CommandGraph nodes handle:
- Chaining multiple dialog texts in sequence
- Branching on choice results
- Setting the speaking character per node
- Waiting for dialog completion before advancing the graph
From code, you interact with the same static methods -- there is no difference in the underlying API.
Background Variants
BasicDialogBackground is an enum nested inside DialogManager (i.e. DialogManager.BasicDialogBackground):
// Defined inside DialogManager
public enum BasicDialogBackground
{
Normal, // Standard dialog box
Sign // Sign post / notice board style
}
Pass background: DialogManager.BasicDialogBackground.Sign to ShowDialog() to use the sign variant. The DialogManager switches between serialized background HidableUiElement references stored in a SerializableDictionary<BasicDialogBackground, HidableUiElement>.