Skip to main content

Stage 8: Spinning KillBricks

Course progressStage 8 of 10
~45 min
Before you start

Finish Stage 7: Rolling Rocks first. You have a Lime green checkpoint at the top of the ramp, and you understand TweenService.

Build

a narrow path with a sweep-arm bar that rotates continuously across it, killing on contact

Learn

CFrame as Roblox's position-and-rotation, `CFrame.Angles` for rotation, and `RunService.Heartbeat` for per-frame motion

Ship

the ninth checkpoint and a 6-line script that spins a part smoothly at 60 frames per second

The big idea

Stage 7 used TweenService for motion. TweenService is great for a predictable path: point A to point B.

Stage 8 needs motion that never stops: a fan blade, clock hand, or sweeping searchlight. That is what RunService.Heartbeat is for.

Heartbeat fires every frame, about 60 times per second. Connect a function to it, rotate the part a little each frame, and the motion looks smooth.

The rotation uses CFrame, Roblox's position-and-rotation type. Position says where a part is. CFrame says where it is and which way it faces.

The design lesson is cyclical danger. The hazard is always there, so the player must find the quiet moment in its pattern.

New words
CFrame
Roblox's combined position+rotation type — a Part's full pose in 3D space
CFrame.Angles
creates a rotation CFrame around the X, Y, Z axes (radians, not degrees)
RunService
the service that exposes per-frame events like Heartbeat and RenderStepped
Heartbeat
an event that fires every frame after physics — perfect for continuous motion
radians
the math-friendly way to measure angles; 360° = 2π radians; one full rotation = math.pi * 2

Build it

Step 1 — Build the narrow path

A long narrow walkway. The spinner sits in the middle, sweeping across the only walkable route.

A narrow path with spinning hazards

Build this part

SpinPath

Block
Open recipe
Size
4 × 1 × 40
Color
Dark stone grey
Material
Concrete
Anchored
✓ Yes
Place
Starting at the Stage 8 Lime green pad, stretching forward

Narrow on purpose. The sweep arm reaches almost across the full width — the player can't sidestep it.

Step 2 — Build the sweep arm

The sweep arm is a long thin bar that rotates around its vertical center. It sweeps over the path like a clock hand.

Build this part

SweepArm

Block
Open recipe
Size
10 × 0.5 × 1
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
Hovering 2 studs above the middle of SpinPath. Its long axis (10) reaches beyond both edges of the path.

Anchored = true is essential. We're rotating the arm in code; physics would interfere if it were unanchored.

Step 3 — Add the ninth checkpoint

Build this part

SpawnLocation (Stage 9 — past the spinner)

Block
Open recipe
Size
6 × 1 × 6
Color
Pink
Material
Plastic
Anchored
✓ Yes
Place
At the far end of SpinPath, past the spinner

Also: check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Pink. In Teams, insert a Team named 'Stage 9', uncheck AutoAssignable, set its TeamColor to match.

Step 4 — Write the spinner script

This script is short, but it introduces two big ideas: CFrame.Angles and Heartbeat.

Right-click SweepArmInsert ObjectScript.

Pass 1 — Add the kill handler (same pattern as Stage 4)

local arm = script.Parent

arm.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)

Press Play. Walk into the arm — it kills you. But it doesn't spin yet. Stop.

Pass 2 — Spin once per frame using Heartbeat

Add the Heartbeat connection at the bottom:

local arm = script.Parent
local RunService = game:GetService("RunService")

arm.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)

-- About one full rotation every 3.5 seconds.
local spinSpeed = 1.8

RunService.Heartbeat:Connect(function(deltaTime)
local turn = spinSpeed * deltaTime
arm.CFrame = arm.CFrame * CFrame.Angles(0, turn, 0)
end)

Press Play. SweepArm spins around its vertical (Y) axis.

The player must wait for the arm to move aside, then dash across before it sweeps back.

Most of the script is the kill handler you already know. The new part is the Heartbeat connection at the bottom.

Understand it

CFrame is Roblox's combined position + rotation. part.Position changes where a part is. part.CFrame changes where it is and which way it faces.

For a spinning part, Position is not enough. You need CFrame because rotation is part of the movement.

CFrame.Angles(x, y, z) creates a rotation. The values are in radians, not degrees.

To spin around the vertical Y axis, put the turn amount in the middle slot: CFrame.Angles(0, turn, 0).

The * matters. arm.CFrame * CFrame.Angles(...) means "start from the arm's current CFrame, then add a small rotation." Each frame adds a little more.

deltaTime keeps speed fair. It tells you how many seconds passed since the last frame. Multiplying by deltaTime keeps the spin steady on faster and slower computers.

Why Heartbeat instead of while true do ... wait()? Heartbeat runs every frame. For smooth rotation, every frame matters.

Script anatomy

How this script updates motion every frame

This script combines two ideas: the arm is still a KillBrick, but Heartbeat also rotates it a tiny amount every frame while the game is running.

local arm = script.Parent
local RunService = game:GetService("RunService")

arm.Touched:Connect(function(otherPart)
local character = otherPart.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)

local spinSpeed = 1.8

RunService.Heartbeat:Connect(function(deltaTime)
local turn = spinSpeed * deltaTime
arm.CFrame = arm.CFrame * CFrame.Angles(0, turn, 0)
end)
  1. Lines 1–2Find the arm and the frame-update service.

    `arm` is the SweepArm part. `RunService` gives scripts access to events that happen during the game loop, including Heartbeat.

  2. Lines 4–10Keep the familiar hazard behavior.

    This is the same touch-to-Humanoid pattern from Stage 4. The arm does not need special kill code just because it spins; it is still a part that kills when touched.

  3. Line 12Set the spin speed.

    `1.8` means 1.8 radians per second, which is about one full rotation every 3.5 seconds. Bigger numbers make the crossing harder because safe windows are shorter.

  4. Lines 14–17Rotate a little bit every frame.

    `Heartbeat:Connect` runs once per frame. `turn` uses deltaTime, so the spin speed stays steady on different computers.

Try this

Learning beat

Try this

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

Predict first

Change line 12 from 1.8 to 3.6. Predict what happens to the spin speed. Then run it. Now try 0.6. Predict and test.

Compare

Change CFrame.Angles(0, turn, 0) to CFrame.Angles(turn, 0, 0). Then try CFrame.Angles(0, 0, turn). Which axis matches a clock hand? Which matches a wheel?

Connect

The pattern arm.CFrame = arm.CFrame * CFrame.Angles(...) adds a tiny rotation every frame. What would happen if you removed the first arm.CFrame * part?

Test your stage

  • SweepArm spins continuously around the vertical axis at a readable pace.
  • Touching the arm kills you; you respawn on the Lime green pad.
  • You can wait for the arm to be on one side, then dash across the path before it sweeps back.
  • You reach the Pink pad at the end.
  • Output is clean — no red errors during Play.
  • Design check. Watch the spinner for 10 seconds. Is the quiet moment (when the arm is on one side and you have time to cross) long enough — at least 1.5 seconds? If too short, lower spinSpeed to 1.2.

If it breaks

  • The arm doesn't spin. Heartbeat connection probably failed. Check Output for red errors. Common cause: RunService is misspelled (lowercase r, missing e).
  • The arm spins but jitters or stutters. Your spinSpeed might be too small (causing tiny visible changes) or the script might be doing other heavy work between Heartbeat calls. Try spinSpeed = 1.8 exactly.
  • The arm spins around the wrong axis. Change which slot in CFrame.Angles(x, y, z) holds spinSpeed. Y is vertical (clock-hand style sweep). X is forward/back tilt (like nodding). Z is roll (like a barrel rolling).
  • The arm flies off into space when it spins. Anchored is off. Click SweepArm, find Anchored in Properties, turn it on.
  • The arm sweeps under the path instead of over it. Move SweepArm up 2-3 studs in the Y axis so it's clearly above the path.
  • Touching the arm doesn't kill me. Standard Touched debugging: Script inside the arm, Humanoid spelled correctly, Health = 0 not = 0.0 or = nil.
Coach notes

The Heartbeat-vs-while-loop concept is hard for campers to feel from a one-line explanation. Have them do this side by side: write a version using while true do … wait(0.03) … end instead of Heartbeat. The motion looks identical at 60 fps but the while loop will stutter when Roblox is busy with other things. Heartbeat doesn't.

  • Radians vs. degrees throws everyone. spinSpeed = 1.8 means 1.8 radians per second, not 1.8 degrees per second. If a camper wants to think in degrees: math.rad(100) converts 100 degrees per second to about 1.75 radians per second.
  • CFrame is genuinely confusing — it's a 4x4 matrix under the hood. Don't try to explain the math. Just teach the pattern: "to spin smoothly, multiply CFrame by CFrame.Angles every frame."
  • If a camper's arm flies off, they almost certainly forgot Anchored. Check first.