HTML5 CANVAS · VANILLA JS · SINGLE FILE
A commercial-grade arcade space shooter — and a full developer guide on how every system inside it was built. Play it. Learn from it. Download the source.
Laser, Spread Shot, Plasma Ball, and Homing Missiles — each with power level upgrades and distinct fire rates.
Splitting asteroids, steering drones that fire back, and homing seekers that keep the pressure on.
HEXON, SERPENTIS, THE TWINS, THE WARDEN, VORTEX, THE MIMIC, SWARMKING, PULSAR, and THE ARCHITECT — each with distinct movement, attacks, and multi-phase escalation.
All music and SFX synthesized at runtime using the Web Audio API — no audio files at all.
Explosions, shockwave rings, score popups, screen shake, motion trails, and neon glow effects.
Zero dependencies. No frameworks. No build tools. Open it in any browser on any OS and it just works.
This guide walks through exactly how Neon Blaster X was built — from a blank HTML file to a fully playable commercial-style arcade game. Every system is covered: the game loop, rendering, player movement, weapons, enemies, bosses, audio, UI, and polish. Everything runs in the browser using only HTML5 Canvas and vanilla JavaScript. No frameworks, no game engine, no dependencies.
If you can open a text editor and a browser, you can follow along. We explain not just what the code does but why it was written that way.
Neon Blaster X is a top-down arcade space shooter with:
The whole thing is a single HTML file. Open it in any modern browser on Windows, Mac, or Linux.
Big game engines want you to split your code across many files and folders. For a project this size, a single HTML file is cleaner and easier to share. CSS goes in a style tag, JavaScript goes in a script tag, and HTML handles the UI overlays. The canvas handles all the actual gameplay rendering.
Every game needs a loop that runs every frame. The browser gives us requestAnimationFrame for this. It calls your function roughly 60 times per second, passing a timestamp. From two consecutive timestamps you get delta time (dt) — how many milliseconds have passed since the last frame.
Delta time is critical. Without it, your game runs faster on a 144Hz monitor than a 60Hz one. By multiplying all movement by dt, you make the game frame-rate independent.
We track what mode the game is in using a single string variable GS. Switching screens is just changing GS and toggling HTML overlay divs. The canvas keeps rendering in the background — asteroids still drift behind the pause screen for free.
The HTML canvas element is just a bitmap you draw to with JavaScript. You get a 2D context and call drawing commands. Nothing is retained between frames so you clear it at the start of each loop.
Canvas has no concept of layers. You draw things in order and later draws appear on top. Always: Background grid → Stars → Particles → Bullets → Ship → Asteroids → Enemies → Boss.
Almost everything in Neon Blaster X has a colored glow. This is just the canvas shadowBlur property. Set shadowColor and anything you draw will have a soft bloom around it.
Stars drift downward at different speeds to give a sense of depth. Each star has a speed value. Slow stars feel far away, fast ones feel close. When a star falls off the bottom it wraps to the top with a new random x position.
When something big explodes, the camera shakes. We implement this by translating the canvas context by a random small offset each frame. The shake amount decays over time.
The ship follows the mouse cursor by smoothly interpolating toward it each frame (lerp). The key is the lerp factor: multiply the distance by a small value each frame. The further away the cursor, the faster the ship moves.
The ship is drawn using canvas path commands. We translate to the ship position and rotate by the ship angle before drawing, so the shape is always defined relative to its center pointing right. This makes the math simple.
Each frame we push the ship's current position onto a trail array (max 18 entries). When drawing, we loop from oldest to newest, drawing circles that fade and shrink toward the older end.
After getting hit the ship becomes temporarily invincible and flashes. We track an invincibility timer in milliseconds. During this time hits are ignored. The flashing skips the draw call on alternating 70ms intervals.
Bullets are simple objects in an array. Each frame we update their position, check if they're off screen, and remove dead ones with a single filter call.
The Nova expands an invisible ring from the ship outward. Anything within the ring at any point during the expansion takes damage. We just check the radius against distances each frame.
Asteroids are procedurally generated jagged polygons. We pick random radii for each vertex to get that irregular rock shape. Large asteroids split into two mediums when destroyed. Mediums split into smalls. This creates satisfying chain reactions.
Drones home in on the player using smooth velocity steering — not teleporting. They also periodically fire aimed bullets. The steering uses a simple approach: add the difference between desired velocity and current velocity, scaled by a small amount each frame.
Seekers are simpler than drones. They point directly at the player and move at constant speed. They don't shoot but move faster. The difference in behavior creates nice variety in how you prioritize threats.
Enemies spawn continuously from timers. The wave system is about when those timers fire and how fast enemies move — not about spawning preset groups. When the screen is clear, a cooldown starts. After the cooldown a new wave begins and spawn timers get faster. Every 5th wave triggers a boss.
Neon Blaster X has 9 bosses that cycle every 5 waves. Each one has a completely different shape, movement style, and attack pattern. Three phases per boss (triggered at 66% and 33% HP) make fights escalate dramatically. After wave 45, the cycle repeats — but with scaling HP.
A hexagonal shape with six orbiting purple nodes. Drifts around the top third of the screen. Three attack phases: radial burst → radial plus aimed fan → dense spiral.
An 8-segment snake that weaves toward the player. The head follows the ship using atan2 plus a sine-wave side offset. Each segment pulls toward the one ahead when the gap grows too large. At higher phases, multiple segments fire simultaneously.
Two separate bosses sharing one HP bar. They fire in coordinated patterns. At 50% HP the first twin dies and the second enters rage mode. The shared HP bar is the key design trick — it looks like one pool, making the first death feel like a dramatic moment.
A giant spinning triangle with laser beams along its edges. Rotates continuously and fires beams at timed intervals, forcing the player to read rotation speed and dodge through gaps.
A gravity well that constantly pulls the ship inward. Fires homing gravity bullets that curve toward the player. The challenge is fighting both the pull force and the bullets simultaneously.
Copies the player's ship shape and mirrors movement on the x-axis. Because it mirrors your inputs, aggressive movement makes it more aggressive. The only way to win is controlled, deliberate play.
An invulnerable core shielded by 12 orbiting drones. You must destroy all drones before the core becomes vulnerable. Drones respawn at higher phases, making this a resource management fight.
Blinks in and out of visibility — only hittable when visible. Fires rings of bullets every time it appears. Managing when to shoot vs dodge requires learning the pulse timing.
Moves slowly side to side while constructing geometric bullet walls. Each wall pattern is different. The player must find and slip through the gaps before the next wall builds.
hp = 80 + wave * 20. The same boss at wave 5 vs wave 50 is a completely different fight.Almost every collision in this game is circle vs circle. It is fast, simple, and good enough for an arcade game. Two circles collide when the distance between their centers is less than the sum of their radii.
We check bullets against all asteroids, then bullets against all enemies. The key is to break out of the inner loop as soon as a bullet hits something — one bullet can only destroy one thing. Then clean up in one pass with filter.
Before doing direct damage to the player, we check if the shield has HP. If it does, we drain shield HP instead. The shield bar is just a div whose width is set by the shieldHP value as a percentage.
Particle systems are surprisingly simple. An explosion is just a burst of small objects that fly outward and fade. Each particle has a position, velocity, color, and a life value that counts down from 1 to 0.
The expanding ring is one particle with a sw flag. Its radius grows over time and its opacity fades. We draw it as a circle stroke instead of a filled dot.
Score popup text is not a canvas draw — it's a real HTML div with a CSS animation. We create it, position it over the canvas, let CSS animate it upward, then remove it after the animation ends. This is far easier than implementing text animation on canvas.
The Web Audio API lets you synthesize sounds in JavaScript without any audio files. Every sound in Neon Blaster X is generated at runtime using oscillators shaped by frequency envelopes. This keeps the file small and makes the game fully self-contained.
You need one AudioContext for the whole game. It can only be created after a user interaction (browser security rule). We initialize it on the first click or keypress.
Each sound effect is an oscillator with a frequency envelope. We set the starting frequency and use exponentialRampToValueAtTime to slide it down or up over a short duration.
Three oscillators run continuously: a low bass drone, a pulsing pad with an LFO tremolo, and an arpeggiated melody that cycles through a note array on a timer. All music, zero audio files.
The menus, pause screen, and game over screen are regular HTML divs absolutely positioned over the canvas. This is much easier than drawing menus on the canvas itself — you get CSS transitions, hover effects, and the browser handles all text rendering.
The HUD is a flex row at the top of the wrapper div. Three blocks: score/best on the left, lives in the middle, wave/kills on the right. The boss HP bar sits in the center block and is hidden until a boss spawns. All HUD values are updated by simple functions that just set textContent or adjust a div width.
Kills within a short time window increase the combo count. Every 3 kills the multiplier increments (up to 8x). The multiplier display fades in when active and fades out when the timer expires.
Scores are saved to localStorage as a JSON array. On game over the player types a name and the score is added, sorted, and sliced to keep only the top 10. The leaderboard screen renders these as HTML rows.
The difference between a rough prototype and a game that feels good to play is almost entirely in polish. None of these features add core gameplay but all of them matter enormously for how it feels.
We hide the system cursor on the canvas and overlay an SVG crosshair that we move with JavaScript. The SVG has the right visual style for the game and makes the ship feel properly targeted.
Wave banners are temporary DOM elements injected over the game. A CSS keyframe animation handles the scale-in, hold, and fade-out. We remove the element after the animation completes using a timeout.
When an enemy takes damage, it flashes white briefly. We track a flash value that starts at 1 and decays. In the draw function, if flash is above a threshold we use white for the stroke color instead of the enemy color.
The scanline overlay is a CSS repeating-linear-gradient on a div that sits above everything at high z-index but with pointer-events: none so it doesn't block interaction. It gives the whole game a slightly retro CRT feel at essentially zero cost.
shadowBlur is one of the most expensive canvas operations. Use it only when drawing and reset it immediately. Never leave it set between frames. This single rule has a huge impact on frame rate.
Creating and destroying thousands of JavaScript objects every frame causes garbage collection pauses. For high-frequency objects like bullets and particles, consider an object pool — a pre-allocated array you reuse rather than allocating new objects. Neon Blaster X uses simple push/filter which works fine at this scale. For a game with hundreds of bullets on screen simultaneously you'd want proper pooling.
Setting fillStyle, strokeStyle, lineWidth etc. adds up. Group all objects of the same color/style together in your draw loop so you change state as few times as possible.
Always multiply movement by delta time. The 50ms cap prevents the "spiral of death" where a slow frame causes large jumps which cause more processing which causes more slow frames.
Download the complete single-file game and the full developer guide PDF. Crack it open in any text editor and start learning — or modifying. Break things. That's how you learn.
FREE · OPEN SOURCE · EDUCATIONAL USE