Skip to main content

Stage 7: Rolling Rocks + lean to dodge

Course progressStage 7 of 10
~50 min
Before you start

Make sure you've finished Stage 6: Hidden Hazard Field + reveal wave. You'll reuse your setBuzz helper (Stage 3) and the GetUserCFrame hand/head reading (Stage 1) — this time reading your head.

Build

a ramp of rolling boulders and a cover wall to hide behind

Learn

how to spawn moving hazards with physics, and how to read the player's real head

Ship

a gauntlet you survive by leaning behind cover, with a danger buzz that grows as rocks near your head

The big idea

Real boulders. A spawner at the top of a ramp drops heavy balls that roll down under gravity and flatten anything in their path. You don't fake the motion — you make a round, unanchored part and let Roblox's physics roll it, exactly like a rock would.

To survive, you hide behind the CoverBlock — a solid wall the boulders smash into instead of you. On a laptop you step behind it. In VR, you physically lean and step behind it: because Roblox VR is roomscale, moving your real body moves your avatar, so leaning behind the wall genuinely tucks you out of the boulder's path.

The new VR sense is in your head — literally. We've read your hands before; now we read your head with GetUserCFrame(Enum.UserCFrame.Head). We measure how close the nearest boulder gets to your real head and turn that into a buzz: peek out and the rumble swells; tuck behind cover and it goes quiet. You feel danger in your hands based on where your head is — a sixth sense for incoming rocks.

New words
BoulderSpawner
a part whose script creates a new rolling boulder every couple of seconds
Instance.new
code that builds a brand-new part while the game runs, instead of placing it in Studio
Folder
a container in the Explorer; we keep all spawned boulders in one so they're easy to find
Debris
a service that auto-deletes a part after a set time, so boulders don't pile up forever
UserCFrame.Head
the VR headset's position — your real head, which we read to sense danger
roomscale
VR where moving your real body moves your avatar, so leaning behind cover really hides you

Build it

Step 1 — Build the ramp, cover, and spawner

Build this part

RockRamp

Block
Open recipe
Size
16 × 1 × 40
Color
Medium stone grey
Material
Slate
Anchored
✓ Yes
Place
Just past the Stage 7 pad — tilt it so one end is high (boulders roll DOWN toward the player)

Rotate it to a gentle slope. Too steep and boulders fly; too flat and they stall.

Build this part

CoverBlock

Block
Open recipe
Size
6 × 8 × 2
Color
Brown
Material
WoodPlanks
Anchored
✓ Yes
Place
Near the bottom of the ramp, off to one side — your hiding spot

Tall and solid. Boulders smash into this instead of you.

Build this part

BoulderSpawner

Block
Open recipe
Size
4 × 1 × 4
Color
Bright red
Material
Neon
Anchored
✓ Yes
Place
At the TOP of the RockRamp, front face pointing down the slope

The boulders launch the way this part's front face points, then gravity rolls them.

Step 2 — Script the boulder spawner

Insert a server Script into BoulderSpawner. It builds a rolling boulder on a loop, gives it a push down the ramp, makes it deadly, and cleans it up.

local spawner = script.Parent
local Debris = game:GetService("Debris")

local SPAWN_EVERY = 2 -- seconds between boulders
local PUSH = 50 -- starting roll speed
local LIFE = 6 -- seconds before a boulder vanishes

-- One folder to hold every boulder, so other scripts can find them.
local folder = Instance.new("Folder")
folder.Name = "Boulders"
folder.Parent = workspace

while true do
local rock = Instance.new("Part")
rock.Shape = Enum.PartType.Ball
rock.Size = Vector3.new(8, 8, 8)
rock.Material = Enum.Material.Rock
rock.Color = Color3.fromRGB(90, 70, 50)
rock.Position = spawner.Position
rock:SetAttribute("Boulder", true) -- tag for the VR danger sense
rock.Parent = folder

-- Spawned parts aren't caught by the Stage 4 scan, so wire their own kill.
rock.Touched:Connect(function(hit)
local humanoid = hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)

-- A push down the ramp; gravity does the rest of the rolling.
rock.AssemblyLinearVelocity = spawner.CFrame.LookVector * PUSH

Debris:AddItem(rock, LIFE)
task.wait(SPAWN_EVERY)
end

Press ▶ Play. Boulders should roll down the ramp on a steady beat. Stand in the open — one flattens you and you respawn. Step behind the CoverBlock — you're safe. Tune PUSH, the ramp slope, and SPAWN_EVERY until the boulders feel dangerous but dodgeable. Fully testable today.

Why the boulder wires its own kill

Your Stage 4 KillBricks scan runs once at the start, so it never sees boulders made later. That's why we give each boulder its own Touched handler when it's created — the same lesson as the Stage 5 fireball stretch. Tagging alone isn't enough for parts born during the game.

Step 3 — Place the Stage 8 checkpoint

  • Insert a SpawnLocation past the ramp (a safe spot the boulders can't reach). Size [6, 1, 6], anchored, a new color (try Pink). Check AllowTeamChangeOnTouch, uncheck Neutral, match TeamColor.
  • Add a StageNumber attribute (number) = 8.
  • Add a Team named Stage 8, matching TeamColor, AutoAssignable unchecked.

Survive the boulders, touch the pad, reset — you should respawn there.

Step 4 — Add the VR head-tracked danger sense

Open VRController. This reads your real head position and buzzes harder as the nearest boulder gets closer to it. It reuses setBuzz (Stage 3) and the world-position math (Stage 1), now with the head instead of a hand. Paste at the bottom:

-- ===== VR boulder danger sense (added in Stage 7) =====
local DANGER_RANGE = 30 -- a boulder closer than this to your head starts the buzz
local boulderFolder = workspace:WaitForChild("Boulders")

RunService.RenderStepped:Connect(function()
if not VRService.VREnabled then return end

-- Your real head's position in the world (same math as the climb, but the head).
local headPos = (camera.CFrame * VRService:GetUserCFrame(Enum.UserCFrame.Head)).Position

-- Find the nearest boulder to your head.
local nearest = math.huge
for _, rock in ipairs(boulderFolder:GetChildren()) do
local dist = (rock.Position - headPos).Magnitude
if dist < nearest then
nearest = dist
end
end

-- Closer boulder = stronger buzz. Out of range = silence.
if nearest < DANGER_RANGE then
setBuzz(math.clamp(1 - nearest / DANGER_RANGE, 0.1, 1))
else
setBuzz(0)
end
end)

Press ▶ Play on your laptop. Output stays clean and nothing buzzes — no headset to read, no rumble. Today's pass is the clean run; the danger sense comes alive at the Stage 10 playtest.

In VR

Tucked behind the CoverBlock, you feel calm — the boulders pass on the far side, far from your head. Lean out to peek and the controllers start to hum, swelling to a hard rumble as a rock bears down. You learn to read the buzz like a danger radar: stay quiet behind cover, dart out only when your hands tell you it's clear. Leaning your real body is the dodge; the buzz is your warning.

Script anatomy

How the danger sense turns head distance into a buzz

Same shape as everything in VRController: check VR, read a device, react. The only new idea is reading the HEAD, and looping a small folder to find the nearest threat.

local DANGER_RANGE = 30
local boulderFolder = workspace:WaitForChild("Boulders")

RunService.RenderStepped:Connect(function()
if not VRService.VREnabled then return end

local headPos = (camera.CFrame * VRService:GetUserCFrame(Enum.UserCFrame.Head)).Position

local nearest = math.huge
for _, rock in ipairs(boulderFolder:GetChildren()) do
local dist = (rock.Position - headPos).Magnitude
if dist < nearest then
nearest = dist
end
end

if nearest < DANGER_RANGE then
setBuzz(math.clamp(1 - nearest / DANGER_RANGE, 0.1, 1))
else
setBuzz(0)
end
end)
  1. Lines 1–2How close is scary, and where the boulders live.

    DANGER_RANGE is the distance where the buzz begins. We grab the Boulders folder the spawner made, so we only loop the boulders — not the whole game — which keeps this fast.

  2. Line 7Read the head, not the hand.

    Exactly the Stage 1 climb math — camera CFrame times the device offset — but with Enum.UserCFrame.Head. That gives your real headset's spot in the world, the thing we measure danger against.

  3. Lines 9–15Find the single nearest boulder.

    We start nearest at math.huge (bigger than anything) and shrink it to the closest boulder's distance. Looping the folder each frame is cheap because it only holds the few boulders currently alive.

  4. Lines 17–21Turn distance into buzz strength.

    1 - nearest / DANGER_RANGE is 0 at the edge of range and near 1 when a boulder is on top of you. math.clamp keeps it between 0.1 and 1 so a far rock still gives a faint warning. Past the range, setBuzz(0) — silence.

Understand it

The boulders are your first spawned, physical hazard. Everything before was placed in Studio; here Instance.new builds parts while the game runs, and real physics — gravity on a round, unanchored part — does the rolling for free. We push them with AssemblyLinearVelocity (the same launch tool from Stage 2 and 5) just to start them moving down the slope. And because they're born mid-game, the Stage 4 scan can't see them, so each boulder wires its own Touched kill — the exact caveat the fireball stretch warned about. The Boulders folder and Debris:AddItem are housekeeping: one keeps them findable, the other keeps them from piling up until the game lags.

The danger sense is the head-reading lesson, and it completes your VR input set: you've now read hands (climb, reveal) and the head (this). The pattern never changed — camera.CFrame * GetUserCFrame(device) gives any VR device's world position — you just pointed it at a new device. Measuring boulder distance to your head (not your feet) is what makes leaning matter: peek your head into danger and the buzz spikes; keep it behind cover and you're calm. The dodge itself is pure physics and roomscale — you move your body, your avatar moves, the boulder hits the wall. The buzz doesn't dodge for you; it tells you when to.

Try this

Learning beat

Try this

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

Predict first

PUSH is 50 and the boulders also roll under gravity. Predict what happens to a boulder if you make the ramp much steeper and raise PUSH to 150. Will it still roll, or fly off the ramp? Test and find the slope that keeps them rolling menacingly.

Compare

The danger sense measures distance to your head. Imagine measuring to your feet (HumanoidRootPart) instead. In a roomscale dodge where you lean your upper body out while your feet stay put, which gives a truer warning — and what does that tell you about why VR designers care which body part they measure?

Connect

You now spawn parts with Instance.new and clean them with Debris. Stage 8 has a spinning arm you'll grab and ride. Would that arm be spawned-and-cleaned like a boulder, or built-once-in-Studio like the cover wall? Why does a thing you grab want to be permanent?

Test your stage

  • Press ▶ Play — boulders roll down the ramp on a steady beat.
  • Standing in the open, a boulder flattens you and you respawn.
  • Hiding behind the CoverBlock, the boulders smash the wall and you survive.
  • Reach the safe spot, touch the Stage 8 pad, reset, and confirm you respawn there.
  • Output is empty on a clean Play — no errors from the spawner or VRController.
  • Design check. Is there a rhythm a player can learn — a safe beat to dart out and back? A gauntlet with no learnable pattern is frustration, not challenge. Tune SPAWN_EVERY until there's a readable window.

If it breaks

  • Boulders don't roll / they stall. The ramp is too flat or PUSH is too low. Steepen the slope a little or raise PUSH. They should pick up speed as they descend.
  • Boulders fly off the ramp. Too steep, or PUSH too high. Ease both down.
  • Boulders don't kill me. Each boulder needs its own Touched handler (in the spawner) — the Stage 4 scan won't catch spawned parts. Check Output for errors in the spawner script.
  • Boulders pile up and the game lags. Debris:AddItem(rock, LIFE) is missing or LIFE is too long. Six seconds is plenty.
  • boulderFolder errors in VRController. It does workspace:WaitForChild("Boulders"), so the spawner must run and create that folder. Make sure the spawner script is in BoulderSpawner and not disabled.
  • Nothing buzzes on my laptop. Expected — no headset, so the loop is skipped. Verified at the Stage 10 playtest.
Coach notes

Two physics ideas land here: spawning with Instance.new and letting gravity do the work. Let campers over-tune first (boulders flying everywhere is hilarious and instructive), then guide them to a slope and beat that's actually fair. The "design check" — is there a learnable rhythm? — is the real lesson; a boulder gauntlet you can't read is just a coin flip.

Reinforce the spawned-parts caveat out loud: "the kill scan from Stage 4 can't see these, so each boulder wires its own kill." Campers who skipped the fireball stretch will hit the "why don't boulders hurt?" bug — this is where that lesson lands for real.

The head-read completes the VR input set (hands + head). Name it: "every VR device uses the same camera.CFrame * GetUserCFrame math — you already knew how to do this." As always, the buzz can't be felt today; clean Play is the pass.