CH Charles Hayes
← back to all projects

Neon Nom

A macOS creature-evolution bullet-hell survivor in Swift and SpriteKit, built on a machine with no Xcode — so every texture, every sound, and the app icon are generated in code rather than shipped as assets.

Swift SpriteKit Swift Package Manager AppKit GLSL

The constraint

I built this on a machine with no Xcode and no Homebrew — Command Line Tools only. Everything had to compile with swift build alone.

That sounds like a minor inconvenience until you list what it rules out. Asset catalogs, SpriteKit scene files, storyboards, Metal shaders, and any bundled audio all silently require Xcode. Which is to say: the normal way you put art and sound into a game was unavailable.

What the constraint produced

Every asset is generated in code. TextureFactory draws the creatures and projectiles, AudioSystem synthesizes the sound effects, IconRenderer produces the app icon at build time. Runtime GLSL through SKShader(source:) survived the ban, so the glow is real shader work.

The neon vector look follows from that more than from any style board. Procedural drawing makes shapes, strokes, and bloom cheap; it makes bitmap art expensive. The aesthetic is what the constraint was good at.

Architecture

No physics engine. Gameplay state lives in plain struct arrays — enemies, bullets, gems, mines, chests — and pooled SKSpriteNodes are dumb views synced once per frame. Collisions run through a uniform-grid spatial hash rebuilt each frame. Nothing allocates in the hot path.

One scene, one state machine. Title, running, level-up, paused, dead, shop, and chest are states on a single GameScene with overlay panels, never separate scenes. Pools survive between runs, so retry is instant.

Content is data, not code. Weapons, modules, permanent traits, draft cards, and chest rewards are all struct-literal tables. Adding content means adding a literal. Every tuning number lives in a single config file, so a balance pass touches one place.

Testing a game with no unit tests

There are none. The regression harness is the game playing itself: an autoplay flag runs a god-mode bot through two full runs at 4× simulation speed and prints milestone lines as it goes — evolved, ascended, chest opened, stage entered, ultra down, run over. A missing line means a broken system, and the frame-rate lines during the run-2 stress test catch performance regressions.

It runs in about a minute against an isolated save file, which makes it a better gate than any test suite I would have realistically written for a hobby game.