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")
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
@Y_VRN@regexman@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.
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.
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.
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.
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).