Stage 2: Sphere Staircase + jump pads & comfort
Make sure you've finished Stage 1: Ascending Walls + VR climbing. Your VRController LocalScript should be in StarterPlayerScripts, and a clean Play should show no errors in Output.
a sphere staircase and a launch pad that flings the player upward
how to move a body with physics velocity, and how to keep a VR player comfortable
a parkour climb you can boost up with a jump pad, with VR comfort settings switched on
The big idea
Stage 1 moved the player by hand. Stage 2 moves the player by physics.
Every character in Roblox has a velocity — a speed and a direction it's traveling right now. A jump is just a quick burst of upward velocity. If we can set that velocity ourselves, we can build a jump pad: step on it, and it launches you higher than you could ever jump. That's the start of parkour — the world helps you move.
The modern, correct way to set a character's speed is AssemblyLinearVelocity. (You may have seen BodyVelocity in old tutorials — it's deprecated. We use the new tool.) You'll author this jump pad from scratch, because physics code is something you can test on a laptop right now.
Then comes the VR half: comfort. Fast movement in a headset can make people queasy if the camera fights the head. So before we launch anyone through the air in VR, we turn on three real comfort settings that keep the view steady. You won't feel the difference on a laptop, but a player in a headset absolutely will.
- velocity
- how fast something is moving and in which direction, as a Vector3 (x, y, z)
- AssemblyLinearVelocity
- the property that holds a character's current velocity — set it to launch the player
- Touched
- an event a part fires when something bumps into it; how a jump pad notices you
- debounce
- a short cooldown that stops an event from firing dozens of times in one touch
- HeadLocked
- a camera setting that pins the view exactly to the headset, so it never lags behind your head
- RecenterUserHeadCFrame
- a VRService method that resets 'forward' to wherever the player is currently looking
Build it
Step 1 — Build the sphere staircase
Spheres are harder to land on than blocks — they're the next step up in difficulty. Build four, climbing in height.
In Workspace, click + → Part, then change Shape to Ball in Properties. Make four.
Build this partSphere_1
BallOpen recipe
Sphere_1
Ball- Size
- 6 × 6 × 6
- Color
- Lily white
- Material
- Plastic
- Anchored
- ✓ Yes
- Place
- Just past the Stage 2 red pad, lowest of the four
Set Shape to Ball in Properties. Anchored ON so it can't roll away.
Build this partSphere_2
BallOpen recipe
Sphere_2
Ball- Size
- 6 × 6 × 6
- Color
- Lily white
- Material
- Plastic
- Anchored
- ✓ Yes
- Place
- A short hop past Sphere_1, a little higher
Build this partSphere_3
BallOpen recipe
Sphere_3
Ball- Size
- 6 × 6 × 6
- Color
- Lily white
- Material
- Plastic
- Anchored
- ✓ Yes
- Place
- A short hop past Sphere_2, higher still
Build this partSphere_4
BallOpen recipe
Sphere_4
Ball- Size
- 6 × 6 × 6
- Color
- Lily white
- Material
- Plastic
- Anchored
- ✓ Yes
- Place
- The highest sphere — make this gap a real challenge
Press ▶ Play and try to hop across. Notice how the round tops make your footing slippery — that's the point. Now we'll give the player a tool to beat it.
Step 2 — Build and script the jump pad
The jump pad sits before the spheres and launches the player up onto them. This is your first physics script, and you'll write it from scratch.
2.1 Build the pad
Build this partJumpPad_1
BlockOpen recipe
JumpPad_1
Block- Size
- 6 × 1 × 6
- Color
- Lime green
- Material
- Neon
- Anchored
- ✓ Yes
- Place
- On the ground in front of Sphere_1
Neon + a bright color tells the player 'step here, something happens.'
2.2 Script the launch
- Right-click JumpPad_1 in Explorer → Insert Object → Script (a regular server Script — the pad acts the same for everyone, so this one lives on the server, not in VRController).
- Double-click it, delete the placeholder, and type:
local pad = script.Parent
local LAUNCH_POWER = 90 -- studs per second, straight up
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
-- Don't relaunch the same player 60 times during one step.
if onCooldown[character] then return end
onCooldown[character] = true
-- Keep their sideways speed, replace the up speed with a big burst.
local v = root.AssemblyLinearVelocity
root.AssemblyLinearVelocity = Vector3.new(v.X, LAUNCH_POWER, v.Z)
task.wait(0.6)
onCooldown[character] = nil
end)
Press ▶ Play and step on the green pad. You should rocket upward — enough to clear the first sphere or two. Tune LAUNCH_POWER until the launch lands the player where you want.
This one you can fully test today. Physics runs the same with or without a headset.
Step 3 — Place the Stage 3 checkpoint
Same checkpoint pattern as Stage 1 — you've got this.
- Insert a SpawnLocation on top of Sphere_4. Size
[6, 1, 6], anchored, a new color (try Bright yellow). Check AllowTeamChangeOnTouch, uncheck Neutral, set TeamColor to match. - Add a
StageNumberattribute (number) = 3. - Add a Team named Stage 3, TeamColor Bright yellow, AutoAssignable unchecked.
Play, cross the spheres, touch the pad, reset — you should respawn on the yellow pad.
Step 4 — Turn on VR comfort
Open your VRController LocalScript from Stage 1. The services you need (VRService, UserInputService, camera) are already required at the top from Stage 1, so you only add the new lines below — paste this block at the bottom of the file.
-- ===== VR comfort (added in Stage 2) =====
if VRService.VREnabled then
camera.HeadLocked = true -- camera tracks the headset exactly, no lag
VRService.FadeOutViewOnCollision = true -- fade to black if your head pokes into a wall
end
-- Press the right bumper (ButtonR1) to re-center your view.
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.ButtonR1 then
VRService:RecenterUserHeadCFrame()
end
end)
Press ▶ Play on your laptop. As always, nothing visible changes and Output stays clean — that's the pass we can run today. These settings only do their job once a headset is attached.
With the comfort settings on, getting launched by the jump pad feels smooth instead of stomach-dropping — the view stays glued to your head the whole way up. If you lean too far and your head clips a sphere, the screen fades to black for a moment instead of showing you the inside of a ball. And if you've turned away from the course, a tap of the right bumper snaps "forward" back to where you're looking.
How the comfort block steadies a VR player
Three small settings, one big difference. This is the kind of code that does nothing visible on a laptop and everything for a person in a headset.
if VRService.VREnabled then
camera.HeadLocked = true
VRService.FadeOutViewOnCollision = true
end
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.ButtonR1 then
VRService:RecenterUserHeadCFrame()
end
end)
Line 1Only touch these in VR.
We guard with VREnabled so a laptop player's camera is left completely alone. Comfort settings are for headsets; there's no reason to change a desktop view.
Line 2HeadLocked pins the camera to the head.
When this is on, the view follows your physical head with zero delay. Any lag between your head turning and the picture moving is exactly what makes people queasy, so we remove it.
Line 3Fade out when your head hits a wall.
FadeOutViewOnCollision is a built-in VRService comfort feature. If your headset's view would end up inside a part, Roblox fades to black instead of showing a disorienting inside-of-the-wall shot.
Lines 6–10A button to re-center forward.
RecenterUserHeadCFrame resets which way is 'forward' to wherever you're looking now. Bind it to the right bumper so a player who's drifted or turned in their room can fix their orientation instantly. You can have more than one InputBegan handler — this one lives happily alongside the grip handler from Stage 1.
Understand it
The jump pad works because a jump is just velocity. By writing to AssemblyLinearVelocity we hand the character a burst of upward speed and let Roblox's physics carry it through the arc — rise, slow, fall — for free. We kept the player's existing X and Z (sideways) speed and only replaced Y, so a running jump still carries you forward. We used AssemblyLinearVelocity instead of the old BodyVelocity because it's the supported, modern property; reaching for deprecated tools is how games break on the next Roblox update.
The debounce matters more than it looks. Touched can fire many times in a single step as your feet jitter on the pad. Without the onCooldown guard, you'd get launched 90 studs per fire and rocket into orbit. One boolean per character, cleared after task.wait(0.6), turns a chaotic spam into one clean launch.
The comfort settings are a different kind of code: they don't change what happens, they change how it feels to a human in a headset. That's a real part of VR design. A launch that's thrilling on a screen can be nauseating in VR if the camera lags or clips. Good VR developers spend as much time on comfort as on mechanics — which is why we turn it on now, before the launches get bigger in Stage 5.
Try this
Try this
Three short experiments. Predict before you run, then test your guess.
LAUNCH_POWER is 90. Predict where the player lands if you set it to 45, and to 200. One of those makes the jump pad useless and one makes it dangerous — decide which before you test, then tune it to feel just strong enough to reach Sphere_2.
Try deleting the debounce lines (the onCooldown checks) and step on the pad. Then put them back. What exactly went wrong without them — and why is "this event can fire many times" a thing you'll have to remember for every Touched script you ever write?
HeadLocked and FadeOutViewOnCollision make fast motion comfortable. Stage 5 launches the player out of a cannon — much faster than this pad. What extra comfort step might a cannon need that a small hop doesn't? (Hint: think about what a sudden, huge motion does to your eyes.)
Test your stage
- Press ▶ Play, step on the green JumpPad_1, and get launched upward.
- The launch is strong enough to help you reach the spheres, but not so strong you fly off the map. Tune
LAUNCH_POWER. - Cross all four spheres, touch the yellow pad, reset, and confirm you respawn on it.
- Step on the pad and immediately stop — you should launch once, not repeatedly. (That's the debounce working.)
- Output is empty on a clean Play — no red errors from the pad Script or
VRController. - Design check. Does the green Neon pad look like something you should step on? In VR, players read glowing parts as "interact here." If it blends in, brighten it.
If it breaks
- I launch forever / fly into space. The debounce isn't working. Check that
onCooldown[character]is set totruebeforetask.wait, and cleared after. - Stepping on the pad does nothing. The Script must be a child of JumpPad_1, and the pad's
CanCollideshould be on so your feet actually touch it. Check Output for errors. - The launch barely lifts me. Raise
LAUNCH_POWER. If it still feels weak, your character may be landing on the pad and the pad is thin — make sure you're stepping on top, not clipping the side. cameraorVRServiceis underlined red in VRController. You're missing thelocal camera = ...orlocal VRService = ...lines from Stage 1 at the top of the file. The comfort block reuses them.- Everything's fine but VR comfort does nothing on my laptop. Correct and expected —
VREnabledis false, so the comfort block is skipped. It comes alive at the Stage 10 headset playtest.
The launch-into-orbit bug is the teaching moment of this stage — let campers hit it. Suggest they remove the debounce on purpose (it's in the Compare prompt), watch themselves rocket away, then put it back. Feeling the bug makes the fix stick far better than a warning.
Watch for campers pasting the comfort block into a server Script by habit — it must go in VRController (the LocalScript), because camera and VRService only mean anything on the client. A red camera is not a valid member error in Output is the tell.
The honest line again: comfort settings can't be felt today. The clean Play is the pass. Budget time for jump-pad tuning — it's the fun part and where the "design check" really lands.