130 Widgets

Semi-random thoughts and tales of tinkering

Lesson 8: Scrolling Camera

Our world so far fits on one screen. But Minecraft worlds are huge! In this lesson, we'll create a world much bigger than the screen and add a camera that follows the player. 🎥

The Problem

Our screen is maybe 600 pixels wide, but we want a world that's thousands of pixels wide. We can't see the whole world at once — we need to show just the part around the player, like looking through a window that moves.

Imagine you're looking at a giant painting through a small window. As you walk along the painting, the window shows different parts. The camera is like that window — it shows a slice of the world centered on the player.

Camera Offset

The camera has a position: cameraX and cameraY. When we draw things, we subtract the camera position from everything. This makes the world appear to slide under the camera!

let cameraX = 0;
let cameraY = 0;

function updateCamera() {
  // Center the camera on the player
  cameraX = playerX - canvas.width / 2;
  cameraY = playerY - canvas.height / 2;
}

// When drawing blocks, subtract the camera position:
function drawWorld() {
  for (let row = 0; row < ROWS; row++) {
    for (let col = 0; col < COLS; col++) {
      const block = world[row][col];
      if (block === 0) continue;

      // Convert world position to screen position
      const screenX = col * BLOCK_SIZE - cameraX;
      const screenY = row * BLOCK_SIZE - cameraY;

      ctx.fillStyle = COLORS[block];
      ctx.fillRect(screenX, screenY, BLOCK_SIZE, BLOCK_SIZE);
    }
  }
}

📐 World vs Screen Coordinates

  • World position: Where something actually IS in the world (like block at column 50)
  • Screen position: Where it appears on your monitor
  • screenX = worldX - cameraX

If the camera is at x=1000 and a block is at x=1050, the block appears at screen position 50 (near the left edge).

Smooth Camera Following

Snapping the camera directly to the player feels jerky. Instead, we make it smoothly glide toward the player:

function updateCamera() {
  // Where the camera WANTS to be
  const targetX = playerX - canvas.width / 2;
  const targetY = playerY - canvas.height / 2;

  // Move 10% of the way there each frame (smooth!)
  cameraX += (targetX - cameraX) * 0.1;
  cameraY += (targetY - cameraY) * 0.1;
}
The 0.1 trick: Moving 10% of the remaining distance each frame creates a smooth "easing" effect. When the player is far from center, the camera moves fast. As it gets closer, it slows down. This feels natural and polished!

Only Drawing Visible Blocks

Our world might have thousands of blocks, but the screen only shows a few dozen. Drawing blocks that aren't visible wastes time! We calculate which blocks are on screen and only draw those:

// Which blocks are visible?
const startCol = Math.max(0, Math.floor(cameraX / BLOCK_SIZE));
const startRow = Math.max(0, Math.floor(cameraY / BLOCK_SIZE));
const endCol = Math.min(COLS, Math.ceil((cameraX + canvas.width) / BLOCK_SIZE) + 1);
const endRow = Math.min(ROWS, Math.ceil((cameraY + canvas.height) / BLOCK_SIZE) + 1);

// Only loop through visible blocks!
for (let row = startRow; row < endRow; row++) {
  for (let col = startCol; col < endCol; col++) {
    // draw this block...
  }
}

Try It! Explore the Big World

⬆️ Click here first! A D to move, Space to jump. Explore the world — it's bigger than the screen!

🏆 Challenges — Try These!

  • Change the 0.1 smooth factor to 0.5 or 1.0 — how does it feel?
  • Make the world even wider — try 60 or 80 columns!
  • Add tall hills at different spots in the world
  • Can you make the camera look ahead of the player (in the direction they're moving)?

📝 What We Learned

  • The camera has a position — we subtract it from everything we draw
  • Smooth following moves the camera 10% of the distance each frame
  • We only draw visible blocks for good performance
  • screen position = world position - camera position