Skip to main content

Stage 4: KillBrick Path + dash & damage buzz

Course progressStage 4 of 10
~50 min
Before you start

Make sure you've finished Stage 3: Plank Walkway + low gravity. Your VRController should hold the climb, comfort, and edge-warning blocks — including the setBuzz helper, which you'll reuse today.

Build

a deadly KillBrick path and a dash pad that flings you across it

Learn

how a touch can deal damage, how to dash forward, and how to feel a hit in VR

Ship

a hazard you beat with a forward dash, with a damage buzz when a brick catches you

The big idea

So far the world has helped you. Now it fights back. A KillBrick is a part that ends your run the instant you touch it — the classic obby hazard. You'll build a path lined with them across a deadly gap.

Walking across is impossible. So you get a new move: the dash. Stage 2's jump pad threw you straight up; the dash throws you forward, in whatever direction you're facing. Point yourself across the gap, hit the pad, and you blast over the kill bricks. The secret is LookVector — every part knows which way it's pointing, and so does your character.

The VR half is about consequences. On a screen, dying is a message. In a headset, we make it physical: the moment a kill brick touches you, both controllers slam with a hard buzz. You'll reuse the exact setBuzz helper you wrote in Stage 3 — proof that the VRController script is becoming a real toolbox.

New words
KillBrick
a part that sets the player's health to 0 when touched — instant respawn
Health / Humanoid
the Humanoid holds the character's Health; at 0, the character dies and respawns
LookVector
the direction a part or character is facing, as a unit Vector3 from its CFrame
HealthChanged
an event the Humanoid fires whenever health goes up or down — how we notice a hit
attribute scan
looping through parts to find the ones you tagged — the same trick as Climbable in Stage 1

Build it

Step 1 — Build the KillBrick path

A platform with a deadly gap, lined with glowing red bricks.

Build this part

HazardPath

Block
Open recipe
Size
10 × 1 × 30
Color
Medium stone grey
Material
Slate
Anchored
✓ Yes
Place
Just past the Stage 4 pad — but leave a wide GAP in the middle of its length

Build this as two platforms (a near half and a far half) with open space between them. The gap is what the dash crosses.

Now place three kill bricks in and around the gap — the things that punish a bad jump.

Build this part

KillBrick_1

Block
Open recipe
Size
10 × 1 × 4
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
At the near edge of the gap

Red Neon is the universal 'this hurts' signal. After building, add the attribute in Step 1.1.

Build this part

KillBrick_2

Block
Open recipe
Size
10 × 1 × 4
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
In the middle of the gap (a floating brick to dash over)
Build this part

KillBrick_3

Block
Open recipe
Size
10 × 1 × 4
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
At the far edge of the gap

1.1 Tag each KillBrick

Just like Climbable in Stage 1, we tag the dangerous parts so one script can find them all.

  • Select KillBrick_1. In Properties → Attributes, click +.
  • Name: Killer. Type: boolean. Value: checked (true).
  • Do the same for KillBrick_2 and KillBrick_3.

Step 2 — Wire the kill bricks with one script

One server Script finds every Killer part and makes it deadly — the same scan-by-attribute pattern as Stage 1, now on the server.

  • In Explorer, right-click ServerScriptServiceInsert ObjectScript. Rename it KillBricks.
  • Type:
for _, part in ipairs(workspace:GetDescendants()) do
if part:IsA("BasePart") and part:GetAttribute("Killer") then
part.Touched:Connect(function(hit)
local humanoid = hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)
end
end

Press ▶ Play and touch a red brick. You should instantly respawn at the Stage 4 pad. One script now powers every kill brick in your whole game — add another Killer part later and it just works.

Step 3 — Build and script the dash pad

The dash pad throws you forward across the gap.

Build this part

DashPad

Block
Open recipe
Size
6 × 1 × 6
Color
Magenta
Material
Neon
Anchored
✓ Yes
Place
On the near platform, a few studs before KillBrick_1, facing across the gap

Insert a server Script into DashPad and type:

local pad = script.Parent
local DASH_POWER = 120 -- forward speed
local UP_POWER = 50 -- a little lift so you arc, not skid
local onCooldown = {}

pad.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
local root = character and character:FindFirstChild("HumanoidRootPart")
if not (humanoid and root) then return end
if onCooldown[character] then return end
onCooldown[character] = true

-- Dash in the direction the PLAYER is facing, with a little lift.
local forward = root.CFrame.LookVector
root.AssemblyLinearVelocity = forward * DASH_POWER + Vector3.new(0, UP_POWER, 0)

task.wait(0.8)
onCooldown[character] = nil
end)

Press ▶ Play. Face across the gap, step on the magenta pad, and dash over the kill bricks. Tune DASH_POWER and UP_POWER until the arc clears the bricks and lands on the far platform. Fully testable today.

Step 4 — Place the Stage 5 checkpoint

  • Insert a SpawnLocation on the far platform. Size [6, 1, 6], anchored, a new color (try Bright violet). Check AllowTeamChangeOnTouch, uncheck Neutral, match TeamColor.
  • Add a StageNumber attribute (number) = 5.
  • Add a Team named Stage 5, matching TeamColor, AutoAssignable unchecked.

Play, dash across, touch the pad, reset — you should respawn there.

Step 5 — Add the VR damage buzz

Open VRController. The damage buzz reuses the setBuzz helper you wrote in Stage 3, so you don't redefine it. Paste this block at the bottom of the file:

-- ===== VR damage buzz (added in Stage 4) =====
-- Reuses setBuzz() from the Stage 3 edge-warning block.
local function watchHealth(character)
local humanoid = character:WaitForChild("Humanoid")
local lastHealth = humanoid.Health

humanoid.HealthChanged:Connect(function(health)
if VRService.VREnabled and health < lastHealth then
local damage = lastHealth - health
local strength = math.clamp(damage / humanoid.MaxHealth, 0.3, 1)
setBuzz(strength)
task.delay(0.2, function() setBuzz(0) end)
end
lastHealth = health
end)
end

if player.Character then watchHealth(player.Character) end
player.CharacterAdded:Connect(watchHealth)

Press ▶ Play on your laptop. Touch a kill brick — you still die and respawn, Output stays clean, and (no headset) nothing buzzes. That clean run is today's pass; the slam of a hit is verified at the Stage 10 playtest.

In VR

Clip a kill brick in a headset and both controllers hit you with a hard jolt at the exact instant you die — a full-strength buzz because a kill brick takes all your health at once. A smaller scrape (if you add weaker hazards later) buzzes softer. Your hands learn the danger of red, fast.

Script anatomy

How the damage buzz turns a hit into a feeling

No new pattern here — it's the same 'react to a change' idea as the jump-pad debounce and the edge warning. We watch the Humanoid's health and buzz proportionally to how hard we got hit.

local function watchHealth(character)
local humanoid = character:WaitForChild("Humanoid")
local lastHealth = humanoid.Health

humanoid.HealthChanged:Connect(function(health)
if VRService.VREnabled and health < lastHealth then
local damage = lastHealth - health
local strength = math.clamp(damage / humanoid.MaxHealth, 0.3, 1)
setBuzz(strength)
task.delay(0.2, function() setBuzz(0) end)
end
lastHealth = health
end)
end

if player.Character then watchHealth(player.Character) end
player.CharacterAdded:Connect(watchHealth)
  1. Lines 1–3Find this life's Humanoid and remember its health.

    Each time you respawn you get a brand-new character, so we wrap this in a function and wait for the Humanoid. lastHealth starts as full health — the value we'll compare every change against.

  2. Lines 5–6Only react to damage, only in VR.

    HealthChanged fires for healing too, so we check health < lastHealth to catch only hits. The VREnabled guard keeps a laptop quiet.

  3. Lines 7–8Scale the buzz to the hit.

    damage is how much health we lost. Dividing by MaxHealth turns it into a fraction (a kill brick = 1.0, a small scrape = less). math.clamp keeps it between 0.3 and 1 so even a tiny hit is felt but nothing exceeds full power.

  4. Lines 9–10Buzz, then stop a fraction of a second later.

    setBuzz(strength) — the same helper from Stage 3 — fires both controllers. task.delay schedules setBuzz(0) after 0.2s so the jolt is a punch, not a constant rumble.

  5. Lines 16–17Re-watch health every time you respawn.

    We watch the current character now, and CharacterAdded re-runs watchHealth on every future respawn. Forget this and the buzz works once, then never again after your first death.

Understand it

A kill brick is just a Touched handler that sets Health to 0. We wired all three with a single scan for the Killer attribute — the same pattern as Stage 1's climbable walls, which is the point: you're not memorizing a new trick per stage, you're reusing one good idea (tag parts, scan for the tag, give them behavior). One script powering every hazard in the game also means tuning is one place, not three.

The dash is AssemblyLinearVelocity again, but aimed with LookVector. LookVector is the forward direction baked into every CFrame, so forward * DASH_POWER is "go that way, this fast." Adding a small upward push turns a flat skid into an arc that clears the bricks. We kept the debounce from Stage 2 because Touched still fires many times per step — that lesson doesn't expire.

The damage buzz is the most reusable idea yet: watch a value, react to its change. Health is just a number; when it drops, we translate the size of the drop into the strength of a buzz. That's the whole loop of good game feedback — and it's why we scaled by MaxHealth instead of buzzing a fixed amount. A game that buzzes the same for a scratch and a death teaches your hands nothing.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first

The dash uses root.CFrame.LookVector — the way the player faces. Predict what happens if you face sideways (or backward) and hit the pad. Then do it. Why is "the dash goes where you look" better game design than "the dash always goes north"?

Compare

In the damage buzz, math.clamp(..., 0.3, 1) sets a floor of 0.3. Imagine removing the floor so tiny hits barely buzz. For a kill brick (full damage) it makes no difference — so when would the floor matter? (Hint: think about the weaker hazards in later stages.)

Connect

You now have a Killer attribute and a scan that powers every tagged part. Stage 6's hidden hazard field is full of dangerous parts. How much of today's KillBricks script will you have to change to make those deadly too? (Answer: almost none — that's the power of the pattern.)

Test your stage

  • Press ▶ Play and touch a red brick — you respawn instantly at the Stage 4 pad.
  • Face across the gap, step on the magenta DashPad, and clear the kill bricks in one arc.
  • Tune DASH_POWER / UP_POWER so the dash reliably lands on the far platform — not short, not into orbit.
  • Touch the Stage 5 pad, reset, and confirm you respawn there.
  • Output is empty on a clean Play — no errors from KillBricks, DashPad, or VRController.
  • Design check. Do the red bricks read instantly as "danger" and the magenta pad as "use me"? Color is your warning system; in VR it's read in a split second.

If it breaks

  • Touching a brick doesn't kill me. Each brick needs Killer = true in Attributes, and the KillBricks script must be in ServerScriptService. Bricks added after the script runs won't be wired — this scan happens once at start.
  • The dash sends me the wrong way. It follows where you're facing. Turn to face across the gap before stepping on the pad. (That's the Predict prompt.)
  • The dash is too weak / too strong. Tune DASH_POWER (forward) and UP_POWER (lift) separately. Too little lift skids you into the bricks; too much sails you over the platform.
  • I dash once then it stops working. Debounce stuck — check onCooldown[character] is cleared after task.wait.
  • The buzz only worked on my first death (in VR). You skipped the CharacterAdded line — without it, the new character after respawn is never watched.
Coach notes

This is the first stage where the world hurts the player, and 10–13s love it. Lean into the design check: a kill brick that isn't obviously red-and-glowing is a "gotcha," and gotchas feel unfair. Push campers to make danger readable.

The reuse story is the lesson here. Say it explicitly: "the Killer scan is the same shape as Stage 1's Climbable scan, and setBuzz is the exact helper you already wrote." When campers notice they're recombining old parts instead of learning new ones, that's real programming clicking into place.

Damage buzz is unfeelable today — clean Play is the pass. The most common silent bug is the missing CharacterAdded line; it won't error, the buzz just quietly dies after one respawn. Flag it now so it's not a mystery at the headset playtest.