Why Vanilla JS for Games?

Frameworks are great for web apps. But for browser games, vanilla JavaScript + Canvas 2D gives you:

  • 60fps performance without framework overhead
  • Zero dependencies — ship a single HTML file
  • Full control over the render loop
  • Instant deployment — drag and drop to any hosting

What We’re Building

A Vampire Survivors-inspired auto-shooter where:

  • Player moves with WASD/Arrow keys
  • Weapons fire automatically
  • Enemies spawn in waves
  • XP gems drop from defeated enemies
  • Level-up grants weapon upgrades

Step 1: The Game Loop

Every game starts with a loop. Here’s the foundation:

class Game {
    constructor(canvas) {
        this.ctx = canvas.getContext('2d');
        this.lastTime = 0;
        this.entities = [];
    }
    
    loop(timestamp) {
        const dt = (timestamp - this.lastTime) / 1000;
        this.lastTime = timestamp;
        
        this.update(dt);
        this.render();
        
        requestAnimationFrame((t) => this.loop(t));
    }
}

Step 2: Player Movement

Responsive movement with keyboard input:

const keys = {};
document.addEventListener('keydown', (e) => keys[e.key] = true);
document.addEventListener('keyup', (e) => keys[e.key] = false);

update(dt) {
    const speed = 200; // pixels per second
    if (keys['w'] || keys['ArrowUp']) this.y -= speed * dt;
    if (keys['s'] || keys['ArrowDown']) this.y += speed * dt;
    if (keys['a'] || keys['ArrowLeft']) this.x -= speed * dt;
    if (keys['d'] || keys['ArrowRight']) this.x += speed * dt;
}

Step 3: Auto-Firing Weapons

The key mechanic — weapons fire on a timer toward the nearest enemy:

class Weapon {
    constructor(type, cooldown) {
        this.type = type;
        this.cooldown = cooldown;
        this.timer = 0;
    }
    
    update(dt, owner, enemies) {
        this.timer -= dt;
        if (this.timer <= 0 && enemies.length > 0) {
            this.fire(owner, this.findNearest(owner, enemies));
            this.timer = this.cooldown;
        }
    }
}

Step 4: Enemy Waves

Enemies spawn from screen edges with increasing density:

spawnWave() {
    const count = Math.floor(5 + this.wave * 2);
    for (let i = 0; i < count; i++) {
        const angle = Math.random() * Math.PI * 2;
        const dist = 600;
        this.entities.push(new Enemy(
            this.player.x + Math.cos(angle) * dist,
            this.player.y + Math.sin(angle) * dist
        ));
    }
    this.wave++;
}

Step 5: XP and Level-Up System

When enemies die, they drop XP gems. Collect enough and choose an upgrade:

levelUp() {
    this.paused = true;
    const options = this.getRandomUpgrades(3);
    this.showUpgradeUI(options);
}

The Neon Glow Effect

The secret sauce for making Canvas games look professional — the glow filter:

ctx.shadowBlur = 15;
ctx.shadowColor = '#00ff88';
ctx.fillStyle = '#00ff88';
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;

This single technique transforms basic shapes into cyberpunk visuals.

Deploy in 30 Seconds

# That's it. Just open index.html or deploy to Vercel
vercel deploy --prod

The Full Source

The complete game is under 500 lines of JavaScript. No build step, no dependencies, no configuration.

Want to try it? Play Neon Survivors →

What’s Next

  • Add a boss battle system
  • Implement persistent upgrades
  • Create new weapon types
  • Port to mobile with touch controls

Browser games are the fastest way to ship a playable product. Start with Canvas, keep it simple, and iterate.