El plan#
Una misión tiene objetivos ("mata 5 goblins", "recoge la llave", "llega al pueblo") que se van cumpliendo, y al completarse da una recompensa. La clave para que sea mantenible: el sistema de misiones escucha eventos del juego, en vez de que cada enemigo o llave conozca las misiones.
Paso 1 — Definir la misión como dato#
using UnityEngine;
public enum TipoObjetivo { Matar, Recoger, Llegar }
[System.Serializable]
public class Objetivo
{
public TipoObjetivo tipo;
public string idDiana; // "goblin", "llave_dorada", "pueblo"
public int cantidad = 1;
[HideInInspector] public int progreso; // en runtime
public bool Completado => progreso >= cantidad;
}
[CreateAssetMenu(fileName = "Mision", menuName = "Quests/Mision")]
public class MisionData : ScriptableObject
{
public string titulo;
[TextArea] public string descripcion;
public Objetivo[] objetivos;
public int recompensaOro = 100;
}El progreso es estado de partida, no del diseño. Si lo modificas en el ScriptableObject en runtime, en el editor persiste entre pruebas y en una build no se guarda (ver ScriptableObject). Para algo serio, copia los datos de la misión a una clase de runtime, o guarda el progreso aparte con JSON. Aquí lo dejamos en el objetivo por simplicidad didáctica.
Paso 2 — El gestor de misiones#
Escucha un evento global "ha pasado algo" y actualiza los objetivos que encajen:
using System;
using System.Collections.Generic;
using UnityEngine;
public class GestorMisiones : MonoBehaviour
{
public static GestorMisiones Instance { get; private set; }
private List<MisionData> activas = new List<MisionData>();
public event Action<MisionData> AlActualizar;
public event Action<MisionData> AlCompletar;
void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; }
public void Aceptar(MisionData mision)
{
foreach (var o in mision.objetivos) o.progreso = 0;
activas.Add(mision);
AlActualizar?.Invoke(mision);
}
// Lo llama el JUEGO cuando ocurre algo: matar, recoger, llegar
public void Notificar(TipoObjetivo tipo, string id, int cantidad = 1)
{
foreach (var mision in activas.ToArray())
{
bool cambio = false;
foreach (var o in mision.objetivos)
{
if (o.tipo == tipo && o.idDiana == id && !o.Completado)
{
o.progreso += cantidad;
cambio = true;
}
}
if (cambio)
{
AlActualizar?.Invoke(mision);
if (Array.TrueForAll(mision.objetivos, o => o.Completado))
Completar(mision);
}
}
}
void Completar(MisionData mision)
{
activas.Remove(mision);
// dar recompensa (oro, ítems...)
AlCompletar?.Invoke(mision);
}
}Paso 3 — Que el juego avise (sin conocer las misiones)#
Un enemigo al morir, o una llave al recogerse, solo notifican lo que hicieron. No saben si hay una misión que lo necesita:
// Al morir un goblin:
GestorMisiones.Instance.Notificar(TipoObjetivo.Matar, "goblin");
// Al recoger la llave:
GestorMisiones.Instance.Notificar(TipoObjetivo.Recoger, "llave_dorada");El enemigo no conoce las misiones; la misión no conoce a los enemigos. Solo hay un canal de "ha pasado X". Puedes añadir 100 misiones nuevas sin tocar el código de los enemigos, y un mismo suceso (matar un goblin) puede contar para varias misiones a la vez. Es el poder de los eventos aplicado a un sistema completo.
Paso 4 — La UI del diario#
Un panel se suscribe a AlActualizar/AlCompletar y redibuja la lista de misiones activas con su progreso ("Goblins: 3/5"). Desacoplada, como siempre.
Errores frecuentes#
- Que cada enemigo/objeto conozca las misiones (acoplamiento imposible de mantener).
- Guardar el progreso en el ScriptableObject y que se corrompa entre partidas.
- No copiar
activasal iterar si una misión puede completarse (modificar la lista dentro del foreach); aquí usamos.ToArray()para evitarlo. - Olvidar persistir el progreso si quieres continuar la partida.
Resumen#
Define las misiones como ScriptableObjects con objetivos (tipo + diana + cantidad). Un GestorMisiones mantiene las activas y expone un método Notificar al que el juego avisa cuando pasa algo (matar, recoger, llegar), sin que enemigos ni objetos conozcan las misiones. La UI escucha los eventos. Recuerda persistir el progreso aparte (no en el asset) con JSON.