El objetivo#
Un NPC con el que hablas: aparece un panel, el texto se escribe letra a letra, y avanzas por las líneas pulsando una tecla. Con la primera pulsación completas la línea de golpe (impaciencia), con la segunda pasas a la siguiente.
Paso 1 — El diálogo como dato#
Lo más simple, un array serializable; para conversaciones grandes, un ScriptableObject por conversación:
[System.Serializable]
public class Linea
{
public string interlocutor;
[TextArea] public string texto;
}Paso 2 — El gestor de diálogo#
using System.Collections;
using UnityEngine;
using TMPro;
public class DialogoUI : MonoBehaviour
{
[SerializeField] private GameObject panel;
[SerializeField] private TextMeshProUGUI textoNombre;
[SerializeField] private TextMeshProUGUI textoLinea;
[SerializeField] private float velocidad = 0.03f; // segundos por letra
private Linea[] lineas;
private int indice;
private bool escribiendo;
private Coroutine rutina;
public void Empezar(Linea[] lineas)
{
this.lineas = lineas;
indice = 0;
panel.SetActive(true);
MostrarLinea();
}
void Update()
{
if (!panel.activeSelf) return;
if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
{
if (escribiendo) CompletarLinea(); // 1.ª pulsación: completa
else Avanzar(); // 2.ª: siguiente línea
}
}
void MostrarLinea()
{
Linea l = lineas[indice];
textoNombre.text = l.interlocutor;
rutina = StartCoroutine(Escribir(l.texto));
}
IEnumerator Escribir(string texto)
{
escribiendo = true;
textoLinea.text = "";
foreach (char c in texto)
{
textoLinea.text += c;
yield return new WaitForSeconds(velocidad);
}
escribiendo = false;
}
void CompletarLinea()
{
if (rutina != null) StopCoroutine(rutina);
textoLinea.text = lineas[indice].texto; // muestra todo de golpe
escribiendo = false;
}
void Avanzar()
{
indice++;
if (indice < lineas.Length) MostrarLinea();
else Cerrar();
}
void Cerrar() => panel.SetActive(false);
}Paso 3 — Disparar el diálogo desde un NPC#
Con un trigger al acercarte, o al pulsar "usar":
public class NPC : MonoBehaviour
{
[SerializeField] private DialogoUI dialogoUI;
[SerializeField] private Linea[] conversacion;
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
dialogoUI.Empezar(conversacion);
}
}Poder saltar la animación letra a letra con la primera pulsación es lo que separa un diálogo agradable de uno frustrante. Nadie quiere esperar a que se escriba un párrafo largo. Por eso la primera pulsación completa la línea y la segunda avanza.
Extras habituales#
- Tags de formato: TextMeshPro admite etiquetas (
<color>,<b>,<size>) dentro del texto para dar énfasis. - Sonido de escritura: un "bip" corto por cada pocas letras (típico de los RPG).
- Retratos: mostrar la cara del interlocutor.
- Ramas de decisión: opciones que llevan a distintas líneas (aquí un ScriptableObject con referencias entre nodos ayuda mucho).
- Pausar el juego durante el diálogo si procede (ver menú de pausa).
Errores frecuentes#
- No poder saltar el efecto de escritura (frustra al jugador).
- No parar la corrutina anterior al avanzar y ver letras "mezclándose".
- Leer el avance en
FixedUpdateen vez deUpdate. - Bloquear el movimiento del jugador y olvidar reactivarlo al cerrar el diálogo.
Resumen#
Define las líneas como datos, muéstralas en un panel con TextMeshPro y escríbelas letra a letra con una corrutina. Permite completar la línea con la primera pulsación y avanzar con la siguiente. Dispáralo desde un NPC con un trigger. Amplíalo con retratos, sonido, formato y ramas de decisión (con ScriptableObjects).