Skip to main content

Stage 2: Sphere Staircase + jump pads & comfort

Course progressStage 2 of 10
~50 min
Before you start

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.

Build

a sphere staircase and a launch pad that flings the player upward

Learn

how to move a body with physics velocity, and how to keep a VR player comfortable

Ship

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.

New words
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 part

Sphere_1

Ball
Open recipe
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 part

Sphere_2

Ball
Open recipe
Size
6 × 6 × 6
Color
Lily white
Material
Plastic
Anchored
✓ Yes
Place
A short hop past Sphere_1, a little higher
Build this part

Sphere_3

Ball
Open recipe
Size
6 × 6 × 6
Color
Lily white
Material
Plastic
Anchored
✓ Yes
Place
A short hop past Sphere_2, higher still
Build this part

Sphere_4

Ball
Open recipe
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 part

JumpPad_1

Block
Open recipe
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 ObjectScript (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 StageNumber attribute (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.

In VR

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.

Script anatomy

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)
  1. 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.

  2. 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.

  3. 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.

  4. 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

Learning beat

Try this

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

Predict first

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.

Compare

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?

Connect

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 to true before task.wait, and cleared after.
  • Stepping on the pad does nothing. The Script must be a child of JumpPad_1, and the pad's CanCollide should 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.
  • camera or VRService is underlined red in VRController. You're missing the local camera = ... or local 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 — VREnabled is false, so the comfort block is skipped. It comes alive at the Stage 10 headset playtest.
Coach notes

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.