Browser game starter
@gameforge/browser-game-starter
Turn a one-line game idea into a complete, playable single-file HTML5 canvas game: game loop, keyboard and touch input, score, best score, game over, and restart. Ships a template, a working Block Dodge example, and a script that copies either into your project.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1<!doctype html>
- 2<html lang="en">
- 3<head>
- 4<meta charset="utf-8">
- 5<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
- 6<title>Block Dodge</title>
- 7<style>
- 8 html, body { margin: 0; height: 100%; background: #0e1116; color: #eef1f5; font-family: system-ui, sans-serif; }
- 9 body { display: flex; align-items: center; justify-content: center; overflow: hidden; touch-action: none; }
- 10 canvas { background: #182030; border-radius: 8px; max-width: 100vw; max-height: 100vh; }
- 11</style>
- 12</head>
- 13<body>
- 14<!-- Idea: dodge the falling blocks for as long as you can; it gets faster every few seconds. -->
- 15<canvas id="game" width="480" height="720"></canvas>
- 16<script>
- 17"use strict";
- 18// Block Dodge: an example game made with browser-game-starter.
- 19// Move with arrow keys, A and D, or drag on a phone. Survive as long as you can.
- 21const CONFIG = {
- 22 playerSpeed: 380,
- 23 playerW: 44,
- 24 playerH: 18,
- 25 blockMin: 24,
- 26 blockMax: 70,
- 27 spawnEvery: 0.8,
- 28 minSpawnEvery: 0.22,
- 29 fallSpeed: 200,
- 30 speedUpPerSecond: 9,
- 31 shakeOnHit: 12,
- 32};
- 34const canvas = document.getElementById("game");
- 35const ctx = canvas.getContext("2d");
- 36const W = canvas.width, H = canvas.height;
- 38let state;
- 39function reset(best) {
- 40 state = {
- 41 mode: "title",
- 42 player: { x: W / 2, y: H - 50 },
- 43 blocks: [],
- 44 particles: [],
- 45 time: 0,
- 46 spawnTimer: 0,
- 47 shake: 0,
- 48 best: best ?? loadBest(),
- 49 };
- 50}
- 51function loadBest() { try { return Number(localStorage.getItem("best:block-dodge")) || 0; } catch { return 0; } }
- 52function saveBest(v) { try { localStorage.setItem("best:block-dodge", String(v)); } catch { /* storage blocked */ } }
- 54const keys = new Set();
- 55let pointerX = null;
- 56addEventListener("keydown", (e) => {
- 57 keys.add(e.key);
- 58 if (e.key === " " || e.key === "Enter") start();
- 59});
- 60addEventListener("keyup", (e) => keys.delete(e.key));
- 61function toCanvasX(clientX) {
- 62 const r = canvas.getBoundingClientRect();
- 63 return ((clientX - r.left) / r.width) * W;
- 64}
- 65canvas.addEventListener("pointerdown", (e) => { pointerX = toCanvasX(e.clientX); start(); });
- 66canvas.addEventListener("pointermove", (e) => { if (e.buttons || e.pointerType === "touch") pointerX = toCanvasX(e.clientX); });
- 67canvas.addEventListener("pointerup", () => { pointerX = null; });
- 69function start() {
- 70 if (state.mode === "playing") return;
- 71 reset(state.best);
- 72 state.mode = "playing";
- 73}
- 75function update(dt) {
- 76 state.shake = Math.max(0, state.shake - 40 * dt);
- 77 for (const pt of state.particles) { pt.x += pt.vx * dt; pt.y += pt.vy * dt; pt.vy += 600 * dt; pt.life -= dt; }
- 78 state.particles = state.particles.filter((pt) => pt.life > 0);
- 79 if (state.mode !== "playing") return;
- 81 state.time += dt;
- 82 const p = state.player;
- 83 let dir = 0;
- 84 if (keys.has("ArrowLeft") || keys.has("a")) dir -= 1;
- 85 if (keys.has("ArrowRight") || keys.has("d")) dir += 1;
- 86 if (pointerX !== null) dir = Math.sign(pointerX - p.x) * Math.min(1, Math.abs(pointerX - p.x) / 24);
- 87 p.x = Math.max(CONFIG.playerW / 2, Math.min(W - CONFIG.playerW / 2, p.x + dir * CONFIG.playerSpeed * dt));
- 89 state.spawnTimer -= dt;
- 90 if (state.spawnTimer <= 0) {
- 91 state.spawnTimer = Math.max(CONFIG.minSpawnEvery, CONFIG.spawnEvery - state.time * 0.015);
- 92 const size = CONFIG.blockMin + Math.random() * (CONFIG.blockMax - CONFIG.blockMin);
- 93 state.blocks.push({ x: Math.random() * (W - size), y: -size, size, hue: 330 + Math.random() * 60 });
- 94 }
- 95 const speed = CONFIG.fallSpeed + state.time * CONFIG.speedUpPerSecond;
- 96 for (const b of state.blocks) b.y += speed * dt;
- 97 state.blocks = state.blocks.filter((b) => b.y < H + b.size);
- 99 const px = p.x - CONFIG.playerW / 2, py = p.y - CONFIG.playerH / 2;
- 100 for (const b of state.blocks) {
- 101 if (px < b.x + b.size && px + CONFIG.playerW > b.x && py < b.y + b.size && py + CONFIG.playerH > b.y) {
- 102 return crash();
- 103 }
- 104 }
- 105}
- 107function crash() {
- 108 state.mode = "over";
- 109 state.shake = CONFIG.shakeOnHit;
- 110 for (let i = 0; i < 30; i++) {
- 111 const a = Math.random() * Math.PI * 2, v = 120 + Math.random() * 260;
- 112 state.particles.push({ x: state.player.x, y: state.player.y, vx: Math.cos(a) * v, vy: Math.sin(a) * v - 200, life: 0.8 });
- 113 }
- 114 const score = Math.floor(state.time * 10);
- 115 if (score > state.best) { state.best = score; saveBest(score); }
- 116}
- 118function draw() {
- 119 ctx.save();
- 120 ctx.clearRect(0, 0, W, H);
- 121 if (state.shake > 0) ctx.translate((Math.random() - 0.5) * state.shake, (Math.random() - 0.5) * state.shake);
- 123 for (const b of state.blocks) {
- 124 ctx.fillStyle = `hsl(${b.hue % 360} 80% 62%)`;
- 125 ctx.fillRect(b.x, b.y, b.size, b.size);
- 126 }
- 127 if (state.mode === "playing" || state.mode === "title") {
- 128 const p = state.player;
- 129 ctx.fillStyle = "#5ee0b5";
- 130 ctx.fillRect(p.x - CONFIG.playerW / 2, p.y - CONFIG.playerH / 2, CONFIG.playerW, CONFIG.playerH);
- 131 }
- 132 ctx.fillStyle = "#5ee0b5";
- 133 for (const pt of state.particles) ctx.fillRect(pt.x, pt.y, 4, 4);
- 134 ctx.restore();
- 136 const score = Math.floor(state.time * 10);
- 137 ctx.fillStyle = "#eef1f5";
- 138 ctx.font = "bold 24px system-ui, sans-serif";
- 139 ctx.textAlign = "left";
- 140 ctx.fillText(`Score ${score}`, 16, 36);
- 141 ctx.textAlign = "right";
- 142 ctx.fillText(`Best ${state.best}`, W - 16, 36);
- 144 if (state.mode !== "playing") {
- 145 ctx.fillStyle = "rgba(14, 17, 22, 0.7)";
- 146 ctx.fillRect(0, 0, W, H);
- 147 ctx.fillStyle = "#eef1f5";
- 148 ctx.textAlign = "center";
- 149 ctx.font = "bold 42px system-ui, sans-serif";
- 150 ctx.fillText(state.mode === "title" ? "Block Dodge" : "Squashed!", W / 2, H / 2 - 30);
- 151 ctx.font = "20px system-ui, sans-serif";
- 152 ctx.fillText("Arrows or drag to move", W / 2, H / 2 + 10);
- 153 ctx.fillText("Tap, click, or press Space to play", W / 2, H / 2 + 44);
- 154 }
- 155}
- 157let last = performance.now();
- 158function frame(now) {
- 159 const dt = Math.min(0.05, (now - last) / 1000);
- 160 last = now;
- 161 update(dt);
- 162 draw();
- 163 requestAnimationFrame(frame);
- 164}
- 165reset();
- 166requestAnimationFrame(frame);
- 167</script>
- 168</body>
- 169</html>
