← Back to Blog Apr 23, 2026 · 08:00 PM · 10 min read

How to Make a Frogger Game: Fundamentals for Beginners

By Wuidi · Tutorial · Part 1

Frogger is a classic arcade game where you guide a frog across busy roads and dangerous rivers to reach safety on the other side. The rules are simple but the gameplay is addictive — dodge cars, ride logs, and reach the goal before time runs out. In this first part of a 6-part series, we cover the fundamentals: the grid and lane system, drawing the frog, handling keyboard input, rendering different lane types, and building the game loop.

What You Will Learn

Step 1: The Grid and Lane System

Frogger is built on a grid where each row is a lane. Lanes have types: safe zones (grass), roads (cars), water (logs), and the goal row at the top. We define the grid dimensions and a lane map that describes each row.

const COLS = 13;       // 13 columns wide
const ROWS = 13;       // 13 rows tall
const CELL = 40;       // each cell is 40px
const WIDTH = COLS * CELL;   // 520px
const HEIGHT = ROWS * CELL;  // 520px

const canvas = document.getElementById('game');
canvas.width = WIDTH;
canvas.height = HEIGHT;
const ctx = canvas.getContext('2d');

// Lane types: 'goal', 'water', 'safe', 'road'
const lanes = [
  { type: 'goal' },    // row 0 — top
  { type: 'water' },   // row 1
  { type: 'water' },   // row 2
  { type: 'water' },   // row 3
  { type: 'water' },   // row 4
  { type: 'water' },   // row 5
  { type: 'safe' },    // row 6 — middle safe zone
  { type: 'road' },    // row 7
  { type: 'road' },    // row 8
  { type: 'road' },    // row 9
  { type: 'road' },    // row 10
  { type: 'road' },    // row 11
  { type: 'safe' },    // row 12 — start (bottom)
];
Tip: Keeping lane definitions in an array makes it easy to change the level layout later. You can add more rows, swap lane types, or generate lanes procedurally for different levels.

Step 2: Drawing the Frog

The frog is a simple colored square on the grid. We track its position in grid coordinates (column and row) and draw it as a green rectangle with a slightly smaller size to create a visual gap between cells.

let frog = {
  col: Math.floor(COLS / 2),  // start center
  row: ROWS - 1               // bottom row
};

function drawFrog() {
  const x = frog.col * CELL;
  const y = frog.row * CELL;

  // Body
  ctx.fillStyle = '#2ecc40';
  ctx.fillRect(x + 3, y + 3, CELL - 6, CELL - 6);

  // Eyes
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(x + 12, y + 12, 4, 0, Math.PI * 2);
  ctx.arc(x + CELL - 12, y + 12, 4, 0, Math.PI * 2);
  ctx.fill();

  ctx.fillStyle = '#111';
  ctx.beginPath();
  ctx.arc(x + 12, y + 12, 2, 0, Math.PI * 2);
  ctx.arc(x + CELL - 12, y + 12, 2, 0, Math.PI * 2);
  ctx.fill();
}

The frog is drawn with a 3px inset from the cell edges so it does not touch the grid borders. Two small white circles with black pupils give it a recognizable face.

Step 3: Keyboard Input

Frogger uses tile-based movement — one key press moves the frog exactly one cell. We listen for arrow keys and update the frog's grid position, clamping to stay within bounds.

document.addEventListener('keydown', (e) => {
  switch (e.key) {
    case 'ArrowUp':
      if (frog.row > 0) frog.row--;
      break;
    case 'ArrowDown':
      if (frog.row < ROWS - 1) frog.row++;
      break;
    case 'ArrowLeft':
      if (frog.col > 0) frog.col--;
      break;
    case 'ArrowRight':
      if (frog.col < COLS - 1) frog.col++;
      break;
  }
});
Tip: Unlike Snake where movement is continuous, Frogger moves one tile per key press. This makes the game feel deliberate — each move is a decision. The boundary checks prevent the frog from leaving the grid.

Step 4: Lane Types and Colors

Each lane type has a distinct color so the player can instantly tell what is safe and what is dangerous. We map lane types to colors and draw each row as a filled rectangle.

const LANE_COLORS = {
  goal:  '#f1c40f',  // yellow — goal zone
  water: '#2980b9',  // blue — water (danger)
  safe:  '#27ae60',  // green — grass (safe)
  road:  '#555'      // gray — road (danger)
};

function drawLanes() {
  lanes.forEach((lane, row) => {
    ctx.fillStyle = LANE_COLORS[lane.type];
    ctx.fillRect(0, row * CELL, WIDTH, CELL);
  });
}

Drawing lanes first creates the background. Everything else — obstacles, logs, the frog — is drawn on top. The color coding is intuitive: green is safe, gray and blue are dangerous, yellow is the goal.

Step 5: The Game Loop

We use requestAnimationFrame for smooth rendering. The game loop clears the canvas, draws lanes, draws the frog, and repeats. Later we will add obstacles and collision detection inside this loop.

let lastTime = 0;

function gameLoop(timestamp) {
  const delta = timestamp - lastTime;
  lastTime = timestamp;

  // Clear
  ctx.clearRect(0, 0, WIDTH, HEIGHT);

  // Draw background lanes
  drawLanes();

  // Draw frog on top
  drawFrog();

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);
Why requestAnimationFrame? Unlike setInterval, requestAnimationFrame syncs with the browser's refresh rate (usually 60fps). It also pauses automatically when the tab is not visible, saving CPU and battery. The delta value tells you how much time passed since the last frame — essential for smooth obstacle movement in Part 2.

Step 6: Drawing Lane Decorations

Plain colored rectangles work, but adding simple decorations makes the game look much better. We draw dashed lane markings on roads and subtle wave lines on water.

function drawLaneDetails() {
  lanes.forEach((lane, row) => {
    const y = row * CELL;

    if (lane.type === 'road') {
      // Dashed center line
      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 1;
      ctx.setLineDash([10, 10]);
      ctx.beginPath();
      ctx.moveTo(0, y + CELL / 2);
      ctx.lineTo(WIDTH, y + CELL / 2);
      ctx.stroke();
      ctx.setLineDash([]);
    }

    if (lane.type === 'safe') {
      // Grass tufts
      ctx.fillStyle = '#1e8449';
      for (let c = 0; c < COLS; c += 2) {
        ctx.fillRect(
          c * CELL + 8, y + CELL - 8, 6, 6
        );
      }
    }

    if (lane.type === 'goal') {
      // Goal markers (lily pads)
      ctx.fillStyle = '#27ae60';
      for (let c = 1; c < COLS; c += 3) {
        ctx.beginPath();
        ctx.arc(
          c * CELL + CELL / 2,
          y + CELL / 2,
          CELL / 3, 0, Math.PI * 2
        );
        ctx.fill();
      }
    }
  });
}

Call drawLaneDetails() right after drawLanes() in the game loop. The dashed lines on roads mimic real road markings. Grass tufts add texture to safe zones. Green circles on the goal row represent lily pads where the frog needs to land.

Step 7: Putting It All Together

Here is the complete game loop with all the drawing functions in the correct order:

function gameLoop(timestamp) {
  const delta = timestamp - lastTime;
  lastTime = timestamp;

  ctx.clearRect(0, 0, WIDTH, HEIGHT);

  drawLanes();        // 1. background colors
  drawLaneDetails();  // 2. road lines, grass, goals
  drawFrog();         // 3. frog on top

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

The draw order matters: lanes first (background), then decorations, then the frog on top. In Part 2 we will add obstacles and logs between the lane details and the frog.

The Complete Picture

Here is how the pieces fit together so far:

  1. Grid — 13×13 cells, each 40px, defining the play area
  2. Lanes — each row has a type (goal, water, safe, road) with a color
  3. Frog — starts at bottom center, moves one tile per arrow key press
  4. Rendering — lanes drawn as colored rectangles with decorations
  5. Game loop — requestAnimationFrame for smooth 60fps rendering
Why Frogger is a great learning project: It teaches grid-based movement, lane-based level design, obstacle patterns, collision detection with moving objects, and platform mechanics (riding logs). These concepts transfer directly to endless runners, side-scrollers, and other lane-based games.

What's Next?

Right now the frog can move around a colorful grid, but there is nothing to dodge or ride. In the next part, we add the core Frogger mechanics: cars driving across roads and logs floating on water.

Continue to Part 2: Traffic & Logs where we add moving obstacles and rideable platforms.

More Articles

🚗
Tutorial Frogger Traffic & Logs: Moving Obstacles and Platforms Tutorial · Apr 23, 2026 · 09:00 PM
💥
Tutorial Frogger Collision Detection: Cars, Water, and Safe Zones Tutorial · Apr 23, 2026 · 10:00 PM
❤️
Tutorial Frogger Scoring & Lives: Points, Timer, and Game Over Tutorial · Apr 23, 2026 · 11:00 PM
← Back to Blog