130 Widgets

Semi-random thoughts and tales of tinkering

Lesson 7: Collision Detection

In Lesson 5, the player fell to a fixed "ground level." But in Minecraft, the ground is made of blocks at different heights! We need to check if the player is bumping into blocks — this is called collision detection. 🧱🚫

The Big Idea

Every frame, before we move the player, we ask: "If I move to this new position, will I be overlapping with any solid block?" If yes, we don't allow the move. If no, we move freely.

Imagine you're walking through a room. Before each step, you check: "Is there a wall where I'm about to step?" If yes, you stop. If no, you take the step. That's collision detection!

Which Block is the Player Touching?

The player's position is in pixels, but our world is a grid of blocks. To convert from pixels to block coordinates, we divide by the block size:

const BLOCK_SIZE = 32;

// Convert pixel position to block position
let blockCol = Math.floor(playerX / BLOCK_SIZE);
let blockRow = Math.floor(playerY / BLOCK_SIZE);

// Now we can check what block is at that position!
let blockType = world[blockRow][blockCol];

📐 Math.floor()

Math.floor() rounds a number down to the nearest whole number. If the player is at pixel 75, Math.floor(75 / 32) gives us 2 — meaning the player is in block column 2. This is how we translate between the smooth pixel world and the blocky grid world!

Checking for Solid Blocks

The player isn't just a single point — they have width and height! We need to check ALL the blocks that the player's body overlaps with:

function isSolid(col, row) {
  const block = getBlock(col, row);
  return block !== 0;  // Anything that isn't air is solid
}

function collidesWithWorld(px, py, pw, ph) {
  // Find which blocks the player overlaps
  const leftCol   = Math.floor(px / BLOCK_SIZE);
  const rightCol  = Math.floor((px + pw - 1) / BLOCK_SIZE);
  const topRow    = Math.floor(py / BLOCK_SIZE);
  const bottomRow = Math.floor((py + ph - 1) / BLOCK_SIZE);

  // Check each overlapped block
  for (let col = leftCol; col <= rightCol; col++) {
    for (let row = topRow; row <= bottomRow; row++) {
      if (isSolid(col, row)) return true;  // Hit something!
    }
  }
  return false;  // Nothing in the way
}

Moving With Collision

The trick is to check collision before actually moving the player. We try the new position — if it would overlap with a block, we don't move there. We handle X and Y separately so the player can slide along walls:

// --- Horizontal Movement ---
let newX = playerX;
if (keys['KeyA']) newX -= SPEED;
if (keys['KeyD']) newX += SPEED;

// Only move if the new position doesn't collide
if (!collidesWithWorld(newX, playerY, playerW, playerH)) {
  playerX = newX;  // Safe to move!
}

// --- Vertical Movement (gravity) ---
vy += GRAVITY;
let newY = playerY + vy;

if (!collidesWithWorld(playerX, newY, playerW, playerH)) {
  playerY = newY;  // Safe to fall/rise
  onGround = false;
} else {
  // We hit something!
  if (vy > 0) {
    // We were falling — snap to top of the block we hit
    playerY = Math.floor((playerY + playerH + vy) / BLOCK_SIZE) * BLOCK_SIZE - playerH;
    onGround = true;
  }
  vy = 0;  // Stop vertical movement
}
🔑 Key Insight: We move X and Y separately. If moving right would hit a wall, we cancel the X movement but still let Y happen (so the player can fall). If falling would hit the ground, we cancel Y movement but still let X happen (so the player can walk). This gives smooth movement!

Try It! Walk Around the Block World

⬆️ Click here first! A D to move, Space to jump. The player now collides with blocks!

🏆 Challenges — Try These!

  • Edit the world array in the code to add higher hills — can the player climb them?
  • Add blocks floating in the air — can the player jump onto them?
  • What happens if you remove the horizontal collision check? Try it!
  • Can you make a block type that isn't solid (like water)? Hint: make isSolid return false for that type

📝 What We Learned

  • Collision detection checks if the player would overlap with solid blocks
  • We convert pixel positions to block positions with Math.floor(px / BLOCK_SIZE)
  • We check all blocks the player's body overlaps — not just one point
  • We handle X and Y separately for smooth sliding movement
  • When the player hits the ground, we snap them to the block surface