How to create an obby, Part 1: Checkpoints system and lava bricks


Things you should know before reading this tutorial

  • Basic events
  • Basics of functions
  • Basics of CFrame
  • Creating leaderstats

Not into words? No problem!

Video Tutorial Here

How to Script Checkpoints | ROBLOX Studio - YouTube

The checkpoint system

Checkpoints are one of the most important parts of an obby, its a way for your players to know where they are, a way for better players to flex on noobs and it saves the progress of the player.

So how do you make this?

First create the model for your checkpoint, I’m going to stick with a neon white part. You don’t really need something super fancy, nobody really cares what the checkpoints in your game look like.

Checkpoint_obbyTutorial


Here are the properties of my checkpoint if you really care:

  • Size: 5,1,5
  • Material: Neon
  • BrickColor: Institutional white
  • Anchored: true

Hopefully you remembered to anchor your checkpoint…

Now that you’ve made your checkpoints, we’re going to need a folder to store all these checkpoints in. Start by creating a folder inside of the workspace, just for naming sake, you should name it something along the lines of “Checkpoints”. Next parent the checkpoint you made to that folder. You should name all checkpoints you make relative to the checkpoint’s corresponding stage. Example:

checkNames_obbyt

Time to get scripting

Let’s begin with creating an auto saving data store… (I’ll be sticking with something simple and somewhat unreliable, if you’d like something better I suggest Data Store 2 by @Kampfkarren → How to use DataStore2 - Data Store caching and data loss prevention - #476 by Tybearic)

local StageData = game:GetService("DataStoreService"):GetDataStore("StageData")
--[[
 * The DataStoreService is responsible for storing data (it's in the name)
Creating a new data store is simple
 * The :GetDataStore(data store: string) function simply finds a data store by the name given in the parameters
or creates a new data store by that name if one doesn't already exist
 * The data from these data stores is quite easy to retrieve...
Each data store comes with some more functions
 * DataStore:GetAsync(key: any)
In this game we'd do something like this
StageData:GetAsync(player.UserId)
The player's userid will be the key for retrieving their saved data in this case
 * DataStore:SetAsync(key: any, updated data: any)
The SetAsync function sets the data for the data store
]]

local Checkpoints = workspace:WaitForChild("Checkpoints"):GetChildren()
-- A table of all checkpoints we created earlier
-- :WaitForChild("Checkpoints") waits until there's an object called "Checkpoints" in the workspace if there isn't one already
-- We do this because sometimes this script can load in before the checkpoints folder
-- :GetChildren() finds all parts inside of an object

function GoToCheckpoint(character, stage)
	local rootPart = character:WaitForChild("HumanoidRootPart")
	repeat wait(0.00) until rootPart
	-- The HumanoidRootPart of the character is basically where the character is (it has other uses as well)
	-- If we change the cframe/position of the root part then the character comes with it
	for i, checkpoint in pairs(Checkpoints) do
		-- Loops through all the checkpoints so we can find the right one
		print("Checkpoint" .. tostring(stage))
		if ("Checkpoint" .. tostring(stage)) == checkpoint.Name then
			print("Checkpoint found")
			rootPart.CFrame = checkpoint.CFrame * CFrame.new(0,1,0)
			-- Change the CFrame of the root part to the player's stage (it's kinda like position)
			-- We're multiplying by CFrame.new(0,1,0) so the player doesn't spawn inside of the checkpoint
			break
			-- Stop looping since we found the checkpoint
		end
	end
end

game:GetService("Players").PlayerAdded:Connect(function(player)
	-- The function we're connecting to this event will be called everytime a player joins the game
	
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"
	
	local stage = Instance.new("IntValue", leaderstats)
	stage.Name = "Stage"
	stage.Value = StageData:GetAsync(player.UserId) or 1
	-- Retrieve the player's data, but if there is none, start the player on stage 1
	
	player.CharacterAdded:Connect(function(character)
		-- The function we're connecting to this event will be called everytime the player's character spawns
		GoToCheckpoint(character, stage.Value)
	end)
end)

for i, checkpoint in pairs(Checkpoints) do
	checkpoint.Touched:Connect(function(touch)
		-- Touched is an event that is fired when the object is touched
		local hum = touch.Parent:FindFirstChild("Humanoid")
		-- If a part in a model touches the part, touch = that part not the model
		if hum and hum.Health > 0 then
			-- The thing that touched the checkpoint is alive
			local player = game:GetService("Players"):GetPlayerFromCharacter(touch.Parent)
			-- This allows us to find a player with their character (it's very useful)
			if player then
				-- Whatever touched the checkpoint is a player
				local leaderstats = player:WaitForChild("leaderstats")
				local stage = leaderstats:WaitForChild("Stage")
				-- Find the stage leader stat
				if (tonumber(checkpoint.Name:match("%d+"))-stage.Value) == 1 then
					-- match() finds certain parts in a string
					-- So when we do match("%d"), we're finding the numbers in a string
					-- This is the player's next checkpoint
					-- This makes it so player's can't skip checkpoints
					-- or lose progress if they accidently go backwards
					stage.Value = stage.Value + 1
				end
			end
		end
	end)
end

game:GetService("Players").PlayerRemoving:Connect(function(player)
	StageData:SetAsync(player.UserId, player.leaderstats.Stage.Value)
	-- Saves the player's data when they leave the game
end)

You can start making your first stages now

FirstStages_OFOBBYJiuhsodifuwaifua

Lava bricks

Lava bricks are an iconic part of obbies, it’s hard to find an obby that doesn’t have them. Lava bricks should be neon and red to let the player know not to touch them.

Lava bricks are pretty simple, so I won’t be explaining much about how they work.

Time to get scripting

Create a new script inside of the part you want to turn into a lava brick, as I did before, notes on how the script works will be in the code.

local part = script.Parent
-- Refers to the parent of the script

part.Touched:Connect(function(touch)
	local hum = touch.Parent:FindFirstChild("Humanoid")
	if hum and hum.Health > 0 then
		hum.Health = 0
		-- If a humanoid's health property is set to 0 they die
	end
end)

If you have anything to add, are having problems, find something to be unclear or have to correct me on something I got wrong, please let me know.


In the 2nd part I’ll be going over spinning parts, instant respawning and some keyboard shortcuts such as “R” to reset.

52 Likes

Sorry for bumping this but where do we put the script, I assume ServerScriptService but when i did it I didn’t get teleported and the stage would only stay if I only went 1 stage, if I went 2+ stages I would get sent back to the start and the stage I was on would no longer let me get to that stage.

4 Likes

Is there an error popping up on the Output tab?

hi i have a question is it a local script or normal script

1 Like

do i put this script in server script service

It’s a normal script in the server script service

2 Likes

oh thats why it didnt work so do i put the checkpoint parts in serverscripterservice too

No, the parts go in the workspace along with the folder they’re in.

oh ok thank you so much for ur help appreciate it btw

No problem, if you have anymore questions, feel free to ask. I’ll make sure to get back to you as soon as possible. :smiley:

2 Likes

for some reason it doesnt work

1 Like

Can I see your code and picture of your checkpoints folder

I’d recommend telling people to use CollectionService to get the tagged items as “KillPart”. This will be much more efficient then spamming multiple scripts. You can loop all tagged Kill Parts with the same one script in serverscriptservice:


local CollectionService = game:GetService(“CollectionService”)
local Debounce = false

local function OnTouch(hit)
if not hit.Parent or Debounce then return end
Debounce = true

if hit.Parent:FindFirstChild(“Humanoid”) then

Humanoid.Health = 0

end

Debounce = false
end


for i, part in pairs(CollectionService:GetTagged(“KillPart”) do

part.Touched:Connect(OnTouch)

end


Now we just need to add all the death blocks the tag “KillPart”.

I’d use “Tag Editor”, a free 5 star plugin in Toolbox to tag items while on “Edit Mode” since its impossible to tag an object without scripting otherwise. The tagged item must be tagged with “KillPart” as it corresponds to our “for i, part in pairs” loop.

“Tagging” is not the same as “Naming” the object. The object can still have a unique name like “bobsBrick” and be tagged as “KillPart”. Tagging is basically a hidden name.

4 Likes

Sure let me just take one I’ll send to ulayer

Screenshot 2021-06-14 114458
Here is a picture of the checkpoints folder but the problem is that it does add value to stage leaderstat intvalue but when i die i dont spawn at the checkpoint is the problembecause i have a spawn location at the beggining?

The spawn location would probably mess things up, you could try instead making a checkpoint on the spawn location named “Checkpoint0”

ah alright ill do it ill tell u if i have any other problems

im sorry to keep interupting you but still not working

Can you show me your code? Maybe there’s a special scenario in your game this tutorial isn’t accounting for.

alright i just copied the code u used

local StageData = game:GetService(“DataStoreService”):GetDataStore(“StageData”)
–[[

  • The DataStoreService is responsible for storing data (it’s in the name)
    Creating a new data store is simple
  • The :GetDataStore(data store: string) function simply finds a data store by the name given in the parameters
    or creates a new data store by that name if one doesn’t already exist
  • The data from these data stores is quite easy to retrieve…
    Each data store comes with some more functions
  • DataStore:GetAsync(key: any)
    In this game we’d do something like this
    StageData:GetAsync(player.UserId)
    The player’s userid will be the key for retrieving their saved data in this case
  • DataStore:SetAsync(key: any, updated data: any)
    The SetAsync function sets the data for the data store
    ]]

local Checkpoints = workspace:WaitForChild(“Checkpoints”):GetChildren()
– A table of all checkpoints we created earlier
– :WaitForChild(“Checkpoints”) waits until there’s an object called “Checkpoints” in the workspace if there isn’t one already
– We do this because sometimes this script can load in before the checkpoints folder
– :GetChildren() finds all parts inside of an object

function GoToCheckpoint(character, stage)
local rootPart = character:WaitForChild(“HumanoidRootPart”)
repeat wait(0.00) until rootPart
– The HumanoidRootPart of the character is basically where the character is (it has other uses as well)
– If we change the cframe/position of the root part then the character comes with it
for i, checkpoint in pairs(Checkpoints) do
– Loops through all the checkpoints so we can find the right one
print(“Checkpoint” … tostring(stage))
if (“Checkpoint” … tostring(stage)) == checkpoint.Name then
print(“Checkpoint found”)
rootPart.CFrame = checkpoint.CFrame * CFrame.new(0,1,0)
– Change the CFrame of the root part to the player’s stage (it’s kinda like position)
– We’re multiplying by CFrame.new(0,1,0) so the player doesn’t spawn inside of the checkpoint
break
– Stop looping since we found the checkpoint
end
end
end

game:GetService(“Players”).PlayerAdded:Connect(function(player)
– The function we’re connecting to this event will be called everytime a player joins the game

    local leaderstats = Instance.new("Folder", player)
leaderstats.Name = "leaderstats"

local stage = Instance.new("IntValue", leaderstats)
stage.Name = "Stage"
stage.Value = StageData:GetAsync(player.UserId) or 1
-- Retrieve the player's data, but if there is none, start the player on stage 1

player.CharacterAdded:Connect(function(character)
	-- The function we're connecting to this event will be called everytime the player's character spawns
	GoToCheckpoint(character, stage.Value)
end)

end)

for i, checkpoint in pairs(Checkpoints) do
checkpoint.Touched:Connect(function(touch)
– Touched is an event that is fired when the object is touched
local hum = touch.Parent:FindFirstChild(“Humanoid”)
– If a part in a model touches the part, touch = that part not the model
if hum and hum.Health > 0 then
– The thing that touched the checkpoint is alive
local player = game:GetService(“Players”):GetPlayerFromCharacter(touch.Parent)
– This allows us to find a player with their character (it’s very useful)
if player then
– Whatever touched the checkpoint is a player
local leaderstats = player:WaitForChild(“leaderstats”)
local stage = leaderstats:WaitForChild(“Stage”)
– Find the stage leader stat
if (tonumber(checkpoint.Name:match("%d+"))-stage.Value) == 1 then
– match() finds certain parts in a string
– So when we do match("%d"), we’re finding the numbers in a string
– This is the player’s next checkpoint
– This makes it so player’s can’t skip checkpoints
– or lose progress if they accidently go backwards
stage.Value = stage.Value + 1
end
end
end
end)
end

game:GetService(“Players”).PlayerRemoving:Connect(function(player)
StageData:SetAsync(player.UserId, player.leaderstats.Stage.Value)
– Saves the player’s data when they leave the game
end)