Missões e diálogos são o coração narrativo dos RPGs no Roblox. Sistemas frágeis falham quando jogadores desconectam no meio de animações ou cancelam tarefas.
Unindo grafos DAG para controlar pré-requisitos, buses de eventos para separar sistemas, árvores de diálogo com opções condicionais e compressão por bitfield, cria-se uma estrutura confiável.
1. Missões como Grafos Acíclicos Dirigidos (DAGs)
Construindo tramas complexas sem travamentos circulares:
- Árvores de Pré-Requisitos: Missões são modeladas como nós de um grafo que só abrem quando todas as etapas antecedentes foram concluídas.
- Caminhos Exclusivos: Filiar-se a uma facção desativa ramos rivais sem gerar inconsistências na história.
- Ordenação Topológica: Verificações automáticas na base de dados impedem a criação acidental de dependências infinitas.
2. Máquina de Estados Finitos (FSM) no Ciclo das Missões
Transições regulamentadas para impedir fraudes:
- Os 6 Estados Padrão:
Bloqueada ➔ Disponível ➔ Ativa ➔ Concluída ➔ Resgatada(com estado deFalha). - Validação no Servidor: O jogador nunca aprova a própria missão; o servidor avalia cada ação antes de consolidar o progresso.
- Interface Otimista: Após a validação do servidor, a tela exibe animações fluidas e efeitos sonoros satisfatórios.
-- Gerenciador de Missões 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. Rastreamento Desacoplado via Bus de Eventos
Separando a jogabilidade da verificação de tarefas:
- Bus Global de Sinais: Em vez de colocar código de missão nos monstros, emite-se um sinal genérico
EntityKilled. - Filtro Econômico: O gerenciador processa somente eventos pertinentes às missões ativas, poupando ciclos de CPU.
- Múltiplos Objetivos: Coleta de ervas e eliminação de chefes monitoradas conjuntamente em tabelas dedicadas.
4. Árvores de Diálogo com Tomada de Decisão
Conversas dinâmicas com impacto direto no jogo:
- Estrutura por Nós: Falas organizadas em blocos com texto, ângulos de câmera e opções de resposta.
- Opções Condicionais: Respostas exclusivas liberadas apenas com itens específicos ou boa reputação.
- Ações no Diálogo: Disparo de cinemáticas, entrega de recompensas ou batalhas direto das conversas.
5. Salvamento Compacto no DataStore com ProfileService
Guardando centenas de missões concluídas com peso quase nulo:
- Compactação por Bitfield: Missões concluídas salvas como bits dentro de inteiros (32 missões em 4 bytes).
- Registro das Missões Ativas: Salva apenas os IDs e contadores em andamento, descartando dados repetitivos.
- Migração Segura: Ganchos de versão no ProfileService ajustam o progresso caso uma atualização altere a campanha.
Frequently Asked Questions
Por que estruturar missões como um grafo DAG?
Para assegurar que nenhuma missão dependa de si mesma em um ciclo infinito, garantindo progressão fluida.
Como o bus de eventos melhora o rendimento?
Elimina verificações constantes por quadro; o sistema reage apenas aos acontecimentos de interesse.
Como evitar perda de progresso em quedas de sinal?
Utilizando o ProfileService com bloqueio de sessão para sincronizar dados em memória e disco com segurança.
Como entregar itens com segurança através de diálogos?
A interface roda no cliente, mas a atribuição do prêmio é checada e realizada exclusivamente no servidor.