How to make a Job System

How it works:

When a player activates a button they will be given a job and after a certain amount of time they will earn money. Then, when they end their shift or leave the job area the earned money will be added to their leaderstats.

I will make a second part on how to give the player task like in work at a pizza place or bloxburg, but for now this is just a simple job system.

This tutorial is recommended for intermediate coders but its explained thoroughly so I’m sure a beginner wouldn’t have much problem following along (but if you don’t feel free to comment questions) :happy1:

Step 1: Set up

First, you will need a proximity prompt where you want the player to take the job. I would recommend putting it in a invisible part above something like a cash register then edit the properties how you feel it should be.

Next create a “floor” everywhere you want the player to be able to walk while in the job, and name it “JobArea”. We will make it so that when the player isn’t standing on the floor their shift will end so, make sure it covers where you need it to.

Now, create a script in server script service. Also create a remote in replicated storage

You will need to create a GUI that looks like this:
d1
The “End Shift” is a button and should have a local script inside it.

This is what your workspace should look like:

Step 2: Scripting The Basics

First, in the server script, define services, module etc…

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

local Remote = ReplicatedStorage:WaitForChild("RemoteEvent")

local Zone = workspace:WaitForChild("JobArea")
local Prompt = workspace:WaitForChild("Prompt").ProximityPrompt

Make sure that the Zone and Prompt lead to where you put yours.

local Hired = false
local Coro = nil --will be explained later

local Wage = 10
local Earned = 0
local ShiftLength = 5 -- in seconds

These are just variables that will be used later. Wage and Shift Length can be changed but for testing you should keep them as they are.

Next, create these empty functions we will fill in later:

Prompt.Triggered:Connect(function(Player)
	
end)

RunService.Heartbeat:Connect(function()
	
end)

Remote.OnServerEvent:Connect(function(Player, Job)
	
end)

Players.PlayerAdded:Connect(function(Player)

end)

Players.PlayerRemoving:Connect(function(Player)
	
end)

Step 3: Functions

Step 3a: Handling Hiring and Paying

Now, we have to create the actual functions. Above the code you just wrote type this:

local function IsInJob(Position)
	local pos = Zone.CFrame:PointToObjectSpace(Position)
	return (math.abs(pos.X) <= Part.Size.X / 2)
		and (math.abs(pos.Z) <= Part.Size.Z / 2)
		and (pos.Y >= 0)
end

What this will do is compare the players position (not yet defined) with the position of the
JobArea (pos) we created earlier. Later we will use this to fire the player if they leave that area.

If you want the player to continuously earn money no matter where they are simple don’t include this function and remove any other lines that include it too. If you do include it you should add a pop up gui to alert the player that they must be standing on the zone or teleport them there.

Next, we will need to actually pay the player with this function:

local function PayPlayer(Player)
	local JobGui = Player:WaitForChild("PlayerGui"):WaitForChild("JobGui")

	if Hired then
		Earned += Wage
		JobGui.Frame.CashEarned.Text = "Cash Earned: " .. Earned
	elseif Hired == false then
		Earned = 0
		JobGui.Frame.CashEarned.Text = "Cash Earned: " .. Earned
	end
end

First, we define where our job gui is. Next, we check if the variable “Hired” is true or not. Then we add how much the player has earned to the wage. If you used different named for the variables make sure to change it. The next line edits the job guis text. The elseif is what happens when the player is fired; resetting how much they earned and their gui.

This is where is starts getting a little complicated at the functions start intertwining. The next function is what will actually hire the player:

local function HirePlayer(Player)
	local HRP = (Player.Character ~= nil) and Player.Character:FindFirstChild("HumanoidRootPart")
	
	if Hired == false and IsInJob(HRP.Position, Zone) then
		Hired = true

This function will define the players Humanoid Root Part (will be used in the earlier function to test if they are in the job area), it will also check if the player is already hired or not. If the player meets all these conditions they will then finally be hired.

Continuing under the if-statement, type this:

       Coro = coroutine.wrap(function()
			while Hired == true do
				task.wait(ShiftLength)

				if Hired == true then
					PayPlayer(Player)
				end
			end

			coroutine.yield()
		end)

		Coro()
	end

We will be using coroutines to continually pay the player during their shift. Coroutines are kinda hard to explain but to put it simply, it allows us to run the pay player function while also running all the other parts of the script. I would highly recommend reading the api reference for coroutines if you don’t understand.

Step 3b: Handling Firing/Quitting

Now to handle what will happen when the player wants to quit their job.

function FirePlayer(Player)
	local JobGui = Player:WaitForChild("PlayerGui"):WaitForChild("JobGui")
	local Cash = Player:WaitForChild("leaderstats").Cash -- Whatever your money is called

	Cash.Value += Earned

	Hired = false
	PayPlayer(Player)

	JobGui.Enabled = false
	Prompt.Enabled = true

	Coro()
end

This essentially pays the player how much they earned, removes the gui, and sets hired to false.

function UpdatePlayer(Player, HRP)
	local JobGui = Player:WaitForChild("PlayerGui"):WaitForChild("JobGui")

	if IsInJob(HRP.Position, Zone) then

		JobGui.Enabled = true
		Prompt.Enabled = false

	elseif not IsInJob(HRP.Position, Zone) or not Hired then
		FirePlayer(Player)
	end
end

All this function does is check if the player leaves their job area then fires them.

Step 4: Finishing System

OK now we can finally go back to those functions we didn’t finish in Step 2.

In the “PromptButtonHoldEnded” function, simply put:

HirePlayer(Player)

In the “Heartbeat” function put:

for _, Player in ipairs(Players:GetPlayers()) do
    if Hired == true then
       local HRP = (Player.Character ~= nil) and Player.Character:FindFirstChild("HumanoidRootPart")

	   UpdatePlayer(Player, HRP)
	end
end

This is a loop that will run on a heartbeat to check if the player leaves the job zone.

And for the last two:

Remote.OnServerEvent:Connect(function(Player, Job)
	if Job == "Job" then
		FirePlayer(Player)
	end
end)

Players.PlayerAdded:Connect(function(Player)
	local LS = Instance.new("Folder", Player)
	LS.Name = "leaderstats"
	
	local Cash = Instance.new("IntValue", LS)
	Cash.Name = "Cash"
end)

Players.PlayerRemoving:Connect(function(Player)
	FirePlayer(Player)
end)

Now, at this point you should be able to test the game. You wont be able to quit the job using the GUI yet but after standing in position and earning money you should see you points in your leader stat go up. It should work as you see in this video:

ezgif.com-gif-maker

Step 5: Finishing up

We are basically done the only thing you need to do is put this code inside the “End Shift”

local Button = script.Parent

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Remote = RS:WaitForChild("RemoteEvent")

Button.Activated:Connect(function()
	Remote:FireServer("Job")
end)

Another thing you should do is datastores. A datastore will save a players total amount of money so that when they rejoin the game it’ll all be there.

I’m not going to explain how to create a datastore in this tutorial but there are many good tutorials that will work I recommend AlvinBlox’s.

Congratulations! You have reached the end of tutorial! Have fun using this for your roleplaying games, simulators, etc… and if you have any issues please comment them! :happy2:

26 Likes

Great tutorial, this was a great idea that I never thought of!

2 Likes

How about .Triggered?

stopforcingmetotypethrtychars

Because I use a 5 second hold duration and Triggered is only if that is zero

1 Like

No.
.Triggered activates right after the promt hold finished

Oh yea I realize you are right but it still works how it should regardless