Skip to main content

How to Make a Minecraft-Like Game

Copied to clipboard!
No boilerplate memorization required

How to Vibe Code a Minecraft-Style Block Sandbox

Stay in your creative flow. Direct AI models to generate procedural terrain chunks, mining & block-placement raycasts, and hotbar crafting—and ship a playable block-building sandbox in your browser in under 25 minutes.

Core Stack

HTML5 Canvas + JS

World Engine

Seeded Terrain Chunks

AI Partner

Claude / Gemini

Target Time

15–25 Mins

The Philosophy

The Vibe Coding Voxel & Block Philosophy

Traditional sandbox development gets bogged down in Perlin noise math, chunk mesh culling, and complex inventory serialization before you can mine a single block of dirt. Vibe coding puts you in the role of World Architect: you describe the biomes, ore distribution, day/night lighting, and hotbar blocks while conversational AI builds the terrain generator and block-interaction loop.

Old-School Voxel Stress

  • Spending weeks writing custom C++ chunk meshers before placing a single block.
  • Tedious resource gathering loops that frustrate players before they can build.
  • Fighting browser right-click context menus when trying to place blocks.
  • Losing your built house every time you refresh the browser tab.

The Vibe Coder Way

  • Instant 2D Terraria/Minecraft-style block world or Three.js 3D voxel starter in a single index.html.
  • Prompt the world: "Generate rolling hills with grass, dirt, stone, coal/gold ores, and surface oak trees."
  • Left-click to mine blocks into your hotbar, Right-click (or Shift+Click) to build anything you imagine.
  • Save and load modified world chunks straight to localStorage.
Execution

The 4-Step Blueprint

From a terrain seed to mining ores, building castles, and surviving the night.

Step 01

Spawn the Procedural Chunk World

Create a single index.html file. Use a 1D/2D heightmap wave to generate layered terrain: sky, grass, dirt, deep stone, ores, and trees.

💡 Pro Vibe Tip: Starting with a 2D side-view block sandbox (or clean isometric/WebGL grid) lets you iterate on mining, crafting, and mobs 5x faster.
Step 02

The "One-Shot Architect" Prompt

Ask your AI for player movement + jumping, a reach radius cursor highlight, Left-Click mining into inventory counts, and Right-Click block placement.

🔑 Key Constraint: Tell the AI: "Call e.preventDefault() on the canvas contextmenu event so right-clicking places blocks cleanly."
Step 03

Hotbar, Crafting & Night Mobs

Add a 1–6 number-key hotbar (Grass, Dirt, Cobblestone, Wood Planks, Brick, Torch), a day-to-night sky transition, and slimes/zombies to fight.

✨ Iteration Secret: Prevent placing a solid block directly inside the player's own bounding box so they never trap themselves in stone.
Step 04

World Save & Creative Freedom

Serialize your chunk grid and inventory to localStorage so players can return to their custom-built fortress anytime.

🏆 The Grand Payoff: An open-ended sandbox where players can mine, explore, and build whatever they imagine right in their browser.
Interactive Tool

Vibe Prompt Configurator

Configure your block world dimension, survival rules, and building tools. Copy the generated prompt into Claude, Cursor, or Gemini.

Special Feature Vibe
Optional custom details added straight into your master prompt.
Generated Master Prompt
Ready to paste into Claude, Gemini, or ChatGPT Single-file HTML5 Canvas ready
Sandbox Spec

Essential Elements for a Minecraft-Style Game

Since Markus Persson and Mojang launched Minecraft, the best voxel and block sandbox games have shared these five core pillars:

  • Creative Block Building

    Let players place and combine different block types (wood, stone, brick, glass) to build anything they can imagine—from cozy cabins to sprawling castles.

  • Explorable Open World & Chunks

    Generate terrain from a map seed split into chunks, complete with hills, surface trees, underground caverns, and hidden ore veins to discover.

  • Rewarding Resource Gathering

    Keep mining snappy and plentiful—every broken block drops directly into the player's hotbar with satisfying particle crumbs so gathering never feels tedious.

  • Creature Combat & Survival

    Populate the world with creatures and night mobs that add excitement, challenge, and a reason to build protective shelters and craft better gear.

  • Unrestricted Creative Freedom

    Avoid rigid linear rules. Give players the freedom to set their own goals—whether mining deep for gold, landscaping a mountain, or experimenting with builds.

  • Intuitive Hotbar & World Save

    Provide a 1–6 quick-select hotbar for switching block materials on the fly and save world edits so creations persist across sessions.

UX Checklist Before Shipping: Highlight the hovered grid tile when it is within mining/building reach, disable the browser right-click menu on the canvas, and prevent placing blocks inside the player's own body.
Starter Template

The "Golden" Single-File Block Sandbox Template

Save this as index.html and open it in any browser to explore, mine, and build in a procedural Minecraft-style 2D block world.

index.html
HTML5 Canvas + Vanilla JS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VibeCraft: Procedural Block Sandbox</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
  body {
    background: #090d16;
    color: #f8fafc;
    font-family: system-ui, -apple-system, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 10px;
  }
  .bar {
    margin-bottom: 8px;
    padding: 8px 16px;
    background: #131c31;
    border: 1px solid #283655;
    border-radius: 999px;
    font-size: 13px;
    color: #cbd5e1;
  }
  canvas {
    border: 2px solid #38bdf8;
    border-radius: 12px;
    box-shadow: 0 20px 50px rgba(0,0,0,0.65);
    max-width: 100%;
    height: auto;
    cursor: crosshair;
  }
</style>
</head>
<body>
  <div class="bar">
    <strong>VibeCraft ⛏️</strong> — Move: <kbd>A</kbd>/<kbd>D</kbd> • Jump: <kbd>W</kbd>/<kbd>Space</kbd> • <strong>Left-Click</strong>: Mine Block • <strong>Right-Click</strong>: Place Block • Keys <kbd>1–6</kbd>: Select Hotbar
  </div>
  <canvas id="game" width="900" height="500"></canvas>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
canvas.addEventListener('contextmenu', e => e.preventDefault());

const TILE = 28;
const WORLD_W = 64;
const WORLD_H = 28;

// 0: Air, 1: Grass, 2: Dirt, 3: Stone, 4: Wood, 5: Leaves, 6: Gold Ore, 7: Brick
const BLOCKS = {
  1: { name: 'Grass', color: '#22c55e', top: '#4ade80', solid: true },
  2: { name: 'Dirt', color: '#92400e', top: '#b45309', solid: true },
  3: { name: 'Stone', color: '#64748b', top: '#94a3b8', solid: true },
  4: { name: 'Wood', color: '#78350f', top: '#a16207', solid: true },
  5: { name: 'Leaves', color: '#15803d', top: '#16a34a', solid: false },
  6: { name: 'Gold', color: '#eab308', top: '#fef08a', solid: true },
  7: { name: 'Brick', color: '#dc2626', top: '#f87171', solid: true }
};

const hotbar = [
  { id: 1, count: 15 },
  { id: 2, count: 25 },
  { id: 3, count: 20 },
  { id: 4, count: 15 },
  { id: 6, count: 5 },
  { id: 7, count: 30 }
];
let selectedSlot = 0;
let world = [];
let particles = [];
let mouse = { x: 0, y: 0 };
const keys = {};

function generateWorld() {
  world = Array.from({ length: WORLD_H }, () => Array(WORLD_W).fill(0));
  for (let x = 0; x < WORLD_W; x++) {
    const surface = Math.floor(11 + Math.sin(x * 0.18) * 2.5 + Math.cos(x * 0.07) * 1.5);
    for (let y = surface; y < WORLD_H; y++) {
      if (y === surface) world[y][x] = 1;
      else if (y < surface + 3) world[y][x] = 2;
      else world[y][x] = (Math.random() < 0.09 && y > surface + 5) ? 6 : 3;
    }
    // Trees
    if (x > 5 && x < WORLD_W - 5 && x % 9 === 0) {
      for (let ty = 1; ty <= 3; ty++) world[surface - ty][x] = 4;
      for (let lx = -1; lx <= 1; lx++) {
        for (let ly = -5; ly <= -3; ly++) {
          if (world[surface + ly][x + lx] === 0) world[surface + ly][x + lx] = 5;
        }
      }
    }
  }
}

const player = { x: 16 * TILE, y: 6 * TILE, w: 20, h: 42, vx: 0, vy: 0, grounded: false };
let camX = 0;
let camY = 0;
let timeAngle = 0;

window.addEventListener('keydown', e => {
  keys[e.code] = true;
  if (e.code.startsWith('Digit')) {
    const n = Number(e.code.replace('Digit', '')) - 1;
    if (n >= 0 && n < hotbar.length) selectedSlot = n;
  }
});
window.addEventListener('keyup', e => { keys[e.code] = false; });

canvas.addEventListener('mousemove', e => {
  const r = canvas.getBoundingClientRect();
  mouse.x = (e.clientX - r.left) * (canvas.width / r.width);
  mouse.y = (e.clientY - r.top) * (canvas.height / r.height);
});

canvas.addEventListener('mousedown', e => {
  const tx = Math.floor((mouse.x + camX) / TILE);
  const ty = Math.floor((mouse.y + camY) / TILE);
  if (tx < 0 || tx >= WORLD_W || ty < 0 || ty >= WORLD_H) return;

  const px = player.x + player.w / 2;
  const py = player.y + player.h / 2;
  const dist = Math.hypot((tx + 0.5) * TILE - px, (ty + 0.5) * TILE - py);
  if (dist > TILE * 5.5) return;

  // Left click (or normal click without right button): Mine
  if (e.button === 0 && !e.shiftKey) {
    const id = world[ty][tx];
    if (id !== 0) {
      const slot = hotbar.find(s => s.id === id);
      if (slot) slot.count++;
      for (let i = 0; i < 8; i++) {
        particles.push({
          x: tx * TILE + 14,
          y: ty * TILE + 14,
          vx: (Math.random() - 0.5) * 5,
          vy: (Math.random() - 0.8) * 5,
          color: BLOCKS[id].color,
          life: 20
        });
      }
      world[ty][tx] = 0;
    }
  } else if (e.button === 2 || e.shiftKey) {
    // Right click: Place selected hotbar block
    const slot = hotbar[selectedSlot];
    if (world[ty][tx] === 0 && slot && slot.count > 0) {
      const bx = tx * TILE;
      const by = ty * TILE;
      const overlapsPlayer = (
        player.x < bx + TILE &&
        player.x + player.w > bx &&
        player.y < by + TILE &&
        player.y + player.h > by
      );
      if (!overlapsPlayer) {
        world[ty][tx] = slot.id;
        slot.count--;
      }
    }
  }
});

function isSolid(tx, ty) {
  if (tx < 0 || tx >= WORLD_W || ty < 0 || ty >= WORLD_H) return true;
  const id = world[ty][tx];
  return id !== 0 && BLOCKS[id].solid;
}

function collideX() {
  const top = Math.floor(player.y / TILE);
  const bottom = Math.floor((player.y + player.h - 1) / TILE);
  if (player.vx > 0) {
    const right = Math.floor((player.x + player.w) / TILE);
    for (let y = top; y <= bottom; y++) {
      if (isSolid(right, y)) {
        player.x = right * TILE - player.w - 0.01;
        player.vx = 0;
      }
    }
  } else if (player.vx < 0) {
    const left = Math.floor(player.x / TILE);
    for (let y = top; y <= bottom; y++) {
      if (isSolid(left, y)) {
        player.x = (left + 1) * TILE + 0.01;
        player.vx = 0;
      }
    }
  }
}

function collideY() {
  player.grounded = false;
  const left = Math.floor(player.x / TILE);
  const right = Math.floor((player.x + player.w - 1) / TILE);
  if (player.vy > 0) {
    const bottom = Math.floor((player.y + player.h) / TILE);
    for (let x = left; x <= right; x++) {
      if (isSolid(x, bottom)) {
        player.y = bottom * TILE - player.h - 0.01;
        player.vy = 0;
        player.grounded = true;
      }
    }
  } else if (player.vy < 0) {
    const top = Math.floor(player.y / TILE);
    for (let x = left; x <= right; x++) {
      if (isSolid(x, top)) {
        player.y = (top + 1) * TILE + 0.01;
        player.vy = 0;
      }
    }
  }
}

function update() {
  timeAngle += 0.0012;
  player.vx = 0;
  if (keys['KeyA'] || keys['ArrowLeft']) player.vx = -3.8;
  if (keys['KeyD'] || keys['ArrowRight']) player.vx = 3.8;
  if ((keys['KeyW'] || keys['Space'] || keys['ArrowUp']) && player.grounded) {
    player.vy = -9.6;
  }

  player.x += player.vx;
  collideX();
  player.vy = Math.min(11, player.vy + 0.48);
  player.y += player.vy;
  collideY();

  camX += ((player.x - canvas.width / 2) - camX) * 0.12;
  camY += ((player.y - canvas.height / 2) - camY) * 0.12;
  camX = Math.max(0, Math.min(WORLD_W * TILE - canvas.width, camX));
  camY = Math.max(0, Math.min(WORLD_H * TILE - canvas.height, camY));

  particles.forEach(p => {
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.25;
    p.life--;
  });
  particles = particles.filter(p => p.life > 0);
}

function draw() {
  // Day / Night sky gradient
  const daylight = (Math.sin(timeAngle) + 1) * 0.5;
  ctx.fillStyle = daylight > 0.35 ? '#0ea5e9' : '#0f172a';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.save();
  ctx.translate(-camX, -camY);

  const startX = Math.max(0, Math.floor(camX / TILE));
  const endX = Math.min(WORLD_W - 1, Math.ceil((camX + canvas.width) / TILE));
  const startY = Math.max(0, Math.floor(camY / TILE));
  const endY = Math.min(WORLD_H - 1, Math.ceil((camY + canvas.height) / TILE));

  for (let y = startY; y <= endY; y++) {
    for (let x = startX; x <= endX; x++) {
      const id = world[y][x];
      if (id === 0) continue;
      const b = BLOCKS[id];
      ctx.fillStyle = b.color;
      ctx.fillRect(x * TILE, y * TILE, TILE, TILE);
      ctx.fillStyle = b.top;
      ctx.fillRect(x * TILE, y * TILE, TILE, 5);
      ctx.strokeStyle = 'rgba(0,0,0,0.2)';
      ctx.strokeRect(x * TILE, y * TILE, TILE, TILE);
    }
  }

  // Cursor reach highlight
  const tx = Math.floor((mouse.x + camX) / TILE);
  const ty = Math.floor((mouse.y + camY) / TILE);
  const dist = Math.hypot((tx + 0.5) * TILE - (player.x + 10), (ty + 0.5) * TILE - (player.y + 21));
  if (dist <= TILE * 5.5) {
    ctx.strokeStyle = '#facc15';
    ctx.lineWidth = 2;
    ctx.strokeRect(tx * TILE + 1, ty * TILE + 1, TILE - 2, TILE - 2);
  }

  // Player character
  ctx.fillStyle = '#38bdf8';
  ctx.fillRect(player.x, player.y + 12, player.w, player.h - 12);
  ctx.fillStyle = '#fde047';
  ctx.fillRect(player.x + 2, player.y, player.w - 4, 14);

  particles.forEach(p => {
    ctx.fillStyle = p.color;
    ctx.fillRect(p.x, p.y, 5, 5);
  });

  ctx.restore();

  // Hotbar UI
  const hbW = hotbar.length * 56;
  const hbX = (canvas.width - hbW) / 2;
  const hbY = canvas.height - 62;

  hotbar.forEach((slot, idx) => {
    const x = hbX + idx * 56;
    ctx.fillStyle = idx === selectedSlot ? '#1e293b' : 'rgba(15, 23, 42, 0.85)';
    ctx.strokeStyle = idx === selectedSlot ? '#facc15' : '#475569';
    ctx.lineWidth = idx === selectedSlot ? 3 : 1.5;
    ctx.beginPath();
    ctx.roundRect(x, hbY, 50, 50, 8);
    ctx.fill();
    ctx.stroke();

    const b = BLOCKS[slot.id];
    ctx.fillStyle = b.color;
    ctx.fillRect(x + 13, hbY + 10, 24, 24);
    ctx.fillStyle = '#f8fafc';
    ctx.font = 'bold 11px sans-serif';
    ctx.textAlign = 'right';
    ctx.fillText(slot.count, x + 45, hbY + 45);
    ctx.textAlign = 'left';
    ctx.fillStyle = '#94a3b8';
    ctx.fillText(idx + 1, x + 5, hbY + 13);
  });

  requestAnimationFrame(() => {
    update();
    draw();
  });
}

generateWorld();
draw();
</script>
</body>
</html>
Troubleshooting

How to Debug Without Losing Your Vibe

Block sandboxes have three classic grid and camera pitfalls. Use these vibe debugging prompts to solve them fast:

Block Browser Context Menu

So right-clicking places blocks cleanly without popping up the browser menu, prompt: "Add canvas.addEventListener('contextmenu', e => e.preventDefault()) and support Shift+Click for trackpad users."

Prevent Self-Burial Placement

Stop players from placing a stone block directly on top of their own character by telling the AI: "Check AABB overlap between the target tile and the player's hitbox before placing any solid block."

Viewport Frustum Culling

Keep large worlds running at 60fps by instructing your AI coding partner: "Only loop over tile indices visible inside the camera bounds (startX..endX, startY..endY) during draw()."

Extensions

The Vibe Prompt Vault

Copy-paste prompts to add a Crafting Menu, Torch Lighting, or a 3D Three.js First-Person Voxel Engine.

🛠️ Prompt: Add a Crafting Bench Overlay & Pickaxe Mining Speeds
Prompt Template

"Extend my HTML5 Canvas block sandbox in index.html with a Crafting menu toggled by pressing 'E'. Let the player craft Wood Planks (from Wood), Torches (from Wood + Coal), and a Gold Pickaxe (from Wood + Gold Ore) that doubles mining reach and spawns golden spark particles."

🕯️ Prompt: Add Underground Cave Darkness & Placeable Glowing Torches
Prompt Template

"Add a tile lighting system to my 2D Minecraft sandbox. Darken underground tiles below surface depth unless they are within a 6-tile radius of a placed Torch block or the player's lantern, and animate a warm flickering radial glow around every torch."

🧊 Prompt: Build a 3D First-Person Voxel World with Three.js CDN
Prompt Template

"Create a complete single-file 3D Minecraft-like voxel game in index.html using Three.js from CDN. Generate a 24x24 chunk of procedural grass, dirt, stone, and tree cubes using InstancedMesh, add PointerLock first-person WASD + Space jump physics, and use Raycaster so Left-Click removes the targeted cube and Right-Click places a new cube on the hit face."

We use cookies for ads and analytics.