Issue with .Touched script not always working?

The problem is WaitForChild will keep looking for ANYTHING named Humanoid inside touched.Parent. Also, WaitForChild will not stop looking until the “Humanoid” instance is found, so for example, if the Player doesn’t have a Humanoid, (Which is basically impossible), then it will keep looking and pause everything beyond it until Humanoid was found. Try using:

local Humanoid = touched.Parent:FindFirstChild("Humanoid")
1 Like

The WaitForChild() is fine, he should just have it inside a conditional which checks if the player/character is valid.

local Checkpoints = workspace:WaitForChild("Checkpoints")
local Debounce = false
local Players = game:GetService("Players")

for _, checkpoint in ipairs(Checkpoints:GetChildren()) do
	checkpoint.Touched:Connect(function(touched)
		if Debounce then
			return
		end
		if touched.Parent:FindFirstChild("Humanoid") then
			Debounce = true
			local Character = touched.Parent
			local Player = Players:GetPlayerFromCharacter(touched.Parent)
			if Player then
				local Humanoid = Character:WaitForChild("Humanoid")
				if tonumber(checkpoint.Name) == Player.leaderstats.Stage.Value + 1 and Humanoid.Health ~= 0 then
					Player.leaderstats.Stage.Value = tonumber(checkpoint.Name) --updating the player's stats to the new checkpoint
					Player.RespawnLocation = checkpoint
				end
			end
		end
		task.wait(0.5)
		Debounce = false
	end)
end
1 Like

@Y_VRN @geometricalC2123 @CommanderRanking thank you for all your replies. Part of the problem has been fixed now but there’s still a few things unclear to me.
The WaitForChild("Humanoid") only runs after the Humanoid has already been found by FindFirstChild("Humanoid"). So that’s not an issue except for very rare cases where the player leaves just while touching a checkpoint then it might yield.
But maybe those few cases are indeed what happened, since most of the time the checkpoints worked fine and then randomly one doesn’t.

If this is the case and there is no Humanoid anymore, wouldn’t :FindFirstChild() just give another error like “attempt to call nil”?

Thanks for your reply. And yeah I don’t think the WaitForChild() on it’s own is that much of a problem if there would be a conditional. But actually isn’t there already a conditional which checks if the player is valid?

if touched.Parent:FindFirstChild("Humanoid") then

↑ This line already checks if the part that touched the checkpoint is part of a player
So what is the use of checking it again? ↓

if Player then

Maybe there’s a difference, I’m guessing maybe the player-model despawns faster than it’s character in the workspace. But I’m not sure how exactly it works, could you clarify on this or just link a developerhub article. Thanks in advance.

It’s possible if you have NPC’s in the game which would also have the Humanoid instance inside of them to trigger the event to fire, they would not pass the player check however. Alternatively no WaitForChild() is possible.

local Checkpoints = workspace:WaitForChild("Checkpoints")
local Debounce = false
local Players = game:GetService("Players")

for _, checkpoint in ipairs(Checkpoints:GetChildren()) do
	checkpoint.Touched:Connect(function(touched)
		if Debounce then
			return
		end
		if touched.Parent:FindFirstChild("Humanoid") then
			Debounce = true
			local Humanoid = touched.Parent:FindFirstChild("Humanoid")
			local Character = touched.Parent
			local Player = Players:GetPlayerFromCharacter(touched.Parent)
			if Player then
				if tonumber(checkpoint.Name) == Player.leaderstats.Stage.Value + 1 and Humanoid.Health ~= 0 then
					Player.leaderstats.Stage.Value = tonumber(checkpoint.Name) --updating the player's stats to the new checkpoint
					Player.RespawnLocation = checkpoint
				end
			end
		end
		task.wait(0.5)
		Debounce = false
	end)
end

The issue is related to the if statement raising an error causing Debounce to forever stay false, it may be due players which take longer than usual to load, I tried making your code as error proof I was able to:

local Players = game:GetService("Players")

local Checkpoints = workspace:WaitForChild("Checkpoints")

for _,checkpoint in pairs(Checkpoints:GetChildren()) do
	local Debounce = false 

	checkpoint.Touched:Connect(function(touched)
		if not Debounce then
			Debounce = true
	
			local character = touched.Parent
			local humanoid = character:FindFirstChildWhichIsA("Humanoid") 
			if not humanoid then Debounce = false return end 
			local player = Players:GetPlayerFromCharacter(character)
			if not humanoid then Debounce = false return end 
			local leaderstats = player:FindFirstChild("leaderstats")
			if not leaderstats then Debounce = false return warn("leaderstats not found/loaded") end 
			local stage = leaderstats:FindFirstChild("Stage")  
			if not stage then Debounce = false return warn("stages not found/loaded") end 
				
			local level = tonumber(checkpoint.Name)
			if level == stage.Value+1 and humanoid.Health > 0 then 
				stage.Value = level 
				player.RespawnLocation = checkpoint 
			end	
			task.wait(0.1)
			Debounce = false
		end
	end)
end

PS: to avoid all this and to ensure everything runs you can wrap your if statement inside a pcall, although it might be a more expensive solution.

2 Likes

You realise the if statement can’t raise an error right?
if Instance:FindFirstChild() then will either return the found instance which is truthy or nil which is falsy, unless the instance itself through which FindFirstChild() is called doesn’t exist the conditional statement will not error.

In this case “touched.Parent” will always be a reference to an instance because everything is a descendant of the workspace.

The if statement itself wont cause an error, but the code wrapped in it will in some occasions. There is a chance the player character loads before their data causing something in the path of Players.leaderstats.Stage to be nil, which will then raise an error related to indexing nil breaking the script and preventing the Debounce from being set to false.

Then you could simply add WaitForChild() commands on the leaderstats folder & the Stage stat inside of it, although by the time a player is able to touch a new checkpoint those should have long been loaded.

I had already fixed the possible debounce issues in my implementations & I wouldn’t handle debouncing in the way you have.

checkpoint.Touched:Connect(function(touched)
	if not Debounce then
		Debounce = true

In this case any touching BasePart will cause the debounce to be activated.

If WaitForChild yields it will also cause the same issue. Also we have no way to “prevent” the amount of time it will take for user data to load, cause in some occasions datastores may be down/or slow(And we can’t wait in general cause other users will be unable to use the checkpoint).

The leaderstats folder and Stage stat are likely parented to the player upon them joining in which case they wouldn’t raise an infinite yield warning. Relying on values to be retrieved from a DataStore isn’t relevant (worst case a player has to wait a little while for his/her stats to load) but the script will still have all the necessary references to instances to perform as necessary.

This is something we have no way to prove, without having access to how the data is being loaded, therefore it’s better not to assume it. Also it’s better not to discuss Datastores as I consider it off-topic.

@Forummer @NyrionDev the leaderstats.Stage.Value is set to 1 by default before it’s parented to the player. So even if the data takes some time to load it’ll never be nil.

Under what circumstances would a leaderstats folder and subsequent values be parented to the player long after they have joined?

There’s our confirmation, thank you, I’ve also slightly modified the earlier version.

local Checkpoints = workspace:WaitForChild("Checkpoints")
local Debounce = false
local Players = game:GetService("Players")

for _, checkpoint in ipairs(Checkpoints:GetChildren()) do
	checkpoint.Touched:Connect(function(touched)
		if Debounce then
			return
		end
		if touched.Parent:FindFirstChild("Humanoid") then
			Debounce = true
			local Humanoid = touched.Parent:FindFirstChild("Humanoid")
			local Character = touched.Parent
			local Player = Players:GetPlayerFromCharacter(touched.Parent)
			if Player and Character and Humanoid then
				local stageNum = tonumber(checkpoint.Name)
				local leaderstats = Player:WaitForChild("leaderstats")
				if leaderstats then
					local Stage = leaderstats:WaitForChild("Stage")
					if Stage then
						if stageNum == Stage.Value + 1 and Humanoid.Health ~= 0 then
							Stage.Value = stageNum
							Player.RespawnLocation = checkpoint
						end
					end
				end
			end
		end
		task.wait(0.5)
		Debounce = false
	end)
end

A lot of unnecessary waits and checks but it’ll essentially ensure everything in each line exists before executing the next.

Then the issue must be related to WaitForChild("Humanoid") causing an infinity yield. As said above my solution is a bit more over-complicated than @Forummer but it will ensure none of this happen. In the worst case scenario it’s better to pcall anything that might cause Debounce to never reset.

Okay I will try your solution and @NyrionDev 's solution. Thank you. I’ll just put one reply as the solution since so this post appears solved.

However, I’m not sure how I’ll be able to test it out without possibly losing all the players again :sweat_smile:

Yeah that’s true, a pcall would be the safest scenario. But it’s probably way to expensive to run it for .Touched events by 200 checkpoints so I’ll only use it if there’s really no other option.

1 Like

I’m still curious as to why in every solution but mine the debounce variable is being declared locally inside the loop.

You just end up redeclaring the same variable hundreds of times.

It’s a debounce for each different checkpoint, although I wonder why the creator sets it to true for all the parts touching it(not only the characters).