Stage 9: Kinetic KillWall + combat arena
Finish Stage 8. Enemies drop loot, and your enemy template is stashed in ServerStorage. The arena reuses that exact template.
a walled hallway with a sliding gate, the Stage 10 checkpoint, and a combat-arena wave
how to spawn a wave of enemies, count how many remain, and open a gate only when the wave is cleared
a hallway that locks you in, spawns a wave you must defeat, and slides open once the last enemy falls
90-second demo:
- Press Play. Walk into the hallway. The exit is blocked by a glowing wall.
- A wave of three enemies spawns and rushes you. Fight with sword and fireball; collect the loot they drop.
- When the last enemy falls, the wall slides down — the way forward opens.
- Explain: "One enemy is a fight. A wave you have to clear is an arena. The trick is counting: the gate watches how many enemies are left and opens at zero."
The big idea
Every system you've built was practice for this: a real combat arena. You walk into a hallway, a gate locks you in, a wave of enemies spawns, and the only way out is to clear the wave. It's the set-piece that makes a fighting game feel like a fighting game.
The new idea is counting and gating. You spawn several enemies and keep a tally of how many are left alive. Each time one dies, the tally drops by one. When it hits zero, you open the gate. That "do something only when a count reaches zero" pattern shows up everywhere — clearing a level, finishing a quiz, emptying a cart.
You'll reuse the enemy template you stashed in ServerStorage back in Stage 7, cloning it as many times as the wave needs. These arena enemies are deliberately simpler than the Stage 7 roamer: they chase and attack, but they don't respawn — because a wave that respawns can never be cleared.
- wave
- a group of enemies spawned together that the player must defeat to progress
- counter
- a number you raise or lower to track how many of something remain
- gate
- a wall or door that blocks progress until a condition is met
- TweenService
- a service that smoothly animates a property, like sliding a wall from closed to open
- TweenInfo
- the settings for a tween — how long it takes and how it eases
- trigger
- the moment that starts something; here, stepping into the hall starts the wave
Build it
Step 1 — Build the walled hallway and gate
A corridor with side walls and a sliding gate blocking the far end.

Build this partHallFloor
BlockOpen recipe
HallFloor
Block- Size
- 16 × 1 × 30
- Color
- Dark stone grey
- Material
- Concrete
- Anchored
- ✓ Yes
- Place
- A long corridor forward from the Stage 9 cyan pad
Build this partHallWall_Left
BlockOpen recipe
HallWall_Left
Block- Size
- 1 × 10 × 30
- Color
- Medium stone grey
- Material
- Brick
- Anchored
- ✓ Yes
- Place
- Along the left edge of HallFloor
Build this partHallWall_Right
BlockOpen recipe
HallWall_Right
Block- Size
- 1 × 10 × 30
- Color
- Medium stone grey
- Material
- Brick
- Anchored
- ✓ Yes
- Place
- Along the right edge of HallFloor
Build this partSlidingWall
BlockOpen recipe
SlidingWall
Block- Size
- 16 × 10 × 1
- Color
- Cyan
- Material
- Neon
- Anchored
- ✓ Yes
- Place
- Across the FAR end of HallFloor, blocking the exit
This is the gate. It starts closed; the script slides it down into the floor when the wave is cleared.
Press Play and confirm the SlidingWall blocks the way out.
Step 2 — Wire the Stage 10 checkpoint
Place this past the SlidingWall — you can only reach it once the gate opens.
Build this partSpawnLocation (Stage 10 — past the gate)
BlockOpen recipe
SpawnLocation (Stage 10 — past the gate)
Block- Size
- 6 × 1 × 6
- Color
- Magenta
- Material
- Plastic
- Anchored
- ✓ Yes
- Place
- Just beyond where SlidingWall stands
Add StageNumber = 10. Check AllowTeamChangeOnTouch. Uncheck Neutral. Set TeamColor to Magenta.
Same gesture: StageNumber = 10 attribute and a Stage 10 Team (Magenta, AutoAssignable unchecked).
Step 3 — Build the arena wave
In ServerScriptService, insert a Script named ArenaWave. Build it in three passes.
Pass 1 — Spawn a wave when the player enters
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local template = ServerStorage:WaitForChild("Enemy")
local hallFloor = workspace:WaitForChild("HallFloor")
local WAVE_SIZE = 3
local DETECT_RANGE = 80
local ATTACK_RANGE = 6
local ATTACK_DAMAGE = 12
local function simpleBrain(enemy)
local humanoid = enemy:WaitForChild("Humanoid")
local root = enemy:WaitForChild("HumanoidRootPart")
local lastAttack = 0
task.spawn(function()
while humanoid.Health > 0 do
task.wait(0.3)
local target, best = nil, DETECT_RANGE
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
local theirRoot = character and character:FindFirstChild("HumanoidRootPart")
local theirHum = character and character:FindFirstChildOfClass("Humanoid")
if theirRoot and theirHum and theirHum.Health > 0 then
local dist = (theirRoot.Position - root.Position).Magnitude
if dist < best then target, best = character, dist end
end
end
if target then
humanoid:MoveTo(target.HumanoidRootPart.Position)
if (target.HumanoidRootPart.Position - root.Position).Magnitude <= ATTACK_RANGE then
if os.clock() - lastAttack >= 1 then
lastAttack = os.clock()
local targetHumanoid = target:FindFirstChildOfClass("Humanoid")
if targetHumanoid then
targetHumanoid:TakeDamage(ATTACK_DAMAGE)
end
end
end
end
end
end)
end
local waveStarted = false
hallFloor.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player or waveStarted then return end
waveStarted = true
for i = 1, WAVE_SIZE do
local enemy = template:Clone()
enemy:PivotTo(CFrame.new(hallFloor.Position + Vector3.new(math.random(-6, 6), 5, math.random(-6, 6))))
enemy.Parent = workspace
simpleBrain(enemy)
end
print("Wave started:", WAVE_SIZE, "enemies")
end)
Press Play. Step onto the hall floor — three enemies drop in and rush you. Fight them; they drop loot like any enemy. The wall doesn't open yet. Stop.
Pass 2 — Count the enemies down
Track how many remain and notice when the wave is cleared. Replace the spawn loop inside Touched with:
local remaining = WAVE_SIZE
for i = 1, WAVE_SIZE do
local enemy = template:Clone()
enemy:PivotTo(CFrame.new(hallFloor.Position + Vector3.new(math.random(-6, 6), 5, math.random(-6, 6))))
enemy.Parent = workspace
simpleBrain(enemy)
local humanoid = enemy:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
remaining = remaining - 1
print("Enemy down. Remaining:", remaining)
task.wait(2)
enemy:Destroy()
if remaining <= 0 then
print("Wave cleared!")
end
end)
end
Press Play. Clear the wave and watch Output count down: "Remaining: 2", "Remaining: 1", "Wave cleared!". The wall still doesn't move — Pass 3. Stop.
Pass 3 — Open the gate when the wave is cleared
Add an openGate function near the top (below the services), and call it when the count hits zero. First the function:
local TweenService = game:GetService("TweenService")
local slidingWall = workspace:WaitForChild("SlidingWall")
local function openGate()
local goal = { Position = slidingWall.Position - Vector3.new(0, slidingWall.Size.Y, 0) }
TweenService:Create(slidingWall, TweenInfo.new(1), goal):Play()
end
Then, in the Died handler, replace the print("Wave cleared!") line with openGate():
if remaining <= 0 then
openGate()
end
Press Play. Step into the hall, defeat all three enemies, and the moment the last one falls, the cyan wall slides down into the floor — the path to Stage 10 opens.
Understand it
The counter is the whole idea. remaining starts at the wave size, each Died lowers it by one, and the gate opens when it reaches zero. This is how every "clear the room to continue" moment works — you're not detecting which enemy died, just that one fewer is alive.
Each enemy's Died connection is its own closure. Because you connect Died inside the spawn loop, each enemy carries a reference to the same remaining variable. Whoever dies decrements the shared count. This is the same "many things, one shared total" pattern you'll meet again whenever you tally scores or votes.
The arena enemies don't respawn — on purpose. The Stage 7 roamer cloned itself on death so the world always had an enemy. An arena enemy must stay dead, or the wave could never be cleared and the gate would never open. Same enemy template, different rule, because the goal is different. Matching the behavior to the goal is a real design decision.
TweenService makes the gate feel built, not blinked. You could set slidingWall.Position directly and the wall would teleport open. A tween slides it over one second, which reads as a mechanism. TweenInfo.new(1) is the only setting you need: one second.
Spawn a wave, count it down, open the gate
Stepping into the hall spawns the wave; each enemy's death lowers a shared counter, and reaching zero tweens the gate open.
local waveStarted = false
hallFloor.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player or waveStarted then return end
waveStarted = true
local remaining = WAVE_SIZE
for i = 1, WAVE_SIZE do
local enemy = template:Clone()
enemy:PivotTo(CFrame.new(hallFloor.Position + Vector3.new(math.random(-6, 6), 5, math.random(-6, 6))))
enemy.Parent = workspace
simpleBrain(enemy)
local humanoid = enemy:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
remaining = remaining - 1
task.wait(2)
enemy:Destroy()
if remaining <= 0 then
openGate()
end
end)
end
end)
Lines 1–6Start the wave once.
The waveStarted flag is a debounce so the wave only triggers the first time a player steps on the floor — not every time a body part brushes it.
Lines 8–13Clone the template into the arena.
Reuse the Stage 7 enemy from ServerStorage, drop each at a random spot above the floor, and give it the simple chase-and-attack brain.
Lines 15–22Count down on each death.
Every enemy's Died handler shares the same remaining counter. One fewer alive each time, and the corpse is cleaned up after 2 seconds.
Lines 20–21Open at zero.
When the last enemy falls, remaining hits 0 and openGate tweens the wall down. The arena is cleared.
Try this
Try this
Three short experiments. Predict before you run, then test your guess.
Predict what happens if you remove the waveStarted flag. Walk back and forth across the hall floor. How many enemies end up spawning? Why is the flag essential here?
Change WAVE_SIZE from 3 to 1, then to 6. Compare how each feels. Where's the line between "a fight" and "unfair"? Does your sword-and-fireball loadout keep up with six?
The gate opens when a counter hits zero. In Stage 10 you fight a single boss, and a wall opens when it falls. How is "boss defeated" just a wave of size one? What's the same, and what's different?
Test your stage
- Press ▶ Play. The SlidingWall blocks the hallway's exit.
- Step onto the hall floor — a wave of three enemies spawns and attacks.
- Defeat them with sword and fireball; collect the loot they drop.
- Output counts down the remaining enemies as they fall.
- When the last enemy falls, the wall slides open and you can reach the magenta Stage 10 pad.
- Design check. Is the arena tense but beatable with your tools? Tune
WAVE_SIZE,ATTACK_DAMAGE, and the enemies' WalkSpeed until clearing it feels like a real fight you earned.
If it breaks
- No enemies spawn. The template must be in ServerStorage named exactly
Enemy(Stage 7 moved it there). If you never ran Stage 7's script, the template won't be stashed — play once withEnemyAIactive first. - Way too many enemies spawn. The
waveStartedflag is missing or reset. It must staytrueafter the first trigger. - The wall never opens. The count isn't reaching zero — usually because an enemy got stuck and never died, or
openGateis called in the wrong place. Watch the Output countdown to see where it stalls. - The wall opens but you still can't pass. It slid down by less than its height. Confirm
openGatesubtracts the fullslidingWall.Size.Y. Enemy is not a valid member of ServerStorage. Same as the first point — the Stage 7 script is what moves the template there.
The concept is "count down to a goal." Make it physical: count three campers, have them sit one at a time, and open an imaginary door when the count hits zero.
- This stage leans on Stage 7's template living in ServerStorage. If a camper skipped or broke Stage 7, the clone fails — check that first.
- The
simpleBrainhere is intentionally a trimmed copy of Stage 7's AI with no respawn. Point that out: same idea, but the goal (a clearable wave) changes the rule. - Total time: 60 minutes. Hallway + gate 15, checkpoint 5, three-pass arena 40.