Skip to main content

📜 Making a Quest Log

For the quest log let's create 2 local scripts. One for the HUD and nother one for the quest log! Feel free to copy paste the example.

👀 Example

--Hud.lua
--!strict
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RoQuest = require(ReplicatedStorage.RoQuest).Client

local localPlayer: Player = Players.LocalPlayer
local playerGui = localPlayer:WaitForChild("PlayerGui")
local questLog = playerGui:WaitForChild("QuestLog")
local hud = playerGui:WaitForChild("HUD")

local screens = {
QuestLog = questLog.Container,
}

local Hud = {}
Hud.currentScreen = ""

function Hud:DisableScreen(screenName: string)
if not screens[screenName] then
return
end

screens[screenName].Visible = false
self.currentScreen = ""
end

function Hud:EnableScreen(screenName: string)
local currentScreen = self.currentScreen
self:DisableScreen(self.currentScreen)

if screenName == currentScreen then
return
end

screens[screenName].Visible = true
self.currentScreen = screenName
end

RoQuest.OnStart():andThen(function()
for _, instance: Instance in hud.Container:GetChildren() do
if not instance:IsA("ImageButton") then
continue
end

(instance :: ImageButton).Activated:Connect(function()
Hud:EnableScreen(instance.Name)
end)
end

end)

--QuestLog.lua

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local RoQuest = require(ReplicatedStorage.RoQuest).Client
local Red = require(ReplicatedStorage.RoQuest.Vendor.Red).Client

type Quest = RoQuest.Quest

local DEFAULT_COLOR: Color3 = Color3.fromRGB(140, 140, 140)
local ACTIVE_COLOR: Color3 = Color3.fromRGB(255, 255, 127)

local localPlayer: Player = Players.LocalPlayer
local playerGui: PlayerGui = localPlayer:WaitForChild("PlayerGui")
local questLog: ScreenGui = playerGui:WaitForChild("QuestLog")
local template: Frame = questLog.Container.Unavailable.Template:Clone()
questLog.Container.Unavailable.Template:Destroy() -- Delete the template after we cached a clone

local QuestLog = {}
QuestLog.screens = {} :: {[string]: ScrollingFrame}
QuestLog.buttons = {} :: {[string]: GuiButton}
QuestLog.currentScreen = "" :: string

function QuestLog:UpdateButtonColor()
for buttonName: string, button: GuiButton in self.buttons do
button.BackgroundColor3 = buttonName == self.currentScreen and ACTIVE_COLOR or DEFAULT_COLOR
end
end

function QuestLog:SetScreen(name: string)
if self.currentScreen == name then
return
end

local currentScrollingFrame: ScrollingFrame? = self.screens[self.currentScreen]
local newScrollingFrame: ScrollingFrame? = self.screens[name]

if currentScrollingFrame then
currentScrollingFrame.Visible = false
end

if newScrollingFrame then
newScrollingFrame.Visible = true
end

self.currentScreen = name
self:UpdateButtonColor()
end

function QuestLog:SetupButtons()
for _, button: Instance in questLog.Container.Buttons:GetChildren() do
if not button:IsA("GuiButton") then
continue
end

(button :: GuiButton).Activated:Connect(function()
self:SetScreen(button.Name)
end)

self.buttons[button.Name] = button
end
end

function QuestLog:SetupWindows()
for _, child: Instance in questLog.Container:GetChildren() do
if not child:IsA("ScrollingFrame") then
continue
end

self.screens[child.Name] = child
end
end

function QuestLog:DestroyQuest(scrollingFrame: ScrollingFrame, questId: string)
local frame: Frame? = scrollingFrame:FindFirstChild(questId)

if frame then
frame:Destroy()
end
end

function QuestLog:CreateQuest(scrollingFrame: ScrollingFrame, quest: Quest)
local Net = Red "QuestManager"

local frame: Frame = scrollingFrame:FindFirstChild(quest.QuestId) or template:Clone()
frame.Name = quest.QuestId
frame.Title.Text = quest.Name
frame.Description.Text = quest.Description
frame.Buttons.Cancel.Visible = quest:GetQuestStatus() == RoQuest.QuestStatus.InProgress
frame.Buttons.Cancel.Activated:Connect(function()
Net:Fire("CancelQuest", quest.QuestId)
end)

frame.Visible = true
frame.Parent = scrollingFrame
end

function QuestLog:PopulateScreen(scrollingFrame: ScrollingFrame, quests: {[string]: Quest})
for _, child: Instance in scrollingFrame:GetChildren() do -- Remove quests no longer in use
if not child:IsA("Frame") then
continue
end

if not quests[child.Name] then
child:Destroy()
end
end

for _, quest: Quest in quests do
self:CreateQuest(scrollingFrame, quest)
end
end

function QuestLog:SetAllScreens()
self:PopulateScreen(self.screens["InProgress"], RoQuest:GetInProgressQuests())
self:PopulateScreen(self.screens["Available"], RoQuest:GetAvailableQuests())
self:PopulateScreen(self.screens["Completed"], RoQuest:GetCompletedQuests())
self:PopulateScreen(self.screens["Delivered"], RoQuest:GetDeliveredQuests())
self:PopulateScreen(self.screens["Unavailable"], RoQuest:GetUnAvailableQuests())
end

RoQuest.OnStart():andThen(function()
QuestLog:SetupWindows() -- Caching our windows
QuestLog:SetupButtons() -- Caching our buttons
QuestLog:SetScreen("InProgress") -- Setting the initial active screen
QuestLog:SetAllScreens() -- Populating all screens

RoQuest.OnUnAvailableQuestChanged:Connect(function()
QuestLog:PopulateScreen(QuestLog.screens["Unavailable"], RoQuest:GetUnAvailableQuests())
end)

RoQuest.OnAvailableQuestChanged:Connect(function()
QuestLog:PopulateScreen(QuestLog.screens["Available"], RoQuest:GetAvailableQuests())
end)

RoQuest.OnCompletedQuestChanged:Connect(function()
QuestLog:PopulateScreen(QuestLog.screens["Completed"], RoQuest:GetCompletedQuests())
end)

RoQuest.OnDeliveredQuestChanged:Connect(function()
QuestLog:PopulateScreen(QuestLog.screens["Delivered"], RoQuest:GetDeliveredQuests())
end)

RoQuest.OnInProgressQuestChanged:Connect(function()
QuestLog:PopulateScreen(QuestLog.screens["InProgress"], RoQuest:GetInProgressQuests())
end)

RoQuest.OnPlayerDataChanged:Connect(function()
QuestLog:SetAllScreens()
end) -- Hard reset our screens
end)

🔑 Main take aways

We used multiple events from RoQuest to listen to the changes from specific quest states. We also used different quest getters to get the information from said quests.

info

You should always have a listener for when the player data changes that resets everything. This ensure that if a player's data gets hard reset that you can update the interface accordingly.

    RoQuest.OnPlayerDataChanged:Connect(function()
QuestLog:SetAllScreens()
end) -- Hard reset our screens