Las misiones y los diálogos sostienen la narrativa de cualquier juego de rol en Roblox. Un diseño débil colapsa ante desconexiones inesperadas o saltos de secuencia.
Mediante grafos DAG para prerrequisitos, buses de señales desacoplados, árboles de conversación condicionales y empaquetado de bits en DataStore, se logran sistemas escalables y libres de errores.
1. Misiones como Grafos Acíclicos Dirigidos (DAGs)
Diseño de narrativas complejas sin bucles infinitos:
- Árboles de Prerrequisitos: Las misiones se representan como nodos que solo se desbloquean cuando todos los nodos predecesores están completados.
- Ramas Excluyentes: Tomar partido por una facción desactiva las rutas rivales sin provocar incoherencias en la trama.
- Ordenación Topológica: Algoritmos automáticos verifican la base de datos para impedir dependencias circulares imposibles de superar.
2. Máquina de Estados Finitos (FSM) para el Ciclo de Vida
Control estricto de transiciones para bloquear trampas:
- Los 6 Estados Canónicos:
Bloqueada ➔ Disponible ➔ Activa ➔ Completada ➔ Reclamada(con estadoFallidade respaldo). - Autoridad en el Servidor: El cliente nunca valida una misión; solo envía acciones del jugador y el servidor certifica el cumplimiento.
- Actualización Optimista de UI: Al confirmarse el avance, el cliente despliega animaciones fluidas y efectos de sonido satisfactorios.
-- Gestor de Misiones FSM
local QuestManager = {}
QuestManager.__index = QuestManager
local QuestDatabase = require(game.ReplicatedStorage.QuestDatabase)
function QuestManager.new(player, profileData)
local self = setmetatable({}, QuestManager)
self.Player = player
self.ActiveQuests = profileData.ActiveQuests or {}
self.CompletedQuests = profileData.CompletedQuests or {}
return self
end
function QuestManager:CanStartQuest(questId)
local data = QuestDatabase[questId]
if not data then return false end
if self.CompletedQuests[questId] or self.ActiveQuests[questId] then return false end
for _, prereqId in ipairs(data.Prerequisites) do
if not self.CompletedQuests[prereqId] then return false end
end
return true
end
function QuestManager:AcceptQuest(questId)
if not self:CanStartQuest(questId) then return false end
self.ActiveQuests[questId] = {
Step = 1,
Counters = table.create(#QuestDatabase[questId].Steps[1].Objectives, 0)
}
return true
end
return QuestManager
3. Seguimiento por Bus de Eventos Desacoplado
Separación total entre la lógica del juego y la verificación de objetivos:
- Bus de Eventos Global: En lugar de vincular código de misión al morir un enemigo, se emite una señal genérica
EntityKilled. - Filtrado Selectivo: El gestor de misiones solo atiende eventos relacionados con tareas activas, ahorrando ciclos de CPU.
- Objetivos Combinados: Registro simultáneo de metas múltiples (derrotar jefes y recolectar objetos) en estructuras de datos claras.
4. Árboles de Diálogo con Decisiones Relevantes
Conversaciones interactivas con consecuencias en el juego:
- Estructura por Nodos: Los NPCs hablan a través de nodos enlazados con texto, encuadres de cámara y opciones de respuesta.
- Opciones Condicionales: Ciertas respuestas solo aparecen si el jugador posee objetos clave o prestigio suficiente.
- Acciones en Diálogo: Disparo de cinemáticas, entregas de objetos o inicio de combates directamente desde los nodos de charla.
5. Guardado Ultracompacto en DataStore con ProfileService
Almacenamiento de cientos de misiones consumiendo un espacio insignificante:
- Empaquetado de Bits: Almacena misiones completadas como bits individuales dentro de enteros, guardando 32 misiones en solo 4 bytes.
- Guardado de Estados Activos: Solo se persisten los IDs de tareas activas y sus contadores, descartando metadatos estáticos.
- Migración de Esquema: Uso de versiones en ProfileService para adaptar partidas guardadas cuando una actualización modifica objetivos.
Frequently Asked Questions
¿Por qué usar un Grafo Acíclico Dirigido (DAG)?
Porque garantiza matemáticamente que no existan bucles infinitos de prerrequisitos que impidan avanzar en el juego.
¿Qué ventaja tiene el bus de eventos en las misiones?
Evita comprobar el estado de los objetivos en cada fotograma; el gestor solo actúa cuando ocurre una acción relevante.
¿Cómo proteger el progreso ante caídas del servidor?
Utilizando ProfileService con bloqueo de sesión para sincronizar contadores atómicamente en memoria y disco.
¿Cómo entregar recompensas seguras desde un diálogo?
La interfaz se muestra en cliente, pero la concesión de recompensas la valida el servidor mediante una RemoteFunction.