Quest dan dialog adalah tulang punggung narasi dalam game RPG dan petualangan di Roblox. Tanpa arsitektur yang kokoh, pemain yang putus koneksi di tengah cutscene dapat mengalami reset data permanen.
Dengan menerapkan graf DAG untuk prasyarat quest, penanganan event bus yang terpisah, pohon dialog kondisional, serta kompresi bitfield di DataStore, pengembang dapat menyajikan pengalaman bermain yang mulus.
1. Quest sebagai Directed Acyclic Graph (DAG)
Menyusun alur cerita kompleks tanpa kebuntuan sirkular:
- Pohon Prasyarat: Quest dimodelkan sebagai simpul graf yang hanya aktif jika semua simpul sebelumnya telah diselesaikan.
- Jalur Cerita Eksklusif: Memilih faksi tertentu akan menonaktifkan cabang cerita lawan secara otomatis tanpa konflik logika.
- Pemeriksaan Topologis: Algoritma verifikasi otomatis saat kompilasi memastikan tidak ada dependensi melingkar yang mustahil diselesaikan.
2. Finite State Machine (FSM) Siklus Hidup Quest
Penerapan transisi status yang ketat untuk mencegah eksploitasi:
- 6 Status Baku:
Terkunci ➔ Tersedia ➔ Aktif ➔ Selesai ➔ Diambil(dengan statusGagal). - Otoritas Penuh Server: Klien tidak bisa menyatakan quest selesai; server memvalidasi tiap aksi sebelum mengubah status.
- Pembaruan UI Optimis: Begitu server mengonfirmasi, HUD klien langsung memutar animasi dan suara reward.
-- Pengelola Quest 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. Pelacakan Sasaran Berbasis Event Bus
Memisahkan aksi game dari kode validasi misi:
- Signal Bus Terpusat: Daripada menulis kode quest di skrip monster, cukup pancarkan sinyal global
EntityKilled. - Penyaringan Efisien: Pengelola quest hanya merespons event yang relevan dengan misi aktif pemain, meniadakan beban CPU berkala.
- Sasaran Majemuk: Melacak beberapa tugas sekaligus (mengumpulkan item dan mengalahkan musuh) dalam struktur data terpadu.
4. Pohon Dialog Interaktif & Logika Pilihan
Percakapan NPC yang memiliki dampak nyata pada jalannya permainan:
- Struktur Simpul Dialog: Dialog disusun dalam simpul berisi teks pembicara, sudut kamera, dan daftar pilihan jawaban.
- Pilihan Bersyarat: Opsi dialog tertentu hanya muncul jika pemain membawa item tertentu atau memiliki reputasi memadai.
- Pemicu Aksi Nyata: Menjalankan cutscene, memicu pertarungan bos, atau memberikan hadiah langsung dari simpul dialog.
5. Kompresi Penyimpanan DataStore dengan ProfileService
Menyimpan ratusan riwayat quest dengan ukuran memori sangat kecil:
- Pengemasan Bitfield: Quest yang telah selesai disimpan sebagai bit individual dalam angka integer (32 quest per 4 byte).
- Penyimpanan Status Aktif Saja: Hanya menyimpan ID misi yang sedang berjalan dan hitungannya, membuang data statis.
- Migrasi Skema Cerita: Hook pembaruan di ProfileService menjaga progres pemain tetap aman saat patch cerita mengubah quest.
Frequently Asked Questions
Mengapa quest harus dimodelkan sebagai DAG?
Untuk mencegah kebuntuan logika di mana dua misi saling membutuhkan satu sama lain sehingga tidak bisa diselesaikan.
Bagaimana event bus menghemat kinerja game?
Menghilangkan loop pengecekan tiap frame; sistem hanya bereaksi saat pemain melakukan tindakan yang relevan.
Bagaimana cara mencegah hilangnya data saat koneksi terputus?
Gunakan ProfileService dengan session locking untuk mencatat perubahan secara instan di memori dan penyimpanan.
Bagaimana membagikan reward dialog secara aman?
Tampilan dialog dijalankan di klien, tetapi pemberian hadiah diverifikasi dan dieksekusi secara ketat oleh server.