How to Make a Kill Brick in Roblox (Touched Events)
Last updated:
Quick answer: A kill brick is a server Script that listens to a part's Touched event, checks hit.Parent for a Humanoid, and sets Humanoid.Health to 0 — with a debounce keyed on os.clock() so the event firing many times per contact cannot kill or damage the player over and over.
A kill brick is a part that kills (or damages) whatever player touches it, and the whole thing is one server Script with a Touched event connection: when a limb touches the part, you look for a Humanoid in the touching part's parent and set its health to zero. The only real subtlety is that Touched fires far more often than it appears it should — once per limb, repeatedly while overlapping — so a production kill brick needs a debounce. Below is the minimal script, an honest look at how Touched actually fires, the debounce fix, and variants for lava floors and damage zones.
What does the simplest kill brick script look like?
Create a Part in Workspace, insert a normal Script into it (not a LocalScript — more on that below), and paste this:
local brick = script.Parent
brick.Touched:Connect(function(hit)
local humanoid = hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)
That is the whole kill brick, and here is what every line does:
local brick = script.Parent— the script sits inside the part, soscript.Parentis the brick itself. If you prefer, you can reference the part by name withworkspace.KillBrickinstead.brick.Touched:Connect(function(hit) ... end)—Touchedfires whenever something physically contacts the part. Thehitargument is the specific part that made contact — a player's LeftFoot, their Torso, a dropped tool, a rolling ball — never the player object directly.hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid")— characters are Models whose direct children include the limbs and aHumanoid, so the character Model ishit.Parent.FindFirstChildOfClass("Humanoid")returns the Humanoid if that Model is a character, and nil if it is just a random part, which makes the check safe for debris.humanoid.Health = 0— setting health to zero kills the character, exactly like falling into lava. The alternativehumanoid:TakeDamage(25)subtracts damage without an instant kill.
Use :Connect with a capital C. The lowercase :connect still happens to exist for legacy reasons but is deprecated; modern Luau code always writes :Connect.
How does Touched actually fire?
This is where most beginners get surprised: one jump onto the brick does not produce one Touched call. The event is raised per part, per contact, so:
- A single player standing on the brick fires
Touchedonce for each limb that touches it — LeftFoot, RightFoot, Torso can each produce their own call within the same second. - If the player wiggles, falls, or gets pushed, the contact breaks and re-forms, firing the event again and again.
- The brick itself can be moving. A kill brick that slides into the player fires
Touchedjust like a player walking into it. - Both a collision start and tiny physics jitters raise it, so without a guard your callback can easily run ten to thirty times for what looks like one touch.
For a deeper dive into the physics behind this, see the Touched event guide. The practical takeaway for a kill brick: assume Touched fires many times per contact and design the callback to be idempotent, or gate it with a debounce.
There is a second trap hiding in hit: it is the part that touched, so hit.Parent is the character only when the touching part is a limb. Touched also fires for hats and accessories welded to the character — those live under a different parent level, which is why FindFirstChildOfClass("Humanoid") on hit.Parent quietly does nothing for them and you only kill when an actual limb hits. That is usually the behavior you want.
Why does my kill brick kill instantly multiple times, and how does debounce fix it?
Without a guard, every repeated Touched call sets health to zero again. For an instant kill that looks harmless, but the moment you switch to damage — humanoid.Health = humanoid.Health - 25 — one landing deals 250 damage because the callback ran ten times. Even for kills, re-running the callback fires your scoring code ten times and awards ten kills for one touch.
The fix is a debounce table: remember the last time each character touched, and ignore any call that arrives too soon after. Use os.clock() for timestamps — it is a monotonic timer meant for measuring elapsed time, and unlike the legacy tick() it is not affected by wall-clock changes.
local Players = game:GetService("Players")
local brick = script.Parent
local COOLDOWN_SECONDS = 1
local lastTouchAt = {} -- [character or player] = os.clock() timestamp
brick.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return
end
local player = Players:GetPlayerFromCharacter(character)
local key = player or character
local now = os.clock()
if lastTouchAt[key] and now - lastTouchAt[key] < COOLDOWN_SECONDS then
return -- still inside the cooldown window from the previous contact
end
lastTouchAt[key] = now
humanoid.Health = 0
end)
Players.PlayerRemoving:Connect(function(player)
lastTouchAt[player] = nil -- no leaks when players leave
end)
Walk through the moving parts:
if not humanoid or humanoid.Health <= 0 then return end— bail out early for non-characters and for corpses, so a body lying on the brick does not keep re-triggering.local key = player or character— keying the debounce by player keeps the table small and stable; keying by character works for NPCs. When a player resets,Humanoidspawns a fresh character, and since the key is the same player object the cooldown still applies across respawns — usually what you want for a hazard.os.clock()returns seconds as a float with sub-second precision. Comparingnow - lastTouchAt[key] < COOLDOWN_SECONDSis the entire debounce; no loops, no waiting.Players.PlayerRemovingcleans the table up. Skipping this is a slow memory leak in games with lots of churn.
If you inherited older code that used wait(), spawn(), delay(), or tick(), treat it as a migration callout: those globals are deprecated scheduling primitives with sloppy timing behavior. Replace wait(n) with task.wait(n), spawn(fn) with task.spawn(fn), delay(n, fn) with task.delay(n, fn), and every tick() timestamp with os.clock(). A kill brick needs none of these — the debounce above is purely event-driven.
How do you make a lava floor or a damage zone instead of an instant kill?
Same skeleton, different line at the bottom. For damage, replace humanoid.Health = 0 with:
local DAMAGE = 25
humanoid:TakeDamage(DAMAGE)
TakeDamage respects the Humanoid's MaxHealth and kills naturally at zero, and it is the idiomatic way to subtract health. A lava floor is just a large anchored part with this script and a short cooldown (around half a second) so a player standing in it takes steady damage instead of dying in one frame.
For a damage zone that only hurts while you stand in it, listen for both contact and separation:
local Players = game:GetService("Players")
local zone = script.Parent
local DAMAGE_PER_SECOND = 15
local inside = {} -- [character] = true while touching
zone.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid then
inside[character] = true
end
end)
zone.TouchEnded:Connect(function(hit)
local character = hit.Parent
if character then
inside[character] = nil
end
end)
task.spawn(function()
while true do
task.wait(1)
for character in inside do
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then
humanoid:TakeDamage(DAMAGE_PER_SECOND)
else
inside[character] = nil
end
end
end
end)
Here TouchEnded (the counterpart to Touched) marks when contact breaks, and one shared loop ticks damage once per second for everyone inside — much tidier than per-part timers.
Should the kill brick be a Script or a LocalScript?
Always a server Script. A LocalScript changing Humanoid.Health only changes the health on that one client: the server and every other player see nothing happen, and an exploiter can simply delete your LocalScript. Kill logic belongs on the server, which is the same authority principle behind Script vs LocalScript and server-validated currency.
How do you connect a kill brick to a scoring system?
Once the kill is server-side you can count it. The simplest version tracks deaths: when your kill brick fires, increment a leaderstat on the dying player's data. Because the debounce already guarantees one callback per contact, the counter stays honest.
local Players = game:GetService("Players")
local brick = script.Parent
local COOLDOWN_SECONDS = 1
local lastTouchAt = {}
local function onTouched(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return
end
local player = Players:GetPlayerFromCharacter(character)
local key = player or character
local now = os.clock()
if lastTouchAt[key] and now - lastTouchAt[key] < COOLDOWN_SECONDS then
return
end
lastTouchAt[key] = now
if player then
local deaths = player:FindFirstChild("leaderstats")
and player.leaderstats:FindFirstChild("Deaths")
if deaths then
deaths.Value += 1
end
end
humanoid.Health = 0
end
brick.Touched:Connect(onTouched)
If you want a scoring system where pushing someone into the brick awards the pusher, tag the character with CollectionService or a LastAttacker attribute when damage is dealt, then read that tag inside onTouched to find who to reward. The leaderstats tutorial covers building the stats folder this code expects.
Common mistakes
- Using a LocalScript. The kill happens only on one client, is trivially exploitable, and looks broken to everyone else. Use a server
Scriptinside the brick. - No debounce. The callback runs dozens of times per touch, which breaks damage math and inflates any counters you hook up. Gate with
os.clock()timestamps. - Assuming
hitis the character.hitis the touching part; the character ishit.Parent, and even then only for limbs. UseFindFirstChildOfClass("Humanoid")instead ofhit.Parent.Humanoid, which errors when the parent is not a character. - Forgetting
hit.Parentcan be nil. Tools, projectiles, and map debris touch bricks constantly. Guard withhit.Parent and ...or the script dies on its first stray contact. - Leaking the debounce table. Clear entries in
PlayerRemoving(and for NPCs when their character is destroyed), or long-running servers slowly accumulate dead keys. - Lowercase
:connector legacytick(). Modern Luau uses:Connectandos.clock(); the old spellings are deprecated and flagged by most linters. - Thin, fast-moving parts skipping contact. A very thin kill brick can be tunneled through by fast characters. Thicken the part or reduce character speed in that zone.
Learn Luau by doing
A kill brick is a perfect first gameplay system: one event, one condition, one effect. Once the debounce pattern clicks, the same shape powers lava, poison swamps, and racing reset zones. Practice the building blocks interactively on /theory, drill event handling on /problems, and build up to full game systems with structured Luablox lessons.
Frequently asked questions
Why does my kill brick kill instantly multiple times?
Because Touched fires once per limb per contact and re-fires constantly while the character overlaps the brick — one jump can produce ten to thirty calls. Add a debounce table keyed on the character or player using os.clock() timestamps, and ignore any call that arrives within your cooldown window.
How do I make a kill brick only kill players and not NPCs?
Use Players:GetPlayerFromCharacter(hit.Parent) and only run the kill when it returns a player. NPCs and mobs have a Humanoid but no associated player, so the check skips them cleanly.
How do I make a kill brick damage instead of kill?
Replace humanoid.Health = 0 with humanoid:TakeDamage(25) (or subtract from humanoid.Health) and keep the debounce, usually with a shorter cooldown around 0.5 seconds. TakeDamage is preferred because it handles max health and natural death at zero.
Can a kill brick be a LocalScript?
No. A LocalScript only changes Humanoid.Health on that single client, so other players and the server see nothing and exploiters can delete the script. Kill logic must live in a server Script so the server is the one setting health.
Why does my kill brick script error when random parts touch it?
The Touched event fires for every physical contact, including tools, projectiles, and map debris. hit.Parent can be anything and even nil. Guard with hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid") before touching health.
Learn core Roblox scripting and Luau programming concepts, including: how to make a kill brick in roblox, kill brick script, roblox touched event kill, roblox lava brick script, debounce roblox touched.