Roblox Touched Events Explained (With Kill Brick Example)

Quick answer: The `.Touched` event fires on the server whenever a part physically contacts another part, and its `hit` argument is a BasePart, never a player directly. To get the player, resolve `hit.Parent` through `Players:GetPlayerFromCharacter`, then use a debounce so rapid collisions do not fire your logic dozens of times per second.

If you want something to happen when a player physically touches a part in Roblox, you connect a function to that part's .Touched event. Every time the part collides with another part, Roblox calls your function once per contact and passes in the other part as the hit argument — which is the single most misunderstood detail of this event, because beginners expect the argument to be the player.

What exactly does the Touched event give you?

The parameter of a .Touched connection is always a BasePart — the specific part that made contact. If a character runs into your part, hit might be their left foot, their head, or any other limb. It will never be a Player object, and it will never be the character Model itself.

local part = script.Parent

part.Touched:Connect(function(hit)
	print(hit.Name)          -- e.g. "LeftFoot", "Handle", "Part"
	print(hit.Parent.Name)   -- e.g. a character model named after the player
end)

This matters because almost every practical use of Touched starts with the question: which player caused this touch?

How do I get the player from the hit part?

Character models in Roblox are named after their player, and every part of that character lives inside the model. That gives you two reliable ways up the hierarchy:

  1. Pass hit.Parent to Players:GetPlayerFromCharacter() — returns the Player if that model is a character, otherwise nil.
  2. For accessory handles and tools, the character may be hit.Parent.Parent, so some scripts walk up until they find a Humanoid.

The idiomatic version looks like this:

local Players = game:GetService("Players")
local part = script.Parent

part.Touched:Connect(function(hit)
	local character = hit.Parent
	local player = Players:GetPlayerFromCharacter(character)

	if player then
		print(player.Name .. " touched the part")
	end
end)

Never skip the nil check. hit could be the floor of the map, a moving platform, or a stray mesh — anything with collision triggers Touched, and GetPlayerFromCharacter will return nil for all of them.

Why does my Touched code fire dozens of times?

A single step onto a part produces many physical contacts between limbs and surfaces. The physics engine does not send one tidy "the player arrived" message — it sends a burst of contact events as feet, legs, and torso brush the surface. If you deal 10 damage on every firing, a one-second stroll across a trap costs a player their whole health bar.

The standard fix is a debounce: record that a player is already being processed and ignore further touches until a cooldown expires.

local Players = game:GetService("Players")
local part = script.Parent
local COOLDOWN = 1

local debounce = {} -- keyed by player UserId

part.Touched:Connect(function(hit)
	local player = Players:GetPlayerFromCharacter(hit.Parent)
	if not player then return end

	if debounce[player.UserId] then return end
	debounce[player.UserId] = true

	-- ... your real logic here ...

	task.wait(COOLDOWN)
	debounce[player.UserId] = nil
end)

game:GetService("Players").PlayerRemoving:Connect(function(player)
	debounce[player.UserId] = nil -- avoid leaking entries for departed players
end)

Keying the table by UserId (rather than by the player object) also keeps things clean when players leave mid-cooldown. A simple boolean variable works fine too when only one thing can ever touch the part.

Is TouchEnded reliable?

Every Touched connection has a mirror event, .TouchEnded, which fires when contact stops. In theory you can build "standing on a pad" logic with the pair. In practice, TouchEnded is notoriously flaky: parts can stop registering contact without a clean separation event, especially with fast-moving characters, network ownership handoffs, or welded assemblies. If your mechanic must know when a player leaves a zone — a capture point, for example — poll positions on a heartbeat loop instead and treat TouchEnded as best-effort sugar.

How do I react only to certain body parts?

Since hit is a BasePart, you can filter by name. Characters contain standard parts such as Head, Torso/UpperTorso, and HumanoidRootPart. Checking hit.Name == "HumanoidRootPart" restricts your logic to roughly one contact per character, which pairs nicely with a debounce:

part.Touched:Connect(function(hit)
	if hit.Name ~= "HumanoidRootPart" then return end
	-- now handle the torso-level contact
end)

Note that R15 rigs split limbs differently from R6, but both include HumanoidRootPart, making it the safest name to filter on across avatar types.

Full example: a proper kill brick

Here is a complete, server-side kill brick. Place this Script (not a LocalScript) inside a Part:

local Players = game:GetService("Players")

local part = script.Parent
local COOLDOWN = 2

local debounce = {}

local function onTouch(hit)
	local character = hit.Parent
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	local player = Players:GetPlayerFromCharacter(character)

	if not (humanoid and player) then return end
	if humanoid.Health <= 0 then return end

	if debounce[player.UserId] then return end
	debounce[player.UserId] = true

	humanoid:TakeDamage(humanoid.MaxHealth) -- instant elimination

	task.wait(COOLDOWN)
	debounce[player.UserId] = nil
end

part.Touched:Connect(onTouch)

Three deliberate choices worth copying:

If you want a variant that deals partial damage per touch, replace the TakeDamage line with humanoid:TakeDamage(25) and keep the cooldown short. To reward points when the trap lands a hit, write to the victim's stats using the classic leaderstats convention described in our leaderstats tutorial.

Where should the script live?

A Script inside the Part works and is the most common pattern for one-off traps. For larger games with many hazards, put a single handler in ServerScriptService and connect it to every hazard via a CollectionService tag — one listener architecture scales better than dozens of identical scripts. Either way, keep it on the server: the same reasoning applies to input handling, where client-side detection through UserInputService feeds RemoteEvents rather than acting unilaterally, a flow we break down in the remote events guide.

Learn Luau by doing

Reading about events only gets you halfway — you need to wire a Touched connection, watch the burst of duplicate firings yourself, and fix it with a debounce to really understand it. On Luablox lessons you do exactly that in guided interactive exercises, the /theory reference explains the event model behind signals, and the /problems section gives you hands-on challenges including input detection covered in our guide on detecting key presses. Start free and ship your first working trap today.

Frequently asked questions

Why does my Touched script error with "attempt to index nil"?

Because `hit.Parent` was not a character. Parts like the map floor, other players accessories handles, or anchored scenery can touch your part, and `GetPlayerFromCharacter` returns nil for all of them. Always check that the result is not nil before using it.

Why does my kill brick damage the player many times in one touch?

Physics engines register multiple contact events during a single collision. Without a debounce flag that ignores repeated fires for a short interval, your damage code runs several times. Add a debounce table keyed by player and reset it after your cooldown.

Can I use TouchEnded to detect when a player steps off a platform?

You can, but TouchEnded is unreliable for gameplay decisions because it can fire late or not at all when parts separate without a clean physics separation. Prefer polling distance or position checks in a loop for critical mechanics, and treat TouchEnded as a convenience only.

Should Touched damage run on the client or the server?

Always the server when it affects gameplay. Client-side damage can be spoofed by exploiters and will not replicate to other players. Put kill brick scripts in ServerScriptService or a server Script inside the part itself.

How do I make a kill brick ignore NPCs or teammates?

After resolving the character, check the Humanoid or team before dealing damage. For example, skip characters whose Humanoid is missing, or compare `player.Team` against the brick allowed teams before calling TakeDamage.

Learn core Roblox scripting and Luau programming concepts, including: roblox touched event, kill brick script roblox, ontouched roblox, getplayerfromcharacter, touched debounce roblox.

Related pages