Cómo dibujar un mapa de losetas con canvas

May 13, 2026 08:11 — desarrollo de videojuegos, javascript

Llevo tiempo tonteando con la idea de hacer un videojuego. No porque tenga una gran idea, sino porque quiero aprender cómo se hacen. Como estoy interesado en aprender desde "cero", no quiero usar un game engine como Unity o Godot. He probado Dragon Ruby y es genial poder programar videojuegos en ruby, mi lenguaje preferido. Al final me he decidido por javascript por una sencilla razón: siempre que tenga acceso a un navegador puedo probar conceptos desde la consola o páginas como Codepen.

En la MDN hay toda una guía de desarrollo de videojuegos en javascript. Esa guía y ChatGPT gratis han sido mis recursos por ahora.

Tengo la intención de documentar lo que voy aprendiendo con ejemplos lo más sencillos posibles, para que me sea fácil reaprender en el futuro. Retomo el aprendizaje cada ciertos meses y aunque los conceptos se quedan, reimplementarlos me cuesta un montón y acabo usando ayuda de tutoriales e IA.

Voy a empezar sin organizar el código. He leído sobre algunos patrones de diseño comunes en el desarrollo de videojuegos, pero no me quiero adelantar. Quiero ir desarrollando en base a mis necesidades.

Puedes explorar el código y ver el resultado en Codepen. A continuación está la explicación paso a paso.

Lo primero, necesitamos un canvas.

<canvas id="canvas"></canvas>

Accedemos al canvas, y obtenemos su contexto. No voy a pararme a explicar cómo funciona un canvas en este post. Puedes revisar la guía de canvas en MDN.

const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
ctx.imageSmoothingEnabled = false

De momento no me quiero liar con imágenes y sprites, así que para los primeros posts usaré cuadrados de colores.

const TILES = {
  1: "brown",
  2: "green",
  3: "blue"
}

Siguiendo el tutorial de MDN, pongo toda la información del mapa en una constante. tileSize es el temaño de cada loseta. Como en este caso son cuadrados de color, no importa mucho y se puede cambiar este valor por cualquiera. Cuando se usen imágenes, tendremos que ajustar este valor.

const MAP = {
  tileSize: 32,
  cols: 6,
  rows: 6,
  tiles: [
    2, 2, 2, 3, 2, 2,
    1, 1, 1, 3, 1, 2,
    1, 2, 1, 3, 1, 2,
    1, 1, 1, 1, 1, 2,
    5, 1, 1, 3, 1, 2,
    3, 3, 3, 3, 3, 3,
  ]
}

Una vez tenemos el mapa, podemos determinar el temaño del canvas:

canvas.width = MAP.cols * MAP.tileSize
canvas.height = MAP.rows * MAP.tileSize

Para dibujar una loseta en el mapa, he creado una función:

function drawTile(col, row, tileNumber) {
  if (tileNumber in TILES) {
    ctx.fillStyle = TILES[tileNumber]

    ctx.fillRect(
      col * MAP.tileSize,
      row * MAP.tileSize,
      MAP.tileSize,
      MAP.tileSize
    )
  }
}

Para poder debuggear más fácilmente, puedes mostrar el número de loseta añadiendo después de ctx.fillRect. Los ajustes en el contexto son globales, por lo que hay que sobreescribirlos cada vez.

ctx.fillStyle = "black"
ctx.font = "16px monospace"
ctx.textAlign = "center"
ctx.textBaseline = "middle"

ctx.fillText(
  tileNumber,
  col * MAP.tileSize + MAP.tileSize / 2,
  row * MAP.tileSize + MAP.tileSize / 2
)

Ya con el mapa definido y la función para dibujar las losetas, lo único que falta es iterar sobre cada loseta del mapa y dibujarla. Esta es una versión bastante explicita en la que creo que es bastante fácil seguir qué es lo que está pasando.

function renderMap(map) {
  let currentCol = 0
  let currentRow = 0

  map.tiles.forEach(tile => {
    drawTile(currentCol, currentRow, tile)

    if (currentCol == map.cols - 1) {
      currentCol = 0
      currentRow++
    } else {
      currentCol++
    }
  })
}

Otra versión con menos código sería:

function renderMap(map) {
  map.tiles.forEach((tile, index) => {
    const col = index % map.cols
    const row = Math.floor(index / map.cols)

    drawTile(col, row, tile)
  })
}

Hay muchas maneras diferentes de iterar sobre las losetas del mapa para dibujarlas. En el tutorial de MDN lo hacen con dos bucles for anidados, uno para las columnas y otro para las filas.

Imagino que el método elegido afectará en el futuro cuando tenga que dibujar varias capas o interactuar con ellas. De momento me parece más intuitivo un único bucle.

Por último solo queda llamar a la función para pintar el mapa.

renderMap(MAP)

Espero que con esto de copiar y pegar no se me haya pasado nada. Si tienes alguna duda o ves que algo no funciona como esperas, déjame un comentario y lo miramos.

En las siguientes iteraciones exploraré cómo añadir un cuadrado que represente al personaje jugador y se pueda mover por el mapa, así como una cámara, para que el personaje esté siempre en el centro de la pantalla y solo se vea la zona de mapa a su alrededor.

Comentarios

Nadie ha comentado nada por ahora.

Deja un comentario