Code That Waits to Be Told
Nothing in an obby sits in a loop asking whether a player has arrived — the part announces it. Connect a function to Touched, walk across the part once, and count how many times it actually fired.
Worth reading first: Where Code Goes, and Where It Runs
Nothing in an obby sits in a loop asking whether a player has arrived — the part announces it. Connect a function to Touched, walk across the part once, and count how many times it actually fired.
An event is something the engine announces
The obvious way to write a killbrick is a loop: check every frame whether a player is standing in the laser, and if so, kill them. It works, it burns a slice of every frame forever, and it is wrong in a way that gets worse with every part you add.
Roblox inverts it. The part already knows when something touched it — the physics engine worked that out as part of the simulation it was doing anyway. So rather than you asking, it tells you.
-- The loop nobody should write:while true do -- check every part in the workspace, every frame, forever task.wait()end -- What the engine offers instead:script.Parent.Touched:Connect(function(otherPart) print("something touched me:", otherPart.Name)end)Connect hands over a function, not a result
Read that line carefully, because the shape of it is the thing to learn. Touched is a property of the part holding an event object. Connect is a method on that event. What you hand it is a function — not the result of calling one.
local function onTouch(otherPart) print("hit by", otherPart.Name)end part.Touched:Connect(onTouch) -- correct: hands over the functionpart.Touched:Connect(onTouch()) -- wrong: calls it now, connects the resultThe second version runs onTouch immediately, with no argument, and connects whatever it returned — which is nil. Nothing is connected, the touch never fires anything, and the error you got happened once at startup and scrolled away.
The anonymous form is the same thing written inline, and it is what you will see in almost every example.
part.Touched:Connect(function(otherPart) print("hit by", otherPart.Name)end)Touched passes you a part, not a player
The argument your function receives is the other partin the collision. Not a player, not a character — a single part, which might be a player's left leg, a falling brick, or another piece of your own obby.
This trips up every beginner exactly once. otherPart.Nameon a player's limb prints "LeftLowerLeg", and there is no otherPart.Health to set, because a leg does not have health. The next chapter of Part 4 is entirely about climbing from that limb to the player it belongs to.
A handful of other events matter in an obby, and they follow the same shape.
Something started touching this part. Gives you the other part.
Something stopped touching it. Less reliable than it sounds — a part destroyed mid-touch may never fire it.
Somebody joined. Gives you the Player object.
That player's character spawned, which happens again on every respawn.
That character died. The right place to hook a checkpoint respawn.
One crossing fires Touched many times
Here is the part that surprises everyone, and it is not a bug in your code or in Roblox. A character is made of many parts, each of which touches the plate separately. Physics jitter makes contacts break and re-form. One deliberate step onto a platform produces somewhere between ten and forty Touched events in well under a second.
14 events → 14 handler runs
Fourteen copies of your handler are now running at once, each convinced it is the only one. If that handler removes the platform and puts it back, thirteen of them will put it back on a schedule nobody planned.
If your handler prints a line, you get forty lines. If it awards a coin, you get forty coins. If it removes the platform, waits, and puts it back, you now have forty overlapping timers all putting the platform back at different moments — which is how a disappearing platform ends up permanently gone.
The fix is a debounce, and it is the whole of the next chapter.
A connection you never disconnect stays
Connect returns a connection object, and most of the time you ignore it. Sometimes you should not.
local connectionconnection = part.Touched:Connect(function(otherPart) print("first touch only") connection:Disconnect()end)A connection keeps the function — and everything the function refers to — alive for as long as the connection exists. Connect inside a loop without disconnecting and you accumulate handlers: after ten rounds, ten copies of the same function fire on every touch, and the game gets slower for reasons that never appear in the Output window.
Key takeaways
- Events invert the question: the engine already knows something touched the part, so it tells you rather than you checking every frame.
- Connect takes a function, not a call. Connect(onTouch()) runs it immediately and connects nil — nothing fires and nothing warns you.
- Touched hands you the other part, not a player. A player's limb has a Name and no Health.
- TouchEnded is less reliable than Touched — a part destroyed mid-touch may never fire it.
- One step onto a plate fires Touched ten to forty times, because a character is many parts and contacts jitter.
- Every one of those firings runs your whole handler. Forty coins, forty prints, forty overlapping timers.
- Connect returns a connection you can Disconnect, which is how you make a handler fire once.
- Connections keep their function alive. Connecting once per round to something long-lived leaks handlers and slows the game silently.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.