Saltar al contenido principal

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ámetroPropósito
localizationKeyClave del sistema de localización (ej. "Dialogs/Welcome")
characterSi se establece, muestra el nombre del personaje en un panel de hablante
monsterSi se establece, muestra el nombre/apodo del monstruo en el panel de hablante
acceptInputSi el jugador puede avanzar/descartar el diálogo
typewriterSpeedSegundos por carácter para la animación de máquina de escribir
switchToNextAfterSecondsAuto-avance después de N segundos (-1 = manual)
localizableModifiersSi es true, las cadenas modificadoras se tratan como claves de localización; si es false, se usan como texto literal
backgroundNormal o Sign (diferentes fondos visuales)
modifiersValores 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:

NivelVariablePropósito
NormalwaitForNormalCharsRetraso base por carácter
MediowaitMiddleRetraso en comas (5x normal por defecto)
LargowaitLongRetraso 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​

  1. ChoiceMenuController.SetNumberOfOptions(n) habilita los primeros N elementos ChoiceOption y redimensiona el panel.
  2. El LocalizedTextMeshPro de cada opción se establece con la clave de localización proporcionada.
  3. El menú de opciones aparece DESPUÉS de que la máquina de escribir termina (cuando showDialog es true).
  4. Al seleccionar, se dispara el callback y el menú de opciones se auto-oculta.
  5. 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 localizableModifiers es 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étodoAbre
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>.