Maquetación Web Dinámica / Canvas y videojuegos 2D
Sprites y spritesheets: animar personajes
Los personajes de un juego no son formas dibujadas a mano en cada
fotograma: son imágenes, y su animación (caminar, saltar, atacar) es una
tira de dibujos que se pasan rápido — como un folioscopio. Esta lección te
enseña el drawImage completo para recortar esas tiras, y el patrón para
animarlas cuadro a cuadro.
El folioscopio y la tira de dibujos
drawImage con recorte: los 9 argumentos
Ya usaste drawImage(img, x, y) para pegar una imagen entera. La versión
poderosa toma 9 argumentos y recorta una REGIÓN de la fuente:
ctx.drawImage(
hoja, // la imagen fuente (el spritesheet)
sx, sy, sw, sh, // RECORTE en la fuente: origen x,y + ancho,alto
dx, dy, dw, dh // DESTINO en el canvas: dónde y de qué tamaño
);
// "toma el rectángulo (sx,sy,sw,sh) de la hoja y píntalo en (dx,dy) del canvas"La clave está en sx: si cada cuadro mide 56px de ancho, el cuadro número
n empieza en sx = n * 56. Cambiando ese multiplicador avanzas por la
tira. Y como dw/dh pueden diferir del recorte, escalas el sprite de
paso (pixel art se ve genial ampliado).
const ANCHO = 56, CUADROS = 4;
let cuadro = 0, tick = 0;
function update() {
tick++;
if (tick % 8 === 0) cuadro = (cuadro + 1) % CUADROS; // avanza cada 8 fotogramas
}
function draw() {
const sx = cuadro * ANCHO; // recorte del cuadro actual
ctx.drawImage(hoja, sx, 0, ANCHO, 56, x, y, ANCHO, 56);
}El tick % 8 es el control de velocidad de la animación: a 60fps,
cambiar de cuadro cada 8 fotogramas da ~7 cuadros/segundo, el ritmo de un
caminado. Sube el número para animar más lento, bájalo para más rápido —
independiente de la velocidad a la que el personaje se DESPLAZA.
Un personaje caminando
<canvas id="sprite" width="340" height="200"></canvas>body { font-family: system-ui; padding: 16px; background: #f8fafc; }
canvas { background: linear-gradient(#e0f2fe 70%, #dcfce7 70%); border: 1px solid #e2e8f0; border-radius: 12px; }const canvas = document.getElementById("sprite");
const ctx = canvas.getContext("2d");
const quieto = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const FW = 56, FH = 56, CUADROS = 4;
// construimos el spritesheet EN un canvas aparte (así el demo no depende de un PNG externo).
// En un juego real, "hoja" sería una imagen: const hoja = new Image(); hoja.src = "player.png";
const hoja = document.createElement("canvas");
hoja.width = FW * CUADROS; hoja.height = FH;
const h = hoja.getContext("2d");
for (let i = 0; i < CUADROS; i++) {
const ox = i * FW, cx = ox + FW/2;
h.fillStyle = "#6d28d9";
h.beginPath(); h.arc(cx, 24, 15, 0, Math.PI*2); h.fill();
h.fillStyle = "white";
h.beginPath(); h.arc(cx + 5, 20, 3, 0, Math.PI*2); h.fill();
h.strokeStyle = "#4c1d95"; h.lineWidth = 4; h.lineCap = "round";
const swing = Math.sin(i / CUADROS * Math.PI * 2) * 9;
h.beginPath();
h.moveTo(cx - 5, 38); h.lineTo(cx - 5 - swing, 52);
h.moveTo(cx + 5, 38); h.lineTo(cx + 5 + swing, 52);
h.stroke();
}
let cuadro = 0, tick = 0, x = 10;
const escala = 1.7, dy = 200 * 0.7 - FH * escala + 8;
function draw() {
ctx.drawImage(hoja, cuadro * FW, 0, FW, FH, x, dy, FW * escala, FH * escala);
}
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
tick++;
if (tick % 8 === 0) cuadro = (cuadro + 1) % CUADROS;
x += 1.6;
if (x > canvas.width) x = -FW * escala;
draw();
requestAnimationFrame(loop);
}
if (quieto) draw();
else requestAnimationFrame(loop);Truco del ejemplo (nota honesta de tramoya): construí el spritesheet
dibujándolo en un canvas oculto, para que el demo no dependa de descargar
un PNG. En un juego real, hoja sería const hoja = new Image(); hoja.src = "player.png" y dibujarías dentro de su onload. Pero el mecanismo que
importa — drawImage recortando el cuadro cuadro * FW y avanzándolo con
el tiempo — es EXACTAMENTE el mismo con una imagen de verdad.
Tu personaje camina, pero la animación va demasiado rápida (patalea frenético) aunque se desplaza a una velocidad normal por la pantalla. ¿Qué ajustas?
Mini-reto
Dale a tu personaje DOS animaciones: además del caminado, un estado
"quieto" (idle) que use solo el cuadro 0 cuando no se mueve. La lógica: si
hay input de movimiento, avanza los cuadros; si no, fija cuadro = 0. Es
la base de las máquinas de estados de animación de todo juego — caminar,
saltar, atacar — donde el estado del personaje decide qué tira de la hoja
reproducir.
Qué sigue
Tu personaje se mueve y se anima, pero atraviesa todo como un fantasma. La próxima lección le da cuerpo físico: DETECCIÓN DE COLISIONES — saber cuándo dos cosas se tocan. Cajas contra cajas, distancias entre círculos: la matemática (sencilla) detrás de recoger una moneda o chocar con un enemigo.