How to make a Checkpoint have sound in an obby?

Hello! I am in the process of making a difficulty chart obby, and I seem to have run into some slight problems when it came down to scripting certain portions. For right now I have a script for the checkpoints but I have not figured out how to make it so that each checkpoint has a sound when someone goes over it. I would want to make it so that only that player hears the sound and that it only plays the sound once for each checkpoint (they can’t go back and have the checkpoint play the sound again). I must admit that I am still relatively new at scripting so any sort of help would be much appreciated! I have included the checkpoint script down below that I am using.

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 = 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)


The script is in ServerScriptService by the way! I also have my checkpoints in a folder in the workspace called Checkpoints and the sound I want to use is called “Checkpoint” that is also in the workspace. I have looked at some of the other discussions about this specific topic on the forum as well but I haven’t found anything that helped me understand how to utilize the sound in a way that I wanted. Thanks again for anyone who helps!

1 Like

You’re going to have to create a RemoteEvent from the server script to a localscript using :FireClient() and enter a script that plays the sound from the workspace

1 Like

I’m a beginner when it comes to scripting so I am not quite sure on how to use remote events and the fire client. Could you point me in the direction of a tutorial/discussion on the forum that would go into further details of these two functions? Any sort of instruction would be helpful for me in better understanding how to execute this properly. Thank you!

Here’s a post on the forum I found about RemoteEvents and RemoteFunctions. This post currently has almost fifty likes, so it should be useful:

Forum Post

If you need to study this on your own, here is the link to Roblox’s documentation on RemoteEvents and RemoteFunctions:

Documentation for Remotes

2 Likes

So I read through both of them, and must confess that I had no idea that there were two different documentation on the topic of Remote Events and Remote Functions. If I read through it right, does that mean that Remote Events is for the entirety of the server to see and Remote Functions are for one singular person to see on the server? If that is the case, does that mean I need to make a Remote Function in order for what I mean to accomplish to work? Sorry if that doesn’t make sense; I still find a majority of this confusing and I am trying to further understand as best I can. Thanks again for the help!

No, you can use a RemoteEvent, too. RemoteEvents only fire to all clients when you call the RemoteEvent:FireAllClients() method. You can use RemoteEvent:FireClient(PlayerInstance) to fire to one client. This will not yield the code on the server’s side until the client’s code finishes running.

The RemoteFunction does not have a method to invoke all of the clients at once. You can use a RemoteFunction if you want your code to yield on the side that is invoking until the other side’s function is finished. Here is an example, in case I did not explain that well:

-- Server --
local answer = remoteFunction:InvokeClient() -- Waits for the client's response.

if answer then -- (if answer == true)
    -- Put ketchup on the client's burger.
end

-- Client --
remoteFunction.OnClientInvoke = function()
    return true -- "Yes, I do want ketchup on my burger."
end

Sorry if I over-explained or under-explained; sorry if I did not answer your question. I am not sure if I understood you.

Edit: I re-read your question. Information returned to a RemoteFunction invoked from the server cannot be accessed from all the other scripts in the server, from what I know, since the client is returning to that specific invoke. This works vice versa, too.

1 Like

I apologize for the late reply! I took a step back from this project for a bit, seeing that all this new information was overwhelming for me. I ended up completely reworking my script so I could integrate tweening for the checkpoint. I’ll insert it in below.

local Players = game:GetService("Players");
local RunService = game:GetService("RunService");
local TweenService = game:GetService("TweenService");
local TweenTime = TweenInfo.new(1);
local Checkpoints = workspace:WaitForChild("Checkpoints"):GetChildren();


local function FindCheckpoint(character, stage)
	for i, checkpoint in pairs(Checkpoints) do
		local checkpoint_number = tonumber(checkpoint.Name);
		if stage.value == checkpoint_number then
			return checkpoint;
		end
	end
end

Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats";
	leaderstats.Parent = player;
	
	local stage = Instance.new("IntValue");
	stage.Name = "Stage";
	stage.Value = 0;
	stage.Parent = leaderstats;
	
	player.CharacterAdded:Connect(function(character)
		local root_part = character:WaitForChild("HumanoidRootPart");
		local checkpoint = FindCheckpoint(character, stage);
		
		RunService.Heartbeat:Wait();
		root_part.CFrame = checkpoint.CFrame * CFrame.new(0,1,0);
		
		
	end)
end)

for i, checkpoint in pairs(Checkpoints) do
	checkpoint.Touched:Connect(function(touch)
		local humanoid = touch.Parent:FindFirstChild("Humanoid");
		if humanoid and humanoid.Health > 0 then
			local player = Players:GetPlayerFromCharacter(touch.Parent);
			if player then
				local leaderstats = player:WaitForChild("leaderstats")
				local stage = leaderstats.Stage;
				
				local checkpoint_number = tonumber(checkpoint.Name);
				if checkpoint_number - stage.Value == 1 then
					stage.Value = checkpoint_number;
					
					local goal = {
						Size = checkpoint.Size + Vector3.new(2, 0.5, 2);
						Transparency = 1;
					};
					
					local checkpoint = checkpoint:Clone()
					checkpoint.Parent = workspace
					
					local tween = TweenService:Create(checkpoint, TweenTime, goal);
					tween:Play();
					
					tween.Completed:Connect(function()
						checkpoint:Destroy();
					end)
				end
			end
		end
	end)
end

This script is still in my ServerScriptService. I am understanding the concept of remote events a bit more but I still don’t quite understand it as much as I want to. Would I need to make another script to integrate the sound function for the checkpoint or is there a way to integrate it in this script? I made a remote event in replicated storage called PlayCheckpointSound btw in an attempt to script it but I am still unsure how. Thanks again for your help for bettering understand this! It’s seems to be definitely a process for trial and error :smiling_face_with_tear:

Yes, you will need to make another script. If you want the sound to only play for the client, you should use a RemoteEvent and fire it to the client that reached the new checkpoint. Then create a LocalScript that will play the sound when fired to.

The server cannot detect OnClientEvent, and the client cannot detect OnServerEvent. So, when firing to one or the other, you need to make an opposing script for the fire to be detected.

1 Like

So make a script in ServerScriptService that addresses the Remote Event and also the Checkpoints; then make a local script to play the sound? Does it matter where I put the local script or should it be in the ReplicatedStorage?

You should put the LocalScript in StarterPlayerScripts. LocalScripts do not run in ReplicatedStorage by the way.

1 Like

Ah okay! Thanks for the tip/response!

1 Like