The Laser That Actually Kills
A killbrick is four lines, and three of them exist to answer the question Touched refuses to: whose leg was that. Trace one touch from the limb it hit all the way back to the player it belonged to.
Worth reading first: Reaching Into the Tree From Code
A killbrick is four lines, and three of them exist to answer the question Touched refuses to: whose leg was that. Trace one touch from the limb it hit all the way back to the player it belonged to.
What Touched actually hands you
You know the shape already. What matters here is being precise about the argument, because everything else in the chapter follows from it.
local laser = script.Parent laser.Touched:Connect(function(otherPart) print(otherPart.Name) --> LeftLowerLeg print(otherPart.ClassName) --> MeshPart print(otherPart.Parent.Name) --> Amaraend)A leg. Not a player, not a character, not something with health. The player's name is one level up, and the thing that can actually be damaged is up there too — as a sibling of the leg, not as one of its children.
A character model is flat: every limb, the Humanoid, and the HumanoidRootPart all sit as direct children of the model. That flatness is what makes one step up exactly the right distance, and it is also what breaks when a hat is involved.
Climbing from a limb to a Humanoid
The Humanoid is the object that makes a model a character. It holds Health, controls walking and jumping, and is what dies. Damage is something you do to a Humanoid, never to a part.
laser.Touched:Connect(function(otherPart) local humanoid = otherPart.Parent.Humanoid -- works, and is fragile humanoid.Health = 0end)This is the version in most tutorials and it is one falling brick away from breaking the laser for everybody. A brick's parent is the Workspace, the Workspace has no child called Humanoid, so the lookup returns nil and the next line throws. An unhandled error inside a Touched handler kills that invocation — and the laser now has a stack trace in the Output window and a bug nobody can reproduce on purpose.
FindFirstChildWhichIsA is the safe climb
Two changes make it correct. Search by class rather than by name, and check the result before using it.
local laser = script.Parent laser.Touched:Connect(function(otherPart) local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid") if not humanoid then return end humanoid:TakeDamage(100)end)Searching by class rather than name matters because nothing guarantees the Humanoid is called "Humanoid". It usually is, and a custom character rig or a badly behaved free model can rename it, at which point a name lookup fails and a class lookup does not.
The if not humanoid then return end is the whole safety of the script. It is the guard from the chapter on nil, in the place it matters most.
- 1otherPart→Part "LeftLowerLeg"
- 2otherPart.Parent→Model "Amara"
- 3:FindFirstChildWhichIsA("Humanoid")→Humanoid
- 4humanoid:TakeDamage(100)→Health 100 → 0
The player dies. This is the case the killbrick was written for.
Every limb of a character is a direct child of the character model, and the Humanoid is a sibling of those limbs. One step up is exactly the right distance.
The hat case is worth dwelling on, because it is a real bug that ships in real experiences. A hat's Handle lives inside an Accessory, which lives inside the character — so otherPart.Parentis the Accessory, and the Humanoid is the Accessory's sibling rather than its child. The search finds nothing and the player walks through your laser wearing a top hat.
Do not fix it with a second .Parent. That would break the ordinary leg case, which is far more common. Search upward instead, and both work.
laser.Touched:Connect(function(otherPart) local character = otherPart:FindFirstAncestorOfClass("Model") if not character then return end local humanoid = character:FindFirstChildWhichIsA("Humanoid") if not humanoid then return end humanoid:TakeDamage(100)end)TakeDamage and Health are different choices
Both will kill a player and they are not interchangeable.
humanoid:TakeDamage(n)
Subtracts n, and respects a ForceField. A player who has just spawned is protected, which is what stops a laser next to the spawn point killing people before they can move.
humanoid.Health = 0
Sets health directly and ignores every protection. Correct for a void floor where nothing should survive; wrong as a default, because it makes spawn protection meaningless.
Prefer TakeDamage unless you have specifically decided that nothing should save the player. Passing 100 kills a default character outright, and passing 25 gives you a laser that takes four hits — which is a design decision you now get to make.
Getting from a character to a Player
A character is a model in the Workspace. A Player is the account behind it, living in the Players service, and holding everything that should survive a death — leaderstats, checkpoints, settings.
local Players = game:GetService("Players")local laser = script.Parent laser.Touched:Connect(function(otherPart) local character = otherPart:FindFirstAncestorOfClass("Model") if not character then return end local player = Players:GetPlayerFromCharacter(character) if not player then return end -- an NPC has a Humanoid and no Player local humanoid = character:FindFirstChildWhichIsA("Humanoid") if humanoid then humanoid:TakeDamage(humanoid.MaxHealth) print(player.Name, "hit the laser") endend)GetPlayerFromCharacterreturns nil for anything that is not a real player's character, which makes it a precise filter as well as a lookup. An NPC with a Humanoid passes every earlier check and fails this one — so if you want the laser to kill NPCs too, this is the line you leave out on purpose.
Key takeaways
- Touched hands you a part. The player's name is one level up, and the damageable object is a sibling of that part.
- A character model is flat — every limb, the Humanoid, and the HumanoidRootPart are direct children of it.
- otherPart.Parent.Humanoid throws the moment a falling brick touches the laser, and an unhandled error kills that invocation.
- FindFirstChildWhichIsA("Humanoid") searches by class, so it survives a rig whose Humanoid was renamed.
- if not humanoid then return end is the whole safety of the script. Without it, one brick produces a stack trace.
- A hat's Handle is one level deeper than a limb. FindFirstAncestorOfClass("Model") handles both; a second .Parent breaks the common case.
- TakeDamage respects ForceFields and spawn protection; setting Health = 0 ignores everything. Prefer TakeDamage.
- Use TakeDamage(humanoid.MaxHealth) rather than a literal 100, in case MaxHealth is not the default.
- GetPlayerFromCharacter returns nil for NPCs, which makes it a filter as well as a lookup.
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.