Personal Checkpoints per Player

Hey all!

I’m gonna keep this simple. I’m currently creating a game which is a very long and challenging obby. I want to give the players their own freedom to set their checkpoints wherever they want too. The players would press the button C on their keyboard to set the checkpoint and then the letter V to teleport back to that checkpoint they have set. The problem is, I am very new to scripting, and I have absolutely no idea how to make that work.

If anyone has any suggestions, it would be much appreciated!
Thank you.

Maybe set the Checkpoint’s CFrame(Posiiton) to the player’s current position

I’d recommend the following logic:

  1. Create a function using ContextActionService (BindAction) that listens to the C and V keycodes
  2. If the key is C then it sets the checkpoint, by getting their Character’s HumanoidRootPart Position and setting it as a variable.
  3. If the key is V then they’re teleported via MoveTo back to that saved position.
local ContextActionService = game:GetService("ContextActionService")
local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer

local SET_CHECKPOINT_KEY = Enum.KeyCode.C
local GOTO_CHECKPOINT_KEY = Enum.KeyCode.V


local SavedCheckpoint;

local function doesPlayerHaveCharacter()
	return (LocalPlayer.Character and LocalPlayer.Character:IsDescendantOf(workspace) and LocalPlayer.Character:FindFirstChild("HumanoidRootPart"))
end

local function checkpointAction(action, ioState, ioObject)
	if ioState == Enum.UserInputState.Begin and doesPlayerHaveCharacter() then
		if ioObject.KeyCode == SET_CHECKPOINT_KEY then
			SavedCheckpoint = LocalPlayer.Character.HumanoidRootPart.Position
			print("Saved Checkpoint!") -- You might want to add some kind of pop-up?
		elseif SavedCheckpoint then
			LocalPlayer.Character:MoveTo(SavedCheckpoint)
		end
	end
end

ContextActionService:BindAction("SetCheckpointAction", checkpointAction, true, SET_CHECKPOINT_KEY, GOTO_CHECKPOINT_KEY)
2 Likes

I’m not usually one for giving free scripts, but since this is a rather easy one, I’m going to help you out while hopefully making you learn something.

So, first thing first, you would want to make a localscript somewhere in the Player. This would work best in StarterPlayerScripts.

Inside the script, it would look something like this:

local Players = game:GetService('Players') -- This gets the "Players" service
local Player = Players.LocalPlayer -- This gets the local Player

local UserInputService = game:GetService('UserInputService') -- This is the service we use to detect buttons clicked

local LastSavedPosition = nil -- The last position the player saved, starts as nil

UserInputService.InputBegan:Connect(function(Input, GameProcessed) -- Setting up a listening event for when a button is clicked, Input is the button clicked and GameProcessed is whether it was inside a Gui Object
	if not GameProcessed then -- We don't want this to run if the button was pressed inside a Gui like the roblox chat, do we?
		if Input.KeyCode == Enum.KeyCode.C then -- Did the player press C?
			print('C was pressed! Let\'s make sure the player\'s character and head exists before saving the position...')
			if Player.Character and Player.Character:FindFirstChild('Head') then
				local Head = Player.Character.Head -- Making the player's head a variable to make this easier
				LastSavedPosition = Head.Position
				print('Saved!')
			end
		elseif Input.KeyCode == Enum.KeyCode.V then -- No need to add a whole new listening event for this, just use elseif!
			print('V was pressed! Player wants to teleport to last saved position')
			if LastSavedPosition then -- We need to make sure the player actually has a saved position to teleport to
				print('We got a saved position! Let\'s get the character and then teleport')
				if Player.Character then
					Player.Character:MoveTo(LastSavedPosition)
					print('tada')
				end
			else
				print('Woah! Save a position first!')
			end
		end
	end
end)
1 Like

This particular script might have unintended consequences. Since the Head is not the root part of the Humanoid, the teleport position will be moved up - this could facilitate fly glitching. Otherwise, we seem to have been working on the same thing!

1 Like

That might be true! However, I didn’t know if he was using R6 or R15, so I figured getting the Head was the safest bet. I didn’t think to get the HumanoidRootPart.

I almost always forget that part even exists

1 Like

Yeah, there is also Humanoid.RootPart, which might (?) be a more sustainable future option when Roblox move to different rig forms.

2 Likes

Hey thank you for the input! I used your script to test it out, and for the most part it works perfectly. However, as unix_system stated, there is an issue with setting the checkpoint next to a wall, as when you teleport back to that spot, it does teleport you onto the top of the wall instead of the position it was set. I changed it from Head to HumanoidRootPart, yet the problem still insists. Do you suspect there is a fix for that? If not I will tackle that problem in another way, such as the layout of the map

There is a fix for that, yeah. Instead of using MoveTo(), we can try CFrame.

local Players = game:GetService('Players') -- This gets the "Players" service
local Player = Players.LocalPlayer -- This gets the local Player

local UserInputService = game:GetService('UserInputService') -- This is the service we use to detect buttons clicked

local LastSavedPosition = nil -- The last position the player saved, starts as nil

function GetHumanoidRootPart()
	return Player.Character and Player.Character:FindFirstChild('HumanoidRootPart') or nil -- Returns HumanoidRootPart if found or nil
end
	
UserInputService.InputBegan:Connect(function(Input, GameProcessed) -- Setting up a listening event for when a button is clicked, Input is the button clicked and GameProcessed is whether it was inside a Gui Object
	if not GameProcessed then -- We don't want this to run if the button was pressed inside a Gui like the roblox chat, do we?
		if Input.KeyCode == Enum.KeyCode.C then -- Did the player press C?
			print('C was pressed! Let\'s make sure the player\'s character and HumanoidRootPart exists before saving the position...')
			local HRP = GetHumanoidRootPart() -- Getting the HumanoidRootPart
			if HRP then -- Does the HumanoidRootPart exist?
				LastSavedPosition = HRP.CFrame
				print('Saved!')
			end
		elseif Input.KeyCode == Enum.KeyCode.V then -- No need to add a whole new listening event for this, just use elseif!
			print('V was pressed! Player wants to teleport to last saved position')
			if LastSavedPosition then -- We need to make sure the player actually has a saved position to teleport to
				print('We got a saved position! Let\'s get the character and then teleport')
				local HRP = GetHumanoidRootPart() -- Getting the HumanoidRootPart
				if HRP then -- Does the HumanoidRootPart exist?
					HRP.CFrame = LastSavedPosition
					print('tada')
				end
			else
				print('Woah! Save a position first!')
			end
		end
	end
end)
1 Like

I optimized the code a bit more.

local Players = game:GetService('Players') -- This gets the "Players" service
local Player = Players.LocalPlayer -- This gets the local Player

local UserInputService = game:GetService('UserInputService') -- This is the service we use to detect buttons clicked

local LastSavedPosition = nil -- The last position the player saved, starts as nil

function GetHumanoidRootPart()
	return Player.Character and Player.Character:FindFirstChild('HumanoidRootPart') or nil -- Returns HumanoidRootPart if found or nil
end
	
UserInputService.InputBegan:Connect(function(Input, GameProcessed) -- Setting up a listening event for when a button is clicked, Input is the button clicked and GameProcessed is whether it was inside a Gui Object
	if not GameProcessed then -- We don't want this to run if the button was pressed inside a Gui like the roblox chat, do we?
		if Input.KeyCode == Enum.KeyCode.C or Input.KeyCode == Enum.KeyCode.V then -- One of the buttons we were listening to was pressed!
			local HRP = GetHumanoidRootPart() -- Since both buttons require the HumanoidRootPart, we\'ll start with that
			if HRP then
				print('HumanoidRootPart exists!')
				if Input.KeyCode == Enum.KeyCode.C then -- Was C pressed?
					print('C was pressed!')
					LastSavedPosition = HRP.CFrame
					print('Saved!')
				else -- No? Must be V then
					print('V was pressed!')
					if LastSavedPosition then -- We need to make sure the player actually has a saved position to teleport to
						HRP.CFrame = LastSavedPosition
						print('tada')
					else
						print('Woah! Save a position first!')
					end
				end
			end
		end
	end
end)

This works perfectly now! Thank you so much!

1 Like