Sistema de Diálogos
El sistema de diálogos es el centro neurálgico del motor para toda la UI modal — desde simples cuadros de texto hasta menús de pantalla completa como la Mochila, el Dex o el Roster de Monstruos. Está construido alrededor del singleton DialogManager y utiliza una arquitectura basada en colas con animación de máquina de escribir, localización y opciones ramificadas.
DialogManager
Ubicación: Assets/OpenMon/Core/Runtime/UI/Dialogs/DialogManager.cs
Namespace: OpenMon.MCE.UI.Dialogs
DialogManager extiende Singleton<DialogManager> e implementa IInputReceiver. Proporciona una API completamente estática — todos los métodos públicos se llaman como DialogManager.ShowDialog(...), DialogManager.ShowChoiceMenu(...), etc.
Instanciación mediante Fábrica de Zenject
El DialogManager NO se coloca manualmente en una escena. Se instancia en tiempo de ejecución a través de 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);
}
La DialogManager.Factory es un GameObjectFactory<DialogManager> que crea el prefab y permite a Zenject inyectar todas las dependencias.
Dependencias Inyectadas
[Inject] private IInputManager inputManager;
[Inject] private ILocalizer localizer;
[Inject] private Roster playerRoster;
[Inject] private EvolutionManager evolutionManager;
[Inject] private MapSceneLauncher mapSceneLauncher;
Diálogos de Texto Básicos
Mostrando un Diálogo Simple
// Fire-and-forget: muestra texto y espera la entrada del jugador
DialogManager.ShowDialog("Dialogs/Welcome");
// Con un personaje hablante
DialogManager.ShowDialog("Dialogs/ProfGreeting", character: professorData);
// Con modificadores de texto (interpolación de cadenas mediante localización)
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });
Mostrar y Esperar (Corrutina)
Para flujos de diálogo secuenciales, usa la variante de corrutina:
yield return DialogManager.ShowDialogAndWait("Dialogs/Moves/Learnt",
localizableModifiers: false,
modifiers: new[] { monsterName, moveName });
Esto bloquea la corrutina hasta que el jugador descarta el diálogo.
Firma Completa de ShowDialog
public static void ShowDialog(
string localizationKey,
CharacterData character = null,
MonsterInstance monster = null,
bool acceptInput = true,
float typewriterSpeed = .01f,
float switchToNextAfterSeconds = -1, // -1 = esperar entrada
bool localizableModifiers = true,
BasicDialogBackground background = BasicDialogBackground.Normal,
HorizontalAlignmentOptions horizontalAlignment = HorizontalAlignmentOptions.Left,
params string[] modifiers
);
Parámetros Explicados
| Parámetro | Propósito |
|---|---|
localizationKey | Clave del sistema de localización (ej. "Dialogs/Welcome") |
character | Si se establece, muestra el nombre del personaje en un panel de hablante |
monster | Si se establece, muestra el nombre/apodo del monstruo en el panel de hablante |
acceptInput | Si el jugador puede avanzar/descartar el diálogo |
typewriterSpeed | Segundos por carácter para la animación de máquina de escribir |
switchToNextAfterSeconds | Auto-avance después de N segundos (-1 = manual) |
localizableModifiers | Si es true, las cadenas modificadoras se tratan como claves de localización; si es false, se usan como texto literal |
background | Normal o Sign (diferentes fondos visuales) |
modifiers | Valores sustituidos en {0}, {1}, etc. en la cadena localizada |
Struct DialogText
Cada diálogo se representa internamente como un struct readonly 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;
}
El método ToString(ILocalizer) resuelve la clave de localización y sustituye los modificadores:
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;
}
Cola de Diálogos y Flujo
DialogManager utiliza una cola interna para manejar múltiples diálogos secuenciales:
ShowDialog("key1") ──► Encolar DialogText
ShowDialog("key2") ──► Encolar DialogText
ShowDialog("key3") ──► Encolar DialogText
Player presses Select:
├─ Si la máquina de escribir aún está animando → SkipTypewriter()
└─ Si el texto está completamente mostrado → NextDialogInternal()
├─ Si hay más páginas en el texto actual → avanzar página
├─ Si hay más diálogos en la cola → desencolar y mostrar el siguiente
└─ Si la cola está vacía → liberar entrada, ocultar diálogo, establecer activo = false
Esperando Diálogos
// WaitUntil la cola de diálogos está vacía
yield return DialogManager.WaitForDialog;
// WaitWhile la máquina de escribir aún está animando
yield return DialogManager.WaitForTypewriter;
Auto-Avance
Cuando switchToNextAfterSeconds > 0, el diálogo avanza automáticamente después de que la máquina de escribir termina y transcurre el retraso especificado:
private IEnumerator NextDialogAfterSeconds(float seconds)
{
yield return new WaitWhile(() => Text.IsTypewriting);
yield return new WaitForSeconds(seconds);
NextDialog();
}
Animación de Máquina de Escribir
El texto se muestra usando LocalizedTypeWriterTMP, que envuelve MCETextAnimator — una
máquina de escribir basada en tweening. El tipo DOTween de abajo es la fachada propia de MCE
OpenMon.Foundation.Tweening.DOTween (incluida con el motor), no el asset de Demigiant:
// MCETextAnimator.ShowText()
textComponent.maxVisibleCharacters = 0;
typewriterTween = DOTween.To(
() => textComponent.maxVisibleCharacters,
x => textComponent.maxVisibleCharacters = x,
totalCharacters,
totalCharacters * waitForNormalChars
).SetEase(Ease.Linear)
.OnComplete(() => onTextShowed.Invoke());
Control de Velocidad
LocalizedTypeWriterTMP.SetTypewriterSpeed(float) establece tres niveles de temporización:
| Nivel | Variable | Propósito |
|---|---|---|
| Normal | waitForNormalChars | Retraso base por carácter |
| Medio | waitMiddle | Retraso en comas (5x normal por defecto) |
| Largo | waitLong | Retraso en puntos (20x normal por defecto) |
Efectos de Texto
MCETextAnimator soporta efectos de vértice opcionales (deshabilitados por defecto):
<wave>— Oscilación sinusoidal vertical por carácter.<shake>— Desplazamiento aleatorio por carácter cada frame.
Habilitar con enableVertexEffects = true en el componente.
Omitir
Presionar el botón de selección mientras la máquina de escribir está animando llama a SkipTypewriter(), que revela instantáneamente todos los caracteres.
Panel de Hablante
Cuando se pasa un CharacterData o MonsterInstance, el diálogo muestra un panel de nombre del hablante:
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);
Menús de Opciones
Los diálogos de opciones usan ChoiceMenuController — una subclase de MenuSelector que soporta de 1 a 11 opciones con auto-dimensionado:
DialogManager.ShowChoiceMenu(
options: new List<string> { "Common/True", "Common/False" },
callback: choice => { /* 0 = Sí, 1 = No */ },
onBackCallback: () => { /* Tratar como cancelar */ },
showDialog: true,
localizationKey: "Menu/Save/Confirmation"
);
Firma Completa de ShowChoiceMenu
public static void ShowChoiceMenu(
List<string> options, // Claves de localización para cada opción
Action<int> callback, // Llamado con el índice seleccionado
Transform position = null, // Posición personalizada (null = predeterminada)
Action onBackCallback = null, // Si se establece, el botón de retroceso cierra y llama a esto
CharacterData character = null, // Hablante
MonsterInstance monster = null, // Hablante
bool showDialog = false, // Mostrar un diálogo de texto junto a la opción
string localizationKey = "", // Clave de localización del texto del diálogo
bool localizableModifiers = true,
params string[] modifiers
);
Cómo Funciona Internamente
ChoiceMenuController.SetNumberOfOptions(n)habilita los primeros N elementosChoiceOptiony redimensiona el panel.- El
LocalizedTextMeshProde cada opción se establece con la clave de localización proporcionada. - El menú de opciones aparece DESPUÉS de que la máquina de escribir termina (cuando
showDialoges true). - Al seleccionar, se dispara el callback y el menú de opciones se auto-oculta.
- Al retroceder (si está permitido), se dispara
onBackCallback.
Patrón Común: Sí/No con Corrutina
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)
{
// El jugador eligió Sí
}
Integración de Localización
Todo el texto de los diálogos pasa por el sistema ILocalizer:
LocalizedTextMeshPro.SetValue(key, localizableModifiers, modifiers)resuelve la clave y sustituye{0},{1}, etc.- Cuando
localizableModifierses true, las cadenas modificadoras se resuelven ellas mismas como claves de localización. - Cuando es false, los modificadores se insertan como cadenas literales (útil para nombres de jugador, números, etc.).
// Modificador localizable: tanto "Dialogs/Caught" como "Monsters/Pikachu" se resuelven
DialogManager.ShowDialog("Dialogs/Caught", modifiers: "Monsters/Pikachu");
// Modificador literal: "500" se inserta tal cual
DialogManager.ShowDialog("Dialogs/XPGain/Single",
localizableModifiers: false,
modifiers: new[] { monsterName, "500" });
Diálogos Complejos (Pantallas Gestionadas)
Más allá del texto básico, DialogManager posee referencias a cada pantalla principal y proporciona métodos de lanzamiento estáticos:
| Método | Abre |
|---|---|
ShowGameMenu(...) | Menú de pausa en juego |
ShowPlayerRosterMenu(...) | Pantalla del equipo de monstruos (con modos elegir/intercambiar) |
ShowBag(...) | Inventario con pestañas (modo batalla, modo venta, modo selección) |
ShowDexScreen(...) | Navegador completo del dex |
ShowSingleMonsterDexScreen(...) | Entrada de un solo monstruo en el dex |
ShowShop(...) | Tienda comprar/vender |
ShowOptionsMenu() | Pantalla de configuración |
ShowProfileScreen(...) | Tarjeta de entrenador |
ShowQuestsScreen(...) | Registro de misiones |
RequestTextInput(...) | Diálogo de entrada de teclado (poner nombre, etc.) |
ShowMoveTutorDialog(...) | Selección de tutor de movimientos |
ShowMoveLearnPanel(...) | Flujo de aprender/reemplazar movimiento |
ShowNewMonsterDialog(...) | Flujo de apodo + almacenamiento de nuevo monstruo |
ShowXPGainDialogLearnMoveAndEvolveForSingleMonsterInPlayerRoster(...) | Pipeline completo de ganancia de XP -> subida de nivel -> aprender movimiento -> evolución |
CloseMenus() | Cierra TODAS las pantallas abiertas en secuencia |
Notificaciones
DialogManager.Notifications expone el NotificationManager hijo para notificaciones estilo toast:
DialogManager.Notifications.StopAllNotifications();
Indicador de Carga
DialogManager.ShowLoadingIcon(true);
DialogManager.ShowLoadingText("Loading/MapData");
// ... trabajo asíncrono ...
DialogManager.ClearLoadingText();
DialogManager.ShowLoadingIcon(false);
Usando Diálogos desde CommandGraph
El comportamiento de NPC se crea en CommandGraph (un sistema de scripting visual basado en nodos). Los nodos de diálogo internamente llaman a la misma API estática DialogManager.ShowDialog(...) y DialogManager.ShowChoiceMenu(...). Los nodos de CommandGraph manejan:
- Encadenar múltiples textos de diálogo en secuencia
- Ramificar según los resultados de las opciones
- Establecer el personaje hablante por nodo
- Esperar a que el diálogo termine antes de avanzar el grafo
Desde código, interactúas con los mismos métodos estáticos — no hay diferencia en la API subyacente.
Variantes de Fondo
BasicDialogBackground es un enum anidado dentro de DialogManager (es decir, DialogManager.BasicDialogBackground):
// Definido dentro de DialogManager
public enum BasicDialogBackground
{
Normal, // Cuadro de diálogo estándar
Sign // Estilo de cartel / tablón de anuncios
}
Pasa background: DialogManager.BasicDialogBackground.Sign a ShowDialog() para usar la variante de cartel. El DialogManager cambia entre referencias de HidableUiElement de fondo serializadas almacenadas en un SerializableDictionary<BasicDialogBackground, HidableUiElement>.