Post

Building Squirrel Defense: 4,900 Lines, Zero Sprites, One Afternoon

Building Squirrel Defense: 4,900 Lines, Zero Sprites, One Afternoon

Every other project from Day 1 was a port. Squirrel Defense was the one I built from nothing — a game I made with my kids. No Haxe source to reference, no level data to transcribe, no existing art to match. Just my kids, a vague idea inspired by the slap minigame from the Simpsons arcade game, and our backyard bird feeder that’s constantly under siege by squirrels.

By the end of the day it had 8 squirrel types, 5 predator types, 8 bird species, 21 upgrades, procedurally generated audio, a full day/night cycle, wave-based progression, and a boss system. 4,879 lines of GDScript across 17 files. Zero image assets for any game entity — every squirrel, bird, hawk, cat, and angry grandmother is drawn with circles, lines, and polygons in _draw().

Here’s how it came together, what I learned about iterating on a game design in real-time, and the mistakes that cost the most time.

Squirrel Defense: protect the feeder

The Pitch: One Sentence

Protect a bird feeder from squirrels by slapping them. Birds land on the feeder and generate points. Squirrels try to climb the pole. You click to slap them away. Between waves, spend points on upgrades.

That was the entire design document. Everything else emerged during iteration.

Architecture: Code-Only Scene Tree

The first decision was structural. Godot normally uses .tscn scene files with a visual editor. I skipped all of that. The entire game is assembled in code:

1
2
3
4
5
6
7
8
9
10
11
func _build_scene_tree() -> void:
    bird_feeder = Node2D.new()
    bird_feeder.set_script(preload("res://scripts/bird_feeder.gd"))
    add_child(bird_feeder)

    bird_container = Node2D.new()
    add_child(bird_container)

    squirrel_container = Node2D.new()
    add_child(squirrel_container)
    # ... hand cursor, HUD, shop, wave manager

Every node is a plain Node2D or CanvasLayer with a script attached at runtime. There’s one .tscn file in the entire project — the root main.tscn that bootstraps main.gd. Everything else is pure GDScript.

Why? Two reasons. First, the AI workflow. Claude Code edits text files. It can’t click buttons in the Godot editor. A code-only architecture means the AI can build, modify, and refactor the entire game without ever opening the editor. Second, it forces you to understand every node relationship explicitly. No hidden inspector properties, no mystery signal connections. If it exists, it’s in a .gd file.

The tradeoff is obvious: you lose visual editing, drag-and-drop, and the scene preview. For a procedurally-drawn game with no sprite assets, that tradeoff is worth it.

The Art Problem: No Sprites, No Problem

I had Kenney assets for the background — trees, bushes, clouds, fences — from three CC0 packs: Background Elements Redux, Animal Pack Redux (for the splash screen decorations), and the UI Pack (for buttons). But for the actual game entities (squirrels, birds, predators, the hand cursor), I went fully procedural. Every frame, every entity redraws itself from scratch.

A squirrel is circles, ellipses, and lines:

1
2
3
4
5
6
7
8
9
10
11
func _draw_body() -> void:
    # Body ellipse
    DrawUtils.draw_filled_ellipse(self, Vector2.ZERO, body_size, body_color)
    # Head circle
    draw_circle(Vector2(dir * 8, -body_size.y * 0.6), head_radius, body_color)
    # Bushy tail: overlapping circles along a curved spine
    for i in range(12):
        var t: float = float(i) / 11.0
        var spine_pos: Vector2 = _get_tail_spine(t)
        var r: float = tail_base_radius * (1.0 - t * 0.6)
        draw_circle(spine_pos, r, tail_color)

The bushy tail was an early lesson. My first attempt used draw_colored_polygon with computed vertices for a fluffy shape. It crashed with triangulation errors on edge cases — degenerate polygons from acute angles. Overlapping circles along a spine look just as good and never crash. Sometimes the dumb approach wins.

The hand cursor is similarly procedural — an open hand with four finger lines, thumb, palm creases, and nail highlights. When you slap, it flattens into a palm-down pose with radiating impact lines. When bitten by a squirrel, it turns red and pulses with an “OUCH!” label. All _draw(), all the time.

Audio: Synthesized Everything

No WAV files in this game. Every sound effect is generated at runtime as a PackedByteArray of 16-bit PCM samples wrapped in an AudioStreamWAV.

The slap sound:

1
2
3
4
5
6
7
8
9
func _generate_slap_sound(is_hit: bool, upgraded: bool) -> AudioStreamWAV:
    var sample_rate: int = 22050
    var duration: float = 0.12 if not is_hit else 0.18
    for i in range(num_samples):
        var t: float = float(i) / float(sample_rate)
        var envelope: float = maxf(0.0, 1.0 - progress * 6.0)
        envelope = envelope * envelope  # quadratic decay
        var noise: float = randf_range(-1.0, 1.0) * envelope * 0.7
        var tone: float = sin(t * 800.0 * TAU) * envelope * 0.3

White noise with a sharp envelope for the whoosh, a tone burst for the impact. When you buy the Big Hand upgrade, the sound gets 30% longer with a lower base frequency and extra resonance harmonics. The sound feels bigger because it is bigger.

Every predator has a species-specific cry:

Predator Frequency Character
Hawk 2200Hz descending to 1400Hz Sharp screech, long decay
Owl 280Hz base, two-pulse envelope Low hoot
Crow 900Hz to 600Hz, square-wave harmonics Harsh caw
Cat 600Hz rising to 1200Hz Yowl with noise
Old Lady 400Hz with +/-100Hz warble Angry warbling shout

The jumping squirrel’s “boing” sound sweeps from 200Hz to 800Hz with spring resonance harmonics layered on top. It sounds exactly like a cartoon spring. Procedural audio is underrated — it’s tiny (no file I/O), infinitely tweakable, and surprisingly expressive.

The Squirrel Taxonomy

What started as “gray squirrels run at the feeder” evolved into eight distinct types, each requiring different strategies:

Type Wave HP Speed Behavior
Gray 1 1 80 Runs straight at feeder
Red 3 1 130 Fast, harder to hit
Jumper 4 1 90 Telegraphs a parabolic leap to the feeder
Fox 5 2 80 Tanky, survives one slap
Flying 6 1 70 Glides in from above, bypasses ground
Black 8 2 120 Fast AND tanky
Parachute 9 1 40 Drifts down slowly, hard to prioritize
Armored 11 3 55 Three slaps to kill, ignores grease

The jumper was the most interesting to design. It sits at the edge of the screen, charges up for 3.5 seconds with a clearly visible telegraph (pulsing crosshair, parabolic arc with dots, “WARNING!” text), then leaps in a ballistic arc toward the feeder. The telegraph is deliberately loud — you have time to react, but only if you notice it while dealing with ground squirrels.

Three upgrades counter jumpers specifically: Spring Trap catches them mid-arc, Feeder Cage bounces them off, Motion Sensor slows the telegraph by 40%. The design philosophy: every new threat gets a matching counter that you can buy if you want to specialize.

Boss squirrels appear from wave 5 onward — 1.4-1.8x scale, 3x health (minimum 5 HP), a golden crown, and 75% speed. They’re slow but take multiple hits. The boss chance scales from 5% at wave 5 to 20% at wave 15+.

The Food Chain

The game has three entity layers that interact:

  1. Birds land on the feeder and passively generate points
  2. Squirrels try to reach the feeder and end the game
  3. Predators scare birds away, reducing your income

You’re defending birds from squirrels, but also defending birds from predators. And sometimes defending squirrels from predators (the old lady scares everything in a 300px radius). The player has to triage: slap the squirrel climbing the pole, or slap the hawk diving at your highest-value bird?

Bird scoring scales by rarity:

Rarity Species Points/sec First Appears
Common Sparrow 1 Wave 1
Common Robin 2 Wave 1
Uncommon Blue Jay 3 Wave 4
Uncommon Cardinal 5 Wave 4
Rare Goldfinch 8 Wave 10
Rare Hummingbird 10 Wave 10
Epic Painted Bunting 15 Wave 13
Legendary Bald Eagle 25 Wave 16

Rare and higher birds get a glow effect — concentric translucent circles pulsing behind them. The Bald Eagle is drawn at 2x scale. When you see one land, you want to protect it. That tension — valuable birds making you play defensively — is the core loop.

21 Upgrades

The upgrade shop between waves is where strategy lives. With 21 options and limited points, you’re always choosing what to prioritize.

The upgrades fall into categories:

Offense: Big Hand (+50% slap radius), Quick Hands (-40% cooldown), Rubber Band Gun (auto-fires every 5s), Wind Chime (auto-knockback every 10s)

Utility: Garden Hose (right-click spray slows squirrels), Peanut Toss (middle-click distracts squirrels toward a point)

Economy: Premium Seed (+30% bird arrival speed), Gourmet Blend (rare birds more common), Double Points (2x), Bird Bath (+40% bird stay time)

Defense: Greased Pole (squirrels slip), Scarecrow (-30% climb speed near feeder), Copper Roof (+1 extra life), Hot Pepper (1 damage when touching feeder), Feeder Cage (blocks jumpers)

Anti-Jumper: Spring Trap (catches mid-leap), Feeder Cage (bounce off), Motion Sensor (slower telegraph)

Anti-Predator: Bird Whistle (scared birds return 50% sooner), Guard Dog (auto-chases ground predators)

Every upgrade has a visual on the feeder itself. Buy the Scarecrow, and a full scarecrow figure appears next to the pole — straw hat, patches, X eyes, stitched mouth. Buy the Guard Dog, and a sleeping dog appears at the base with a breathing animation. The feeder becomes a visual record of your upgrade choices.

The Day/Night Cycle

Wave progress maps to time of day. Each wave starts at dawn and ends at dusk:

1
2
3
4
5
0-15%:  Dawn   (purple → pink → orange sky)
15-30%: Sunrise (orange → blue)
30-70%: Noon   (bright blue, full sun)
70-85%: Sunset (blue → orange, warm light)
85-100%: Dusk  (orange → dark purple, stars appear)

The sun sprite follows a sinusoidal arc across the sky. Stars twinkle at dawn and dusk. Clouds drift and fade. Every background element — trees, bushes, rocks, grass — is tinted by an ambient light color that shifts with the cycle. At noon everything is bright and true-color. At sunset, everything is warm orange. At dusk, muted purple.

The sky gradient is 24 horizontal color bands interpolated between a top and bottom color. The ambient tint is multiplied into every sprite and _draw() call. It’s cheap (a single Color.lerp per element) and sells the time-of-day effect convincingly.

Terrain also varies between waves. Tree positions shift by +/-40px, new rocks and bushes appear on later waves, grass is regenerated with a seeded RNG. The backyard feels slightly different each wave without being jarring.

What Went Right

Iterative design worked. Starting with gray squirrels running at a feeder and layering complexity wave by wave produced a coherent difficulty curve. Each new enemy type was tested in isolation, then integrated. The jump mechanic went through three iterations (instant jump, slow arc, telegraphed arc with visual indicators) before it felt fair.

Procedural everything kept iteration fast. No sprite sheets to update, no audio files to re-export. Changing a squirrel’s color is one line. Making the slap sound meatier is a frequency tweak. Adding a new predator is a new entry in the type dictionary plus a _draw_*() function. The feedback loop from “I want this” to “I can see it” was seconds, not minutes.

The upgrade system created emergent strategy. I didn’t plan for “anti-jumper builds” or “economy builds” — they emerged from the 21 upgrades interacting. Players who invest early in Premium Seed + Gourmet Blend + Double Points can afford expensive upgrades faster. Players who invest in Big Hand + Quick Hands can handle more squirrels manually. The upgrade diversity creates replay value that wasn’t designed top-down.

Code-only architecture enabled AI-assisted development. Claude Code could read, modify, and extend every part of the game without visual editors. The entire 17-file codebase was legible as text. This turned out to be the biggest force multiplier — the AI could work at full speed because there were no binary assets or GUI-configured properties to work around.

What Went Wrong

The predator file got too big. predator.gd is 1,015 lines — the largest file in the project. It handles hawks, owls, crows, cats, and old ladies with completely different behavior patterns (circling, diving, sneaking, pouncing, running across). Each predator type is essentially its own AI. This should have been a base class with type-specific subclasses, but by the time the complexity warranted it, refactoring would have cost more than the code smell.

Squirrel bite mechanic is hard to discover. Squirrels that get close to your cursor charge up and bite, stunning you for 1.5 seconds. It’s a great mechanic (you can’t just park your cursor on the feeder), but there’s no tutorial or first-encounter hint. Most players get bitten and don’t understand what happened. I should have added a one-time popup: “Squirrels bite! Don’t let them get too close.”

No right-click or middle-click on mobile. The game was designed for desktop mouse input. The Garden Hose (right-click) and Peanut Toss (middle-click) upgrades are useless on touchscreens. I added on-screen buttons as a late fix, but the mobile UX is bolted on rather than designed in. Future projects should plan for touch from the start.

By the Numbers

Metric Value
Total lines of GDScript 4,879
Script files 17
Squirrel types 8
Bird species 8
Predator types 5
Upgrades 21
External sprite assets for entities 0
External audio files 0
Scene files (.tscn) 1 (root only)
Claude Code sessions 3
Estimated prompts ~20
Estimated tokens ~43.7M
Active AI processing time ~66 min

Lessons for Building Games with AI

1. Start with the core loop, not the content. The first version had one squirrel type, one bird, no upgrades, no predators. It was playable in 15 minutes. Everything after that was layering — new types, new mechanics, new visuals. The AI is good at extending working systems. It’s less good at building complex interconnected systems from scratch in one shot.

2. Procedural art is an AI superpower. The AI can write _draw() code fluently — circles, lines, polygons, color math. It struggles with sprite sheets, texture atlases, and visual editors. If your art style is procedural, the AI can iterate on visuals as fast as it iterates on logic. A squirrel redesign is a diff, not a Photoshop session.

3. Use persistent memory across sessions. The MEMORY.md file recorded every key decision: draw_string() signature in Godot 4.4, the bushy tail circles-not-polygons fix, the _update_bird_hud predator skip bug. Each session started with the AI reading this file and knowing every previous pitfall. Without it, session 3 would have re-discovered session 1’s bugs.

4. Name your magic numbers. The biggest debugging headaches were unnamed constants scattered across files — 300.0 for peanut attraction range, 250.0 for predator fear range, 80.0 for bite range. When something felt wrong (“squirrels are too aggressive”), finding the right 80.0 among dozens of numeric literals was tedious. Constants or config dictionaries from the start would have saved time.

5. Playtest between features, not after. The jumper telegraph went through three iterations because I added it, played a full wave, felt the timing was off, adjusted, played again. Each playtest was 2 minutes. If I’d batched testing (“add jumpers, flyers, and parachute squirrels, then test”), debugging timing issues across three new types simultaneously would have been much harder.

Credits

All background art — trees, bushes, clouds, sun, fence — comes from Kenney’s Background Elements Redux pack. The splash screen animals (owl, rabbit, chick, parrot) are from Kenney’s Animal Pack Redux. Buttons use Kenney’s UI Pack. All three packs are CC0 (public domain) — free to use, no attribution required, though Kenney absolutely deserves it. If you’re making games and need quality art assets, kenney.nl is an incredible resource.

Every game entity — all 8 squirrel types, 8 bird species, 5 predators, the hand cursor, the bird feeder, upgrade visuals, and all particle effects — is drawn procedurally in code with zero image assets. All audio is synthesized at runtime.

Play It

Open Fullscreen ↗

Best played on desktop (left-click to slap, right-click to spray after upgrade, middle-click for peanut toss). Mobile works via touch — tap to slap, on-screen buttons appear for spray and peanut after purchasing those upgrades.

This post is licensed under CC BY 4.0 by the author.