How to make a Quest System Part 3

Hi! This is part 3 of my quest system tutorial. This part deals with the goal of the quest and changing the quest GUI.

NOTE: Sorry for the super delayed post.

If you haven’t read the other parts then here is the link:
~Part 1: How to make a Quest System
~Part 2: How to make a Quest System Part 2

Let’s Begin! :smiley:

First, you’re going to need a folder with all the objects you need to collect, grab, etc. Make sure that all the models or parts has a proximity prompt inside.

Like this:

image

Put the folder in the ReplicatedStorage

Then, you need a leaderstats script in the ServerScriptService with this code:

game.Players.PlayerAdded:Connect(function(player)
	local questsFolder = Instance.new("Folder", player)
	questsFolder.Name = "QuestsFolder"
end)

This code checks if a player joins the game. If it detects a player joining the game, then it makes a folder inside the player named “QuestsFolder”. This folder will contain all the player’s quests.

Now we’re going back to our quest giver script. If you don’t know about this script, make sure to read the parts 1 and 2 above. Now we’re going to add a function to the script. Like this:

local questModule = require(game:GetService("ReplicatedStorage"):WaitForChild("QuestModule"))
local prompt = script.Parent:WaitForChild("ProximityPrompt")
local addQuestToGUI = game:GetService("ReplicatedStorage"):WaitForChild("AddQuest")
local bunniesFolder = game.ReplicatedStorage:WaitForChild("Bunnies")

local quest = "Grab 2 bunnies"

local number = 2

local parentName = script.Parent.Name	

local function findQuest()
	for _,questInModule in pairs(questModule.Quests) do
		if questInModule.Name == quest then
			print("Found quest")
			return questInModule
		end
	end
end

local function makeFolder(questsFolder, questFound, parentName, number)
	local folder = Instance.new("Folder", questsFolder)
	folder.Name = questFound.Name
	local counter = Instance.new("NumberValue", folder)
	counter.Name = "Counter"
	counter.Value = number
	local maxCounter = Instance.new("NumberValue", folder)
	maxCounter.Name = "MaxCounter"
	maxCounter.Value = number
	return folder
end

prompt.Triggered:Connect(function(player)
	if game.Players:FindFirstChild(player.Name) then
		local questFound = findQuest()
		local questsFolder = player:WaitForChild("QuestsFolder")
		if not questsFolder:FindFirstChild(questFound.Name) then
			local folder = makeFolder(questsFolder, questFound, parentName, number)
			addQuestToGUI:FireClient(player, questFound, parentName, number)
			bunniesFolderClone.Parent = workspace
			for _,promptInBunny in pairs(bunniesFolder:GetDescendants()) do
				if promptInBunny:IsA("ProximityPrompt") then
					promptInBunny.Triggered:Connect(function()
						folder.Counter.Value -= 1
						promptInBunny.Parent.Parent:Destroy()
					end)
				end
			end
		end
	end
end)

The new function we made, makeFolder, makes a folder in the QuestsFolder we made earlier. Then it makes a NumberValue name “Counter” in the folder that handles the number of objects you need to collect or grab in the quest. Then we fire it when the prompt is triggered. We have also fixed the bug where the player can get the same quest even if the player already has the quest. The fix for this is the if-statement “if not questsFolder:FindFirstChild(questFound.Name) then”. This if-statement checks whether there is a quest also named as the quest we’re giving to the player. If it doesn’t find one, the code below the if-statement fires. There is also a new variable named bunniesFolder. This folder, which is in the ReplicatedStorage, is the one I mentioned earlier:

First, you’re going to need a folder with all the objects you need to collect, grab, etc. Make sure that all the models or parts has a proximity prompt inside.

Then we clone the bunniesFolder and put it in the workspace for the player to see. After that, we make a for loop so that when each bunny is grabbed or interacted, the bunny is destroyed and we minus the value of the counter with 1.

Now we’re going back to the local script in the StarterGui.

local event = game.ReplicatedStorage:WaitForChild("AddQuest")
local questsGUI = script.Parent:WaitForChild("Quests")
local questFrameTemp = game.ReplicatedStorage:WaitForChild("TempQuestFrame")
local mainFrame = questsGUI:WaitForChild("BGFrame")
local questsFrame = mainFrame:WaitForChild("QuestsFrame")
local player = game.Players.LocalPlayer
local questsFolder = player:WaitForChild("QuestsFolder")

questsFolder.ChildAdded:Connect(function(child)
	local counter = child:WaitForChild("Counter")
	local maxCounter = child:WaitForChild("MaxCounter")
	counter:GetPropertyChangedSignal("Value"):Connect(function()
		local frame = questsFrame:WaitForChild(child.Name)
		if frame then
			local counterFrame = frame:WaitForChild("Counter"):WaitForChild("MainCounter")
			local counterText = frame:WaitForChild("Counter"):WaitForChild("CounterText")
			counterFrame.Size = UDim2.new(counter.Value / maxCounter.Value,0,1,0)
			counterText.Text = counter.Value.."/"..maxCounter.Value
		end
	end)
end)

local function makeFrame(questName, parentName, number)
	local clone = questFrameTemp:Clone()
	clone.Name = questName.Name
	local counter = clone:WaitForChild("Counter")
	local NPCname = clone:WaitForChild("NPCName")
	if parentName == nil then
		NPCname.Text = ""
	else
		NPCname.Text = tostring(parentName)
	end
	local counterText = counter:WaitForChild("CounterText")
	counterText.Text = number.."/"..number
	local QuestName = clone:WaitForChild("QuestName")
	QuestName.Text = tostring(questName.Name)
	clone.Parent = questsFrame
end

event.OnClientEvent:Connect(function(questName, parentName, number)
	if mainFrame.Visible == false then
		mainFrame.Visible = true
	end
	if questsFolder:FindFirstChild(questName.Name) then
		makeFrame(questName, parentName, number)
	else
		print("No quest found")
	end
end)

As you can see, we have added a ChildAdded event that fires when there’s a new object parented to another object. In our case, the object we parented is the folder we made for the quest. So when a new folder for the quest is parented to the Quests Folder, the code below the ChildAdded event fires. The code below the ChildAdded event is simple. When the counter’s value in the folder is decreased, we change the counterFrame's size.

Let’s test it out!

Thanks for going through my tutorial! I hope you learned something in this tutorial. If you did, please give a like and feel free to give a feedback. Stay tuned for part 4! :smiley:

19 Likes

I’ve been looking for a tutorial like this for a while because I struggled to make one myself long ago.
question:
You check if there’s a quest, if so, increase/decrease the value. From what I’ve read, you make a function for each quest. Is that corrent?
If it is, how would I go about making more than one question for the same quest giver ? Would I have to make a function for every single question? Wouldn’t that be overkill? In my case, I wanted to have a LOT of quests.

My apologies if this is answered in the next tutorials.

3 Likes

Thanks for the feedback! You’re right. I haven’t thought of that yet. A simple fix for this is making a new quest variable like this in the quest giver script:

local quest = "Grab 2 bunnies"
local number = 2
local bunniesFolder = game.ReplicatedStorage:WaitForChild("Bunnies")

local quest2 = "Chop 5 trees"
local number2 = 5
local treesFolder = game.ReplicatedStorage:WaitForChild("Trees")

Then adding it to our quest module in the ReplicatedStorage:

local questModule = {
	Quests = {
		Quest1 = {
			Name = "Grab 2 bunnies",
			Reward = 50,
			Currency = "Gold"
		},
		Quest2 = {
		    Name = "Chop 5 trees",
		    Reward = 100,
		    Currency = "Gems"
		}
	}
}

return questModule

After that, we just check the name of the quest. If the quest is named “Grab 2 bunnies”, then we run the code under an if-statement. If the quest is named “Chop 5 trees”, we run the code under it also. Like this:

prompt.Triggered:Connect(function(player)
	if game.Players:FindFirstChild(player.Name) then
		local questFound = findQuest()
		local questsFolder = player:WaitForChild("QuestsFolder")
		if not questsFolder:FindFirstChild(questFound.Name) then
		    if questFound.Name == "Grab 2 bunnies" then
		        local folder = makeFolder(questsFolder, questFound, parentName, number)
		    	addQuestToGUI:FireClient(player, questFound, parentName, number)
		    	local bunniesFolderClone = bunniesFolder:Clone()
		    	bunniesFolderClone.Parent = workspace
		    	for _,promptInBunny in pairs(bunniesFolder:GetDescendants()) do
			    	if promptInBunny:IsA("ProximityPrompt") then
				    	promptInBunny.Triggered:Connect(function()
					    	folder.Counter.Value -= 1
					    	promptInBunny.Parent.Parent:Destroy()
				    	end)
		    	    end
		    	end
            elseif questFound.Name == "Chop 5 trees" then
                local folder = makeFolder(questsFolder, questFound, parentName, number)
		    	addQuestToGUI:FireClient(player, questFound, parentName, number)
		    	local treesFolderClone = treesFolder:Clone()
		    	treesFolderClone.Parent = workspace
		    	for _,promptInTree in pairs(treesFolder:GetDescendants()) do
			    	if promptInTree:IsA("ProximityPrompt") then
				    	promptInTree.Triggered:Connect(function()
					    	folder.Counter.Value -= 1
					    	promptInTree.Parent.Parent:Destroy()
				    	end)
		    	    end
		    	end
		    end
		end
	end
end)
2 Likes

Hmm… I see.

My approach on this was similar but what if you added a table that contains how many quests the players have finished?

For example

-- every time a player joins
finishedQuests[player.UserId] = {} or playerData.finishedQuests

--===========
-- Quests would be the same but using numbers instead of keys.

--===========
-- when a player requests a quest
questData = Quests[ finishedQuests[player.UserId] ]

Now this is more of a personal opinion but here is another suggestion:
Instead of using an if statement for every different quest, I’d go with this

Quest1 = {
	Name = "Grab 2 bunnies",
	Reward = 50,
	Currency = "Gold"
        Action = function()

        local folder = makeFolder(questsFolder, questFound, parentName, number)
        addQuestToGUI:FireClient(player, questFound, parentName, number)
         local bunniesFolderClone = bunniesFolder:Clone()
	 bunniesFolderClone.Parent = workspace
	 for _,promptInBunny in pairs(bunniesFolder:GetDescendants()) do
	 if promptInBunny:IsA("ProximityPrompt") then		  
              promptInBunny.Triggered:Connect(function()
		   	  folder.Counter.Value -= 1
	               promptInBunny.Parent.Parent:Destroy()
	      end)
       end
end
}

return questModule

→ I’m currently on mobile so it’s a little hard to write

2 Likes

Thanks again for the suggestion! Honestly, your idea is far more efficient than I did. You can do it your way. This tutorial is only a guide on how you’re going to make your system and is not a strict step-by-step tutorial. On your suggestion though, you can do it like this:

On the module script, you can do it like this:

local questModule = {
	Quests = {
		Quest1 = {
			Name = "Grab 2 bunnies",
			Reward = 50,
			Currency = "Gold",
			Action = function(bunniesFolder, folder)
				local bunniesFolderClone = bunniesFolder:Clone()
				bunniesFolderClone.Parent = workspace
				for _,promptInBunny in pairs(bunniesFolderClone:GetDescendants()) do
					if promptInBunny:IsA("ProximityPrompt") then
						promptInBunny.Triggered:Connect(function()
							folder.Counter.Value -= 1
							promptInBunny.Parent.Parent:Destroy()
						end)
					end
				end
			end,
		}
	}
}

return questModule

And on the quest giver script, you can do it like this:

prompt.Triggered:Connect(function(player)
	if game.Players:FindFirstChild(player.Name) then
		local questFound = findQuest()
		local questsFolder = player:WaitForChild("QuestsFolder")
		if not questsFolder:FindFirstChild(questFound.Name) then
			if questFound.Name == "Grab 2 bunnies" then
				local folder = makeFolder(questsFolder, questFound, parentName, number)
				addQuestToGUI:FireClient(player, questFound, parentName, number)
				questFound.Action(bunniesFolder, folder)
			end
		end
	end
end)

But on this part, I’m going to be doing this in the data saving part of the tutorial:

What if you added a table that contains how many quests the players have finished?

2 Likes