Skip to main content

Stage 4: KillBrick Path + fireball power

Course progressStage 4 of 10
~60 min
Before you start

Finish Stage 3. Your sword deals damage to the training dummy. Keep the dummy nearby — you'll blast it with a fireball today.

Build

a KillBrick danger path, the Stage 5 checkpoint, and a fireball power that fires on a key press

Learn

why a power needs a client half and a server half, and how a RemoteEvent passes a message between them

Ship

a glowing fireball that launches when you press F, flies where you aim, and damages whatever it hits

Teacher demo

90-second demo:

  • Press Play. Aim at the training dummy and press F. A glowing fireball launches from your hands and flies at the dummy.
  • When it hits, the dummy takes a big chunk of damage and the fireball pops.
  • Press F aimed at a wall — the fireball flies, hits, and vanishes.
  • Explain: "Your sword's brain lived inside the Tool. A power is different — the key press happens on your computer, but the damage has to happen on the server so nobody can cheat. Today you connect the two."

The big idea

This is the flashiest stage yet, and the most important idea in all of Roblox scripting: the client and the server are two different computers, and they talk through a RemoteEvent.

Here's why that matters. The client is the player's own machine — it knows what keys they press and where their mouse points. The server is Roblox's trusted machine — it's the only place damage should be decided, because a clever player could edit their own client to cheat. So a power is always two halves: a LocalScript on the client that notices the key press, and a Script on the server that actually spawns the fireball and deals the damage. The bridge between them is a RemoteEvent — a mailbox the client drops a message into and the server reads.

Today: press F → the LocalScript fires the RemoteEvent with where you're aiming → the server spawns a glowing, particle-trailed fireball that flies there and damages any Humanoid it hits.

New words
client
the player's own computer; it knows their inputs (keys, mouse) but isn't trusted with damage
server
Roblox's trusted machine that runs the real game; the only safe place to decide damage
LocalScript
a script that runs on the client; the only kind that can read a player's key presses
RemoteEvent
a one-way mailbox between client and server; FireServer sends, OnServerEvent receives
UserInputService
the service that tells a LocalScript which keys and buttons the player pressed
ParticleEmitter
an object that sprays little glowing particles — what makes the fireball look like fire
Debris
a service that deletes an object after a set time, so old fireballs clean themselves up

Build it

Step 1 — Build the KillBrick path

A run of glowing red bricks that send you back to the checkpoint if you touch them. This is the obby obstacle; the fireball is the combat layer on top.

A KillBrick path in a Roblox combat obby with a player casting an orange fireball

First, a safe path to walk on:

Build this part

HazardPath

Block
Open recipe
Size
8 × 1 × 30
Color
Dark stone grey
Material
Concrete
Anchored
✓ Yes
Place
Stretching forward from the Stage 4 green pad

Then three danger bricks sitting on the path, with gaps to dodge between:

Build this part

KillBrick_1

Block
Open recipe
Size
8 × 1 × 3
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
Across HazardPath, about a third of the way along
Build this part

KillBrick_2

Block
Open recipe
Size
8 × 1 × 3
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
Across HazardPath, about halfway along
Build this part

KillBrick_3

Block
Open recipe
Size
8 × 1 × 3
Color
Really red
Material
Neon
Anchored
✓ Yes
Place
Across HazardPath, about two-thirds along

Now make the red bricks dangerous. In ServerScriptService, insert a Script named KillBricks and type:

local Players = game:GetService("Players")

for _, part in ipairs(workspace:GetChildren()) do
if part.Name:sub(1, 9) == "KillBrick" then
part.Touched:Connect(function(otherPart)
local humanoid = otherPart.Parent:FindFirstChildOfClass("Humanoid")
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if humanoid and player then
humanoid.Health = 0
end
end)
end
end

Press Play. Walk into a red brick — you reset to the checkpoint. Dodge between them to cross.

Step 2 — Wire the Stage 5 checkpoint

Build this part

SpawnLocation (Stage 5 — end of the hazard path)

Block
Open recipe
Size
6 × 1 × 6
Color
Bright yellow
Material
Plastic
Anchored
✓ Yes
Place
At the far end of HazardPath

Add StageNumber = 5. Check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Bright yellow.

Same gesture: StageNumber = 5 attribute and a Stage 5 Team (Bright yellow, AutoAssignable unchecked).

Step 3 — Build the RemoteEvent mailbox

The client and server need a shared mailbox. It goes in ReplicatedStorage, the one place both can see.

  • In Explorer, right-click ReplicatedStorageInsert ObjectRemoteEvent.
  • Rename it CastFireball (exact spelling — both scripts look for this name).

Step 4 — Build the fireball power

You'll write two scripts that talk through the mailbox, in three passes.

Pass 1 — The client notices the key press

The script that reads keys must be a LocalScript, and it lives in StarterPlayerScripts.

  • In Explorer, expand StarterPlayer, right-click StarterPlayerScriptsInsert ObjectLocalScript.
  • Rename it FireballClient. Type:
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local castFireball = ReplicatedStorage:WaitForChild("CastFireball")

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()

UserInputService.InputBegan:Connect(function(input, typing)
if typing then return end
if input.KeyCode == Enum.KeyCode.F then
print("F pressed — sending fireball request")
castFireball:FireServer(mouse.Hit.Position)
end
end)

Now the server side, so you can see the message arrive. In ServerScriptService, insert a Script named FireballServer:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local castFireball = ReplicatedStorage:WaitForChild("CastFireball")

castFireball.OnServerEvent:Connect(function(player, targetPosition)
print("Server got a fireball request from", player.Name)
end)

Press Play and press F. Output prints both lines — the client's "sending" and the server's "got a request." The mailbox works. Stop.

Pass 2 — The server spawns a flying fireball

Replace the body of OnServerEvent in FireballServer with this:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local castFireball = ReplicatedStorage:WaitForChild("CastFireball")

castFireball.OnServerEvent:Connect(function(player, targetPosition)
local character = player.Character
if not character then return end
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end

local fireball = Instance.new("Part")
fireball.Shape = Enum.PartType.Ball
fireball.Size = Vector3.new(2.5, 2.5, 2.5)
fireball.Color = Color3.fromRGB(255, 120, 0)
fireball.Material = Enum.Material.Neon
fireball.CanCollide = false
fireball.Position = root.Position + root.CFrame.LookVector * 4 + Vector3.new(0, 2, 0)
fireball.Parent = workspace

local offset = targetPosition - fireball.Position
if offset.Magnitude < 1 then
fireball:Destroy()
return
end
local direction = offset.Unit
fireball.AssemblyLinearVelocity = direction * 70
end)

Press Play, aim, press F. A glowing orange ball launches from in front of you and flies toward your aim point. It doesn't hurt anything yet. Stop.

Pass 3 — Add fire particles, damage, and cleanup

Add this inside OnServerEvent, after the velocity line:

local Debris = game:GetService("Debris")

local fire = Instance.new("ParticleEmitter")
fire.Color = ColorSequence.new(Color3.fromRGB(255, 170, 0))
fire.Lifetime = NumberRange.new(0.3)
fire.Rate = 80
fire.Speed = NumberRange.new(2)
fire.Parent = fireball

local hasHit = false
fireball.Touched:Connect(function(otherPart)
if hasHit then return end
if otherPart:IsDescendantOf(character) then return end

local humanoid = otherPart.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
hasHit = true
humanoid:TakeDamage(40)
end
fireball:Destroy()
end)

Debris:AddItem(fireball, 4)

Press Play. Aim at the dummy, press F. The fireball trails fire, slams into the dummy, deals 40 damage, and pops. Aim at a wall — it flies, hits, and vanishes. Any fireball that hits nothing deletes itself after 4 seconds so the world doesn't fill with old fireballs.

Understand it

The client/server split is the whole lesson. Only a LocalScript can read UserInputService, so the key press must start on the client. But damage must be decided on the server, because the client isn't trusted. The RemoteEvent is the only honest way across: the client says "I'd like to cast," and the server decides what actually happens. Every power, ability, and shop purchase in serious Roblox games works exactly this way.

The server builds the fireball, not the client, so every player sees the same fireball. If the client spawned it, only that one player would see it — the others would watch you press F and nothing would appear. Server-created parts replicate to everyone automatically.

The hasHit flag is a debounce again — the same problem as the sword. A fast-moving ball can register several Touched events in one frame, so without the flag a single fireball could deal 40 damage many times. It deals damage once, then destroys itself.

The Debris:AddItem line is good housekeeping. A fireball that sails past everything would otherwise live forever, and hundreds of them would lag the game. Debris deletes it after 4 seconds.

Script anatomy

The server half of the fireball power

When the RemoteEvent fires, the server spawns a glowing ball at the caster, sends it toward the aim point, and makes it deal damage once before cleaning up.

castFireball.OnServerEvent:Connect(function(player, targetPosition)
local character = player.Character
if not character then return end
local root = character:FindFirstChild("HumanoidRootPart")
if not root then return end

local fireball = Instance.new("Part")
fireball.Shape = Enum.PartType.Ball
fireball.Material = Enum.Material.Neon
fireball.CanCollide = false
fireball.Position = root.Position + root.CFrame.LookVector * 4 + Vector3.new(0, 2, 0)
fireball.Parent = workspace

local offset = targetPosition - fireball.Position
if offset.Magnitude < 1 then
fireball:Destroy()
return
end
local direction = offset.Unit
fireball.AssemblyLinearVelocity = direction * 70

local hasHit = false
fireball.Touched:Connect(function(otherPart)
if hasHit then return end
if otherPart:IsDescendantOf(character) then return end
local humanoid = otherPart.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
hasHit = true
humanoid:TakeDamage(40)
end
fireball:Destroy()
end)

game:GetService("Debris"):AddItem(fireball, 4)
end)
  1. Lines 1–5Who cast it, and from where?

    OnServerEvent always passes the player first, then whatever the client sent (the aim point). Find their character and HumanoidRootPart so the fireball starts at them.

  2. Lines 7–13Build the fireball on the server.

    A Neon ball with collisions off, spawned just in front of the caster. Because the server makes it, every player sees it.

  3. Lines 15–16Aim it.

    Subtract positions to get a direction, .Unit shrinks it to length 1, times 70 sets the speed. AssemblyLinearVelocity launches the ball that way.

  4. Lines 18–29Damage once, then clean up.

    The hasHit flag stops repeat damage. Skip the caster's own body, deal 40 to a Humanoid, destroy the ball. Debris deletes any fireball that hits nothing after 4 seconds.

Try this

Learning beat

Try this

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

Predict first

The fireball is built by the server. Predict what other players would see if you moved the Instance.new("Part") code into the LocalScript instead. Why does spawning it on the server matter for a multiplayer fight?

Compare

Change the speed from 70 to 30, press Play, and fire. Then try 150. Compare how each feels to aim. Faster is easier to land but the fire trail is harder to see — which speed reads best?

Connect

The fireball can be spammed as fast as you can press F. In Stage 5 you'll add a cooldown so it can't. Where would a "wait before you can cast again" check live — on the client, the server, or both? Why both?

Test your stage

  • Press ▶ Play. Dodge between the KillBricks to cross the path; touching one resets you.
  • Reach the Stage 5 yellow pad.
  • Aim at the dummy and press F. A glowing fireball launches and trails fire.
  • The fireball hits the dummy, deals 40 damage, and pops.
  • A fireball aimed at empty space deletes itself after a few seconds.
  • Output (Pass 1) showed both the client and server messages.
  • Design check. 40 damage from a fireball vs 25 from a sword swing — does the power feel stronger than the basic weapon? A power should feel worth the key press.

If it breaks

  • Pressing F does nothing. The FireballClient script must be a LocalScript inside StarterPlayerScripts — a regular Script there won't read input. Also confirm the RemoteEvent is named exactly CastFireball.
  • Output shows the client line but not the server line. The RemoteEvent name doesn't match in one of the scripts, or FireballServer isn't in ServerScriptService.
  • The fireball appears but doesn't move. The velocity line is missing or runs before the part is parented. Parent the fireball to workspace before setting its velocity.
  • The fireball falls to the ground instead of flying straight. That's gravity — a little arc is normal. Increase the speed to flatten the arc, or see the Stretch challenge to remove gravity.
  • The fireball hurts me. The if otherPart:IsDescendantOf(character) then return end guard is missing or the fireball spawns inside you. Push the spawn point further with a bigger LookVector * 4.
Coach notes

This is the conceptual peak of the course. Spend real time on Pass 1 — the two print statements proving a message crossed from client to server is the single most important thing campers learn here. Don't rush to the visual fireball before that lands.

  • The most common bug is a regular Script (not LocalScript) in StarterPlayerScripts, or the LocalScript placed in StarterPack/Workspace. Input only works from a LocalScript in a client location.
  • If a camper asks "why not just damage on the client?" — that's the perfect question. Answer: a cheater can edit their client, but not the server.
  • Total time: 60 minutes. KillBrick path + script 15, checkpoint 5, RemoteEvent 5, three-pass power 35.