Survive the night as the security guard! Watch the cameras, close the doors, and don't let the animatronics sneak into your office. Build cameras, doors, a power meter, animatronic AI, and a terrifying jump scare!
🧒 Ages 8+⏱️ 1.5-2.5 Hours🎮 Godot 4✓ Free
📷 Camera Systems🚪 Signals⚡ Power Drain🤖 AI Movement😱 Jump Scares⏰ Timer Nodes
Understand what we're building and get Godot open and ready!
🧒
We're going to build a SPOOKY game! You're a security guard at a pizza restaurant but at night, the animatronic animals (like robot bears and bunnies!) come to life and try to find you. You have to check cameras, close your doors, and SURVIVE until 6am. It's creepy, it's exciting, and YOU are going to build it!
👨👧
Parent note: Episode 7 of My First Video Game. This is the most complex episode yet — it introduces node signals, a SubViewport-based camera system, a state machine via Timer nodes, and AnimationPlayer for the jump scare. The spooky theme is age-appropriate for 8+ but as always, parents should judge. Five Nights at Freddy's is a major part of many kids' gaming culture — this is a great way to demystify how it works!
💡
Purple boxes are for grown-ups! The child does the typing — your job is to read the steps aloud and celebrate progress. This episode has more code than earlier ones, but each piece is clearly explained. Take it one step at a time — the payoff (a working jump scare!) is HUGE.
✏️
Fill in the Blanks
+15 XP
In this workshop we're building a game inspired by . The player is a who must survive until 6am.
🧠
Knowledge Check
+15 XP
What engine are we using to build Night Watch?
AUnity
BUnreal Engine
CGodot 4
2
📁
New Project — Night Watch!
Create a fresh spooky project!
Locked
🎯
Goal for this step
Create a new Godot 4 project called "Night Watch" and configure the window.
🧒
Every game starts as a blank project — like an empty notebook waiting for your story! Let's set ours up. We'll use a wider window this time because our security office is wide (you need room for both doors!).
👨👧
Parent note: Standard Godot 4 new project. Compatibility renderer. Set the window size to 960x540 in Project Settings → Display → Window (16:9, fits most screens). Also recommend setting Stretch Mode to "canvas_items" and Aspect to "keep" so it scales cleanly.
What to do
1Open Godot 4 and click "New Project".
2Name it Night Watch. Choose a folder on your Desktop.
3Make sure Renderer is set to Compatibility.
4Click "Create & Edit".
5Go to Project → Project Settings → Display → Window. Set Width: 960, Height: 540.
🕹️
Try it! After creating the project, go to Project → Project Settings → Display → Window. Set Width: 960, Height: 540. This gives us a nice wide office!
🔢
Put It In Order
+15 XP
Click these steps in the correct order to set up a new Godot project:
Set Window Width to 960 and Height to 540
Click "New Project" in Godot
Name it "Night Watch" and click Create & Edit
🧠
Knowledge Check
+15 XP
What window size do we set for our Night Watch project?
A1920 x 1080
B960 x 540
C640 x 480
3
🏢
Build the Security Office!
Your home for the whole night...
Locked
🎯
Goal for this step
Create the main scene — a dark security office with a desk, monitor, and two door panels.
🧒
This is your office! It's small, dark, and a bit scary. You have a screen in the middle (that's the camera monitor), and two doors — one on the LEFT and one on the RIGHT. As long as those doors are closed, nothing can get in. As long as you have POWER.
👨👧
Parent note: Create main.tscn with a Node2D root named "NightWatch". Build the office using ColorRect nodes: a dark background, a centre monitor area, a left door panel, a right door panel. Use CanvasLayer for UI elements. Keep it simple — solid coloured rectangles work perfectly.
What to do
1Click Scene → New Scene. Root: Node2D. Rename it NightWatch. Save as main.tscn.
2Add a ColorRect child. Set it to cover the full screen (Width: 960, Height: 540). Colour: very dark grey or near-black.
3Add another ColorRect for the monitor in the centre — make it dark green/teal. Roughly 400x260, centred.
4Add a ColorRect on the far left for the left door panel — tall and dark. About 120px wide.
5Add a ColorRect on the far right for the right door panel — matching size.
6Press Ctrl+S then F5 to test!
💡
The "office" in FNAF is entirely 2D UI-style. There is no player movement — the player is static. Everything is about switching what you SEE (cameras) and what you PRESS (doors).
✏️
Fill in the Blanks
+15 XP
The root node type for our main scene is . We save it as .
🧠
Knowledge Check
+15 XP
What node type do we use for the coloured rectangles in our office scene?
AColorRect
BSprite2D
CPanel
4
📷
Build the Camera System!
See the whole building from your desk!
Locked
🎯
Goal for this step
Create multiple camera "views" and let the player switch between them by clicking.
🧒
The coolest part of FNAF is the cameras! You click a button and suddenly you can see a DIFFERENT room — the kitchen, the stage, the hallway. We're going to build that. Each camera is just a picture of a different room, and we swap which one is showing on our monitor.
👨👧
Parent note: Camera approach: create a CanvasLayer containing the monitor. Inside it, create multiple "room" panels as ColorRect children — one per camera location (Stage, Hallway Left, Hallway Right, Kitchen). Only one is visible at a time. A row of camera buttons swaps which panel is visible. This is conceptually identical to FNAF's actual camera approach.
What to do
1Inside the monitor ColorRect, add 4 ColorRect children — name them Cam1_Stage, Cam2_HallLeft, Cam3_HallRight, Cam4_Kitchen.
2Give each panel a different colour. Only show Cam1_Stage at start (set the others to Visible: off).
3Add a row of Button nodes below the monitor — one for each camera.
4Attach the script below to your NightWatch root node.
5Connect each button's pressed signal to the _on_cam_button function.
📝 GDScript — Camera Switching
extends Node2D
# Drag each camera panel into these in the Inspector@export varcam_panels: Array[ColorRect] = []
var current_cam = 0funcshow_cam(index: int):
for i in cam_panels.size():
cam_panels[i].visible = (i == index)
current_cam = index
func_on_cam_button(index: int):
show_cam(index)
🕹️
Try it! Click each camera button. Does the picture on the monitor change? You should see a different room name/colour for each camera.
💻
Code Challenge
+20 XP
Fill in the blanks to show and hide camera panels:
func show_cam(index: int):
for i in cam_panels.size():
cam_panels[i]. = (i == )
Hint: We set each panel's visibility property, comparing i to the parameter.
🧠
Knowledge Check
+15 XP
How does the camera system work in our FNAF-style game?
AIt moves the player to different rooms
BIt shows and hides different panels on the monitor
CIt uses a real 3D camera to render different angles
5
🚪
The Doors & Lights!
Your only defence against the dark...
Locked
🎯
Goal for this step
Add left and right door buttons — clicking them slams the door shut (and costs power)!
🧒
The doors are your best friends in this game! Click a door button and SLAM — the door closes and nothing can get past it. But here's the catch: keeping a door closed uses up your POWER. So you have to be smart about when you close them!
👨👧
Parent note: Add a Button below each door panel. A boolean tracks door state. When the door is closed, draw a thick ColorRect over the doorway. The power drain rate increases while a door is closed. Door state is checked by the animatronic AI later.
What to do
1Add a Button node below your left door panel. Label it "Close Left Door". Do the same on the right.
2Add a dark ColorRect over each doorway (named LeftDoorBlock and RightDoorBlock). Set them Visible: off.
3In your NightWatch script, add the door variables and functions below.
4Connect each door button's pressed signal to the matching function.
5In the Inspector, drag LeftDoorBlock and RightDoorBlock into the @export fields.
📝 GDScript — Door Toggle
var left_door_closed = falsevar right_door_closed = false@export varleft_door_block: ColorRect
@export varright_door_block: ColorRect
func_on_left_door_pressed():
left_door_closed = not left_door_closed
left_door_block.visible = left_door_closed
func_on_right_door_pressed():
right_door_closed = not right_door_closed
right_door_block.visible = right_door_closed
⚡
True or False?
+15 XP
Closing a door uses a boolean variable to track its state.
You connect the door button's "clicked" signal to your function.
The not keyword in GDScript flips a boolean from true to false (or vice versa).
🧠
Knowledge Check
+15 XP
What Godot feature do we use to link a button click to our door function?
ASignals
BGroups
CAnimationPlayer
6
⚡
The Power Meter!
When the power runs out... the doors open.
Locked
🎯
Goal for this step
Add a draining power bar — doors drain it faster. When power hits 0, all doors open!
🧒
Here is the scariest part of the whole game — the POWER. You start with 100% power, but it slowly drains away as time passes. And if you keep a door closed, it drains EVEN FASTER. If the power hits zero... all the doors fly open and you're totally defenceless!
👨👧
Parent note: Power is a float from 100.0 to 0.0. In _process(delta), drain at a base rate (e.g. 2.0 per second). Each closed door adds +3.0 per second. Display it as a ProgressBar node. When power reaches 0.0, force both doors open.
What to do
1Add a ProgressBar node to your UI — position it at the bottom left.
2Add a Label showing "Power:" and a percentage number.
3Add the power logic to your NightWatch script (see code below).
4Call _update_power(delta) from inside _process(delta).
📝 GDScript — Power System
var power = 100.0var power_drained = false@export varpower_bar: ProgressBar
@export varpower_label: Label
func_process(delta):
_update_power(delta)
func_update_power(delta):
if power_drained:
returnvar drain = 2.0# base drain per secondif left_door_closed:
drain += 3.0if right_door_closed:
drain += 3.0
power -= drain * delta
power = max(power, 0.0)
power_bar.value = power
power_label.text = str(int(power)) + "%"if power <= 0.0:
_power_out()
💻
Code Challenge
+20 XP
Fill in the blanks for the power drain calculation:
var drain = 2.0
if left_door_closed:
drain +=
power -= drain *
Hint: Each door adds 3.0 to the drain rate, and we multiply by the time step.
🧠
Knowledge Check
+15 XP
What happens when the power reaches 0%?
AThe game automatically wins
BThe cameras stop working but doors stay closed
CBoth doors are forced open and you're defenceless
7
🐻
Create Your Animatronic!
Meet Freddy — he's... friendly. Probably.
Locked
🎯
Goal for this step
Build an animatronic scene with a sprite, a position tracker, and a movement timer.
🧒
Here comes the star of the show — our animatronic robot bear! We'll call him Freddy. Right now he just sits on the stage and looks cute. But in the next step, we'll make him start MOVING towards you. Tick tock, tick tock...
👨👧
Parent note: Animatronic scene: Node2D → Sprite2D + Timer node. The animatronic has a "room" variable (integer: 0=Stage, 1=HallLeft, 2=HallRight, 3=LeftDoor, 4=RightDoor). The Timer fires every few seconds. Save as freddy.tscn and instance it in main.tscn.
What to do
1Click Scene → New Scene. Root: Node2D. Rename it Freddy.
2Add a Sprite2D child. Make it big and bearlike.
3Add a Timer child. Set Wait Time: 5.0 and tick Autostart: on.
4Attach the script below. Save as freddy.tscn.
5In main.tscn, drag freddy.tscn into the scene tree.
📝 GDScript — Freddy Setup
extends Node2D
# 0 = Stage, 1 = Hall Left, 2 = Hall Right, 3 = Left Door, 4 = Right Doorvar room = 0signalreached_door(side)
@export varmove_timer: Timer
@export varmarkers: Array[ColorRect] = []
func_ready():
move_timer.timeout.connect(_on_move_timer)
func_on_move_timer():
_move_closer()
func_move_closer():
pass# We'll fill this in next step!func_update_display():
for i in markers.size():
markers[i].visible = (i == room)
✏️
Fill in the Blanks
+15 XP
The animatronic starts in room which represents the . We use a node to control when it moves.
🧠
Knowledge Check
+15 XP
What does the signal reached_door(side) line do?
AIt makes Freddy visible on the camera
BIt moves Freddy to the next room
CIt defines a custom event that Freddy can emit when reaching a door
8
👣
Animatronic Movement AI!
He's getting closer... slowly...
Locked
🎯
Goal for this step
Make Freddy move through rooms on a timer, appearing on the correct camera panel each time.
🧒
Freddy is going to creep towards you one room at a time! He moves on a timer — every few seconds he shuffles one step closer to your office. Check the cameras and you might see him moving around! When he reaches your DOOR, that's when you need to slam it shut FAST.
👨👧
Parent note: Movement path: Stage (0) → Hall Left (1) → Left Door (3) OR Hall Right (2) → Right Door (4). Randomise which side. When room == 3 or 4, emit reached_door. In main.tscn, if the corresponding door is closed, reset Freddy; if not, trigger jump scare.
What to do
1Add a ColorRect marker for Freddy inside each camera panel — link them via @export.
2Fill in the _move_closer() function in Freddy's script.
3Update _update_display() to show Freddy only on the correct camera.
4In main.tscn, connect Freddy's reached_door signal to _on_freddy_at_door.
📝 GDScript — AI Movement
func_move_closer():
match room:
0: # Stage pick a side
room = [1, 2].pick_random()
1: # Hall Left → Left Door
room = 32: # Hall Right → Right Door
room = 43, 4: # At a door — emit signal
reached_door.emit("left"if room == 3else"right")
return
_update_display()
🔢
Put the Movement Path In Order
+15 XP
Click Freddy's movement path in the correct order (left side):
Hall Left (room 1)
Left Door (room 3) — emit signal
Stage (room 0)
🧠
Knowledge Check
+15 XP
What happens when Freddy reaches a door and it's closed?
AHe breaks through the door
BHe gets sent back to the Stage (room 0)
CThe game ends immediately
9
😱
The Jump Scare!
...BOO!
Locked
🎯
Goal for this step
When the animatronic reaches an open door, trigger a full-screen jump scare and game over!
🧒
This is the most exciting step — the JUMP SCARE! If Freddy reaches your door and it's open, he JUMPS at the screen! A huge spooky face fills everything, there's a loud noise, and it's game over. It sounds scary to build but actually it's just showing a big coloured rectangle really fast!
👨👧
Parent note: Jump scare: a CanvasLayer with a high layer number (e.g. 10) containing a full-screen ColorRect and a large Label. Hidden normally. On trigger: set visible = true, play audio, wait 1.5 seconds, then show Game Over screen.
What to do
1Add a CanvasLayer to main.tscn. Set Layer to 10. Name it JumpScare.
2Inside it, add a full-screen ColorRect (red/dark) and a huge Label saying "BOO!". Set the CanvasLayer Visible: off.
3Optionally add an AudioStreamPlayer for a scary sound.
4Add the _trigger_jumpscare() function to your main.gd script.
CanvasLayer lets us draw UI elements on top of everything else.
The jump scare in FNAF lasts about 30 seconds.
The await keyword pauses the function until the timer finishes.
🧠
Knowledge Check
+15 XP
Why do we set the CanvasLayer's Layer property to 10?
ASo it renders on top of all other UI elements
BSo it runs 10 times faster
CSo it only shows on camera 10
10
🌅
Win & Lose Conditions!
Survive until 6am or face the consequences!
Locked
🎯
Goal for this step
Add a night timer counting from 12am to 6am. Survive all the way and you win!
🧒
Here's how you WIN: survive the whole night! The clock starts at midnight (12am) and slowly ticks towards 6am. If you make it to 6am without getting caught, you did it — you survived the night! Each hour takes about a minute of real time, so 6 hours = about 6 minutes.
👨👧
Parent note: Night timer: a float from 0.0 to 1.0 mapping to 12am-6am. Increment in _process by delta / (6 * 60) to make 6 minutes = a full night. Display as "12 AM", "1 AM" etc. At night_time >= 1.0, call _win().
What to do
1Add a Label for the clock — top centre. Name it ClockLabel.
2Create WinScreen and GameOverScreen CanvasLayers (both hidden). Each has a message and "Play Again" button.
3Add the night timer logic and win/lose functions to main.gd.
4Connect "Play Again" buttons to get_tree().reload_current_scene().
The player has 5% power left and the clock shows 5 AM. Both doors are closed. What will happen first?
AThe player wins because 6 AM arrives first
BThe power runs out and the doors fly open
CFreddy breaks through the closed door
🧠
Knowledge Check
+15 XP
How long does one in-game night last in real time?
A60 seconds
B30 minutes
CAbout 6 minutes
11
🎚️
Difficulty & Night 1 Tuning!
Just hard enough to be scary, just easy enough to survive!
Locked
🎯
Goal for this step
Tune Freddy's speed, power drain, and movement chance so Night 1 feels fair but tense.
🧒
Right now the game might be too easy or too hard. Let's tune it — that's what real game designers do! We'll slow Freddy down at the start and speed him up over time, and make sure the power lasts just long enough.
👨👧
Parent note: The real FNAF uses an "AI level" system from 0-20 per animatronic, checked each movement tick. An AI of 0 never moves, AI of 20 always moves. This simple random check creates emergent behaviour that feels intelligent. Start ai_level at 3 for Night 1.
What to do
1In freddy.gd, add @export var ai_level: int = 3 and wrap movement in a random check.
2Set Freddy's Timer Wait Time to 7.0 in the Inspector.
3Adjust power drain — try base drain of 1.5 and +2.5 per door.
4Play test! Tweak until Night 1 is winnable but tense.
📝 GDScript — AI Level
@export varai_level: int = 3func_on_move_timer():
# Only move if the random roll beats the AI levelif randi() % 20 < ai_level:
_move_closer()
💻
Code Challenge
+20 XP
Fill in the AI level check — Freddy only moves if the random number is less than his level:
if randi() % < :
_move_closer()
Hint: The max AI level is 20, and we compare against the ai_level variable.
🧠
Knowledge Check
+15 XP
If Freddy's AI level is 3, what are the chances he moves each tick?
A100% (always moves)
B15% (3 out of 20)
C50% (coin flip)
12
✨
Polish & Celebrate!
You built a horror game. Legend!
Locked
🎯
Goal for this step
Add final touches — atmosphere, sound, and your own creative spin!
🧒
Your game WORKS! Now let's make it feel really spooky and special. We can add flickering lights, eerie sound effects, a better looking office, and anything else YOU want. This is the fun part — make it your own!
👨👧
Parent note: Remind the child: they just built one of the most mechanically complex games in the whole series. Camera systems, AI, signals, power management, jump scares — that's real game development. They should be incredibly proud!
Polish ideas
1Flickering lights: add a Timer (0.15s) that randomly toggles a dark ColorRect.
2Camera static: briefly show a white ColorRect when switching cameras.
3Ambient sound: add an AudioStreamPlayer with a looping dark tone.
4Second animatronic: duplicate Freddy, give it a different movement path!
5Celebrate! Save everything, press F5, and survive the night!
📝 GDScript — Flickering Lights
func_on_flicker_timer_timeout():
if randf() < 0.05: # 5% chance to flicker
light_flicker.visible = trueawait get_tree().create_timer(0.08).timeout
light_flicker.visible = false
✏️
Fill in the Blanks
+15 XP
To add a flickering light, we use a node and the function to create random chance.
🧠
Final Knowledge Check
+15 XP
Which Godot concepts did you learn in this workshop? (Pick the best answer)
AOnly how to make coloured rectangles
BOnly how to use Timer nodes
CSignals, CanvasLayer, Timers, AI movement, @export, and await
🎉🐻😱🎊👻
You Built a Horror Game!
Incredible — you've made a working FNAF-style game with cameras, doors, power, AI movement, and a jump scare. That's serious game development!
Use these free browser tools alongside this workshop to create custom sprites, sounds, levels and colour schemes for your game. No installs. Free forever.