
Beat 'em up
Avanzas por un escenario peleando cuerpo a cuerpo contra oleadas de enemigos que te rodean, con golpes, agarres y a veces objetos del entorno usados como armas.
También se llama: yo contra el barrio, peleas callejeras, brawler
Mecánicas que lo definen
- Movimiento en un plano con profundidad: además de avanzar a lo largo de la calle, te mueves hacia el fondo y hacia el frente, y solo golpeas a quien está en tu misma franja.
- Combos cortos con un solo botón de ataque: varios golpes seguidos, el último más fuerte y que derriba o empuja al enemigo.
- Grupos de enemigos que te rodean y atacan por la espalda; colocarte para tenerlos a todos delante es la mitad del juego.
- Agarres, lanzamientos y armas que se recogen del suelo o se les quitan a los enemigos, además de comida que recupera vida.
- Avance por tramos: la pantalla se detiene hasta que vences al grupo y luego te indica que sigas.
- Cooperativo para dos o más jugadores en la misma pantalla.
Bucle de juego
Entras en un tramo, la cámara se bloquea, llegan los enemigos, te colocas para no quedar rodeado, encadenas golpes, vences al grupo y avanzas al siguiente.
Demo propia
Controles. Flechas o WASD para moverte a lo largo de la calle y hacia el fondo, espacio, Z o J para golpear. En móvil, los botones en pantalla.
Abrir en pantalla completaUna calle de tres pantallas con tres peleas. En cada una la cámara se bloquea, llegan enemigos por los dos lados y no puedes seguir hasta vencerlos. Lo justo para sentir lo que define al género, que es pelear en grupo en un plano con profundidad.
Hecha con Kaplay, en unas 202 líneas de JavaScript.
Cómo está hecha
Configuración y controles
Se definen botones virtuales para las cuatro direcciones y para golpear, así el mismo código sirve para teclado y pantalla táctil. Las constantes clave son FLOOR_TOP y FLOOR_BOTTOM, que delimitan la franja de calle por la que puedes caminar, y LANE, la distancia de profundidad dentro de la cual dos personajes se pueden golpear.
const k = kaplay({ width: 640, height: 360, letterbox: true, pixelDensity, background: [40, 36, 58], buttons: { left: { keyboard: ["left", "a"] }, right: { keyboard: ["right", "d"] }, up: { keyboard: ["up", "w"] }, down: { keyboard: ["down", "s"] }, punch: { keyboard: ["space", "z", "j"] }, }, }); touchControls(k, [ { button: "left", label: "◀" }, { button: "up", label: "▲" }, { button: "down", label: "▼" }, { button: "right", label: "▶" }, { button: "punch", label: "✊", side: "right" }, ]); const LEVEL_WIDTH = 2240; const FLOOR_TOP = 215; // the street is a band: y is depth, not height const FLOOR_BOTTOM = 340; const SPEED_X = 170; const SPEED_Y = 110; // walking into the depth is slower, like in the classics const LANE = 16; // two fighters only touch if their feet are this close in depth const PLAYER_HP = 10;La calle
El escenario es una pared con ventanas y un suelo con líneas que sugieren profundidad. Es decorado puro, sin colisiones; al final hay una puerta que marca la salida.
function buildStreet() { add([rect(LEVEL_WIDTH, FLOOR_TOP), pos(0, 0), color(70, 62, 92)]); add([rect(LEVEL_WIDTH, 360 - FLOOR_TOP), pos(0, FLOOR_TOP), color(96, 96, 110)]); for (let x = 40; x < LEVEL_WIDTH; x += 160) { add([rect(70, 90), pos(x, 60), color(110, 140, 190), opacity(0.5)]); // windows add([rect(LEVEL_WIDTH, 2), pos(0, FLOOR_TOP + 40 + (x % 3) * 30), color(120, 120, 136)]); } add([rect(30, 120), pos(LEVEL_WIDTH - 60, FLOOR_TOP - 120), color(230, 200, 90)]); // exit door }El jugador y la profundidad
Aquí está el truco del género: la coordenada y no es la altura, sino la profundidad en la calle. Por eso no hay gravedad, la velocidad hacia el fondo es menor que la horizontal y el orden de dibujo (z) se iguala a y: quien está más abajo en la pantalla está más cerca y se dibuja delante. La sombra bajo los pies ayuda a ver en qué franja estás.
const player = add([ rect(28, 60), pos(120, 280), anchor("bot"), color(120, 200, 255), opacity(1), z(0), { hp: PLAYER_HP, dir: 1, hurtTime: 0 }, ]); player.add([rect(36, 8, { radius: 4 }), anchor("center"), color(0, 0, 0), opacity(0.3)]); // shadow const fist = player.add([rect(22, 10), pos(14, -40), color(255, 230, 160), opacity(0)]); onButtonDown("left", () => (player.dir = -1)); onButtonDown("right", () => (player.dir = 1)); player.onUpdate(() => { const vx = (isButtonDown("right") ? 1 : 0) - (isButtonDown("left") ? 1 : 0); const vy = (isButtonDown("down") ? 1 : 0) - (isButtonDown("up") ? 1 : 0); if (attackTime <= 0) player.move(vx * SPEED_X, vy * SPEED_Y); // you cannot walk mid-punch player.pos.y = clamp(player.pos.y, FLOOR_TOP + 10, FLOOR_BOTTOM); player.pos.x = clamp(player.pos.x, camLeft() + 16, camLeft() + 624); player.z = player.pos.y; // whoever is lower on screen is closer: draw it in front player.hurtTime -= dt(); player.opacity = player.hurtTime > 0 && Math.floor(time() * 20) % 2 ? 0.3 : 1; fist.pos.x = player.dir > 0 ? 14 : -36; hud.text = t({ es: "Vida: ", en: "Health: " }) + "■".repeat(Math.max(0, player.hp)); });El combo de tres golpes
Cada pulsación lanza un golpe; si vuelves a pulsar dentro de la ventana de COMBO_WINDOW, sigue el siguiente de la cadena. El tercero hace más daño y empuja lejos al enemigo, lo que te da espacio cuando te rodean. Si pulsas mientras aún dura un golpe, la orden se guarda (búfer) y sale en cuanto termina: así el combo fluye aunque pulses un poco antes. Un golpe solo acierta si el enemigo está delante, cerca y en tu misma franja de profundidad.
// Punch, punch, finisher: each press inside the window chains the next hit. // The third one hits harder and knocks the enemy away, which buys you space. const COMBO = [ { damage: 1, knock: 40, time: 0.16 }, { damage: 1, knock: 40, time: 0.16 }, { damage: 2, knock: 420, time: 0.3 }, ]; const COMBO_WINDOW = 0.45; // seconds to press again and keep the chain let step = -1; let attackTime = 0; let comboTime = 0; let queued = false; // a press during a punch is remembered (input buffer) function punch() { step = comboTime > 0 ? (step + 1) % COMBO.length : 0; const hit = COMBO[step]; attackTime = hit.time; comboTime = COMBO_WINDOW; fist.opacity = 1; fist.color = step === 2 ? rgb(255, 140, 90) : rgb(255, 230, 160); for (const e of get("enemy")) { const dx = (e.pos.x - player.pos.x) * player.dir; // > 0 means in front of you if (dx > 0 && dx < 62 && Math.abs(e.pos.y - player.pos.y) < LANE) hurtEnemy(e, hit); } } onButtonPress("punch", () => (attackTime > 0 ? (queued = true) : punch())); onUpdate(() => { attackTime -= dt(); comboTime -= dt(); if (attackTime <= 0) { fist.opacity = 0; if (queued) (queued = false), punch(); } });Enemigos que rodean
Cada enemigo elige un lado del jugador y camina hasta un punto a esa distancia, en su misma franja. Como la mitad elige la izquierda y la otra mitad la derecha, el grupo te rodea solo. Antes de golpear cambia de color durante casi medio segundo: esa telegrafía es tu aviso para salir de su franja o golpearlo primero, porque recibir un golpe lo aturde y cancela su ataque.
// Each enemy picks a side of the player and walks to a spot there, so a // group surrounds you. Before hitting, it flashes for a moment (telegraph): // that is your cue to step out of its lane or to hit first. function spawnEnemy(side, delay) { const e = add([ rect(28, 58), pos(camLeft() + 320 + side * 360, rand(FLOOR_TOP + 20, FLOOR_BOTTOM)), anchor("bot"), color(220, 90, 80), z(0), { hp: 4, side, state: "walk", timer: delay, knock: 0 }, "enemy", ]); e.add([rect(36, 8, { radius: 4 }), anchor("center"), color(0, 0, 0), opacity(0.3)]); e.onUpdate(() => { e.z = e.pos.y; e.timer -= dt(); e.knock *= 0.9; e.move(e.knock, 0); if (e.state === "stun") { if (e.timer <= 0) (e.state = "walk"), (e.timer = rand(0.3, 0.8)); return; } const spot = vec2(clamp(player.pos.x + e.side * 52, camLeft() + 20, camLeft() + 620), player.pos.y); const near = Math.abs(spot.x - e.pos.x) < 8 && Math.abs(spot.y - e.pos.y) < 6; if (e.state === "walk") { e.color = rgb(220, 90, 80); if (!near) e.moveTo(spot, 95); else if (e.timer <= 0) (e.state = "windup"), (e.timer = 0.45); } else if (e.state === "windup") { e.color = rgb(255, 210, 120); // telegraph if (e.timer <= 0) { const dx = Math.abs(player.pos.x - e.pos.x); if (dx < 64 && Math.abs(player.pos.y - e.pos.y) < LANE && player.hurtTime <= 0) { player.hp--; player.hurtTime = 0.7; shake(4); if (player.hp <= 0) go("end", false); } e.state = "walk"; e.timer = rand(0.8, 1.6); // cooldown before the next attack } } }); } function hurtEnemy(e, hit) { e.hp -= hit.damage; e.state = "stun"; // getting hit cancels its own attack (hitstun) e.timer = 0.4; e.color = rgb(255, 255, 255); e.knock = hit.knock * player.dir; if (e.hp <= 0) destroy(e); }Oleadas que bloquean el avance
Cada pelea es una entrada de la lista WAVES con la posición donde empieza y cuántos enemigos trae. Al llegar, la cámara se fija y los enemigos entran por los dos lados. Cuando no queda ninguno, aparece el aviso de avanzar y la cámara se libera. Tras la última pelea, llegar a la puerta gana la partida.
// The street is split into fights. On reaching a fight the camera locks and // you cannot move on until every enemy is down, then a sign tells you to go. const WAVES = [ { x: 400, enemies: 2 }, { x: 1000, enemies: 3 }, { x: 1600, enemies: 4 }, ]; let wave = 0; let lockX = null; // camera x while a fight is on let fighting = false; onUpdate(() => { if (!fighting && wave < WAVES.length && player.pos.x >= WAVES[wave].x) { fighting = true; lockX = camPos().x; for (let i = 0; i < WAVES[wave].enemies; i++) spawnEnemy(i % 2 ? -1 : 1, i * 0.6); } if (fighting && get("enemy").length === 0) { fighting = false; lockX = null; wave++; banner.text = wave < WAVES.length ? t({ es: "¡Avanza! →", en: "Go! →" }) : t({ es: "¡Hacia la salida! →", en: "To the exit! →" }); wait(1.5, () => (banner.text = "")); } if (wave === WAVES.length && player.pos.x > LEVEL_WIDTH - 90) go("end", true); });Cámara que solo avanza
La cámara sigue al jugador hacia la derecha pero nunca retrocede, y el jugador no puede salir de lo que se ve. Durante una pelea se queda fija. Así el escenario funciona como una cinta que avanza a tramos.
// The camera only moves forward: going back is not allowed (classic belt scroll). let camX = 320; function camLeft() { return (lockX ?? camX) - 320; } onUpdate(() => { if (lockX === null) camX = clamp(Math.max(camX, player.pos.x), 320, LEVEL_WIDTH - 320); camPos(lockX ?? camX, 180); });Escenas
El juego tiene tres escenas (title, game y end) y go() salta de una a otra. Al volver a game todo empieza de cero.
function screen(title, subtitle, next) { add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 40)), anchor("center")]); add([ text(subtitle, { size: 16, font: FONT, align: "center", width: 560 }), pos(center().add(0, 30)), anchor("center"), color(200, 210, 255), ]); onButtonPress("punch", next); onKeyPress("enter", next); onClick(next); } scene("title", () => screen( t({ es: "Beat 'em up", en: "Beat 'em up" }), t({ es: "Flechas para moverte por la calle (también hacia el fondo), espacio o Z para golpear: tres golpes seguidos hacen un combo. Vence cada grupo para avanzar.\nPulsa espacio o toca para empezar.", en: "Arrows to move along the street (into the depth too), space or Z to punch: three hits in a row make a combo. Beat each group to move on.\nPress space or tap to start.", }), () => go("game"), ), ); scene("end", (won) => screen( won ? t({ es: "¡Calle despejada!", en: "Street cleared!" }) : t({ es: "Te derribaron", en: "You were knocked out" }), t({ es: "Pulsa espacio para jugar otra vez.", en: "Press space to play again." }), () => go("game"), ), ); go("title");
Qué simplificamos
- Los personajes son rectángulos con una sombra; no hay animaciones ni sonido.
- Un solo botón de ataque y un solo tipo de enemigo; no hay salto, agarres, armas ni comida.
- Los enemigos no se turnan para atacar; cada uno espera un tiempo al azar entre golpe y golpe.
Para modificarla
- Añade un agarre, que al pulsar el golpe pegado a un enemigo aturdido lo sujete y el siguiente golpe lo lance hacia atrás.
- Haz que como máximo dos enemigos puedan estar en «windup» a la vez (turnos de ataque) y compara cómo se siente con la versión original.
- Añade cajas que se rompen y sueltan comida que recupera vida.
Código completo
// Beat 'em up (brawler) demo
// Engine: Kaplay 3001 (https://kaplayjs.com). MIT licence.
import kaplay from "../../vendor/kaplay.mjs";
import { t, FONT, touchControls, pixelDensity } from "../common.js";
// #region setup
const k = kaplay({
width: 640,
height: 360,
letterbox: true, pixelDensity,
background: [40, 36, 58],
buttons: {
left: { keyboard: ["left", "a"] },
right: { keyboard: ["right", "d"] },
up: { keyboard: ["up", "w"] },
down: { keyboard: ["down", "s"] },
punch: { keyboard: ["space", "z", "j"] },
},
});
touchControls(k, [
{ button: "left", label: "◀" },
{ button: "up", label: "▲" },
{ button: "down", label: "▼" },
{ button: "right", label: "▶" },
{ button: "punch", label: "✊", side: "right" },
]);
const LEVEL_WIDTH = 2240;
const FLOOR_TOP = 215; // the street is a band: y is depth, not height
const FLOOR_BOTTOM = 340;
const SPEED_X = 170;
const SPEED_Y = 110; // walking into the depth is slower, like in the classics
const LANE = 16; // two fighters only touch if their feet are this close in depth
const PLAYER_HP = 10;
// #endregion
// #region street
function buildStreet() {
add([rect(LEVEL_WIDTH, FLOOR_TOP), pos(0, 0), color(70, 62, 92)]);
add([rect(LEVEL_WIDTH, 360 - FLOOR_TOP), pos(0, FLOOR_TOP), color(96, 96, 110)]);
for (let x = 40; x < LEVEL_WIDTH; x += 160) {
add([rect(70, 90), pos(x, 60), color(110, 140, 190), opacity(0.5)]); // windows
add([rect(LEVEL_WIDTH, 2), pos(0, FLOOR_TOP + 40 + (x % 3) * 30), color(120, 120, 136)]);
}
add([rect(30, 120), pos(LEVEL_WIDTH - 60, FLOOR_TOP - 120), color(230, 200, 90)]); // exit door
}
// #endregion
scene("game", () => {
buildStreet();
const hud = add([text("", { size: 18, font: FONT }), pos(16, 12), fixed(), z(1000)]);
const banner = add([text("", { size: 26, font: FONT }), pos(320, 120), anchor("center"), fixed(), z(1000)]);
// #region player
const player = add([
rect(28, 60), pos(120, 280), anchor("bot"), color(120, 200, 255), opacity(1), z(0),
{ hp: PLAYER_HP, dir: 1, hurtTime: 0 },
]);
player.add([rect(36, 8, { radius: 4 }), anchor("center"), color(0, 0, 0), opacity(0.3)]); // shadow
const fist = player.add([rect(22, 10), pos(14, -40), color(255, 230, 160), opacity(0)]);
onButtonDown("left", () => (player.dir = -1));
onButtonDown("right", () => (player.dir = 1));
player.onUpdate(() => {
const vx = (isButtonDown("right") ? 1 : 0) - (isButtonDown("left") ? 1 : 0);
const vy = (isButtonDown("down") ? 1 : 0) - (isButtonDown("up") ? 1 : 0);
if (attackTime <= 0) player.move(vx * SPEED_X, vy * SPEED_Y); // you cannot walk mid-punch
player.pos.y = clamp(player.pos.y, FLOOR_TOP + 10, FLOOR_BOTTOM);
player.pos.x = clamp(player.pos.x, camLeft() + 16, camLeft() + 624);
player.z = player.pos.y; // whoever is lower on screen is closer: draw it in front
player.hurtTime -= dt();
player.opacity = player.hurtTime > 0 && Math.floor(time() * 20) % 2 ? 0.3 : 1;
fist.pos.x = player.dir > 0 ? 14 : -36;
hud.text = t({ es: "Vida: ", en: "Health: " }) + "■".repeat(Math.max(0, player.hp));
});
// #endregion
// #region combo
// Punch, punch, finisher: each press inside the window chains the next hit.
// The third one hits harder and knocks the enemy away, which buys you space.
const COMBO = [
{ damage: 1, knock: 40, time: 0.16 },
{ damage: 1, knock: 40, time: 0.16 },
{ damage: 2, knock: 420, time: 0.3 },
];
const COMBO_WINDOW = 0.45; // seconds to press again and keep the chain
let step = -1;
let attackTime = 0;
let comboTime = 0;
let queued = false; // a press during a punch is remembered (input buffer)
function punch() {
step = comboTime > 0 ? (step + 1) % COMBO.length : 0;
const hit = COMBO[step];
attackTime = hit.time;
comboTime = COMBO_WINDOW;
fist.opacity = 1;
fist.color = step === 2 ? rgb(255, 140, 90) : rgb(255, 230, 160);
for (const e of get("enemy")) {
const dx = (e.pos.x - player.pos.x) * player.dir; // > 0 means in front of you
if (dx > 0 && dx < 62 && Math.abs(e.pos.y - player.pos.y) < LANE) hurtEnemy(e, hit);
}
}
onButtonPress("punch", () => (attackTime > 0 ? (queued = true) : punch()));
onUpdate(() => {
attackTime -= dt();
comboTime -= dt();
if (attackTime <= 0) {
fist.opacity = 0;
if (queued) (queued = false), punch();
}
});
// #endregion
// #region enemies
// Each enemy picks a side of the player and walks to a spot there, so a
// group surrounds you. Before hitting, it flashes for a moment (telegraph):
// that is your cue to step out of its lane or to hit first.
function spawnEnemy(side, delay) {
const e = add([
rect(28, 58), pos(camLeft() + 320 + side * 360, rand(FLOOR_TOP + 20, FLOOR_BOTTOM)), anchor("bot"),
color(220, 90, 80), z(0),
{ hp: 4, side, state: "walk", timer: delay, knock: 0 },
"enemy",
]);
e.add([rect(36, 8, { radius: 4 }), anchor("center"), color(0, 0, 0), opacity(0.3)]);
e.onUpdate(() => {
e.z = e.pos.y;
e.timer -= dt();
e.knock *= 0.9;
e.move(e.knock, 0);
if (e.state === "stun") {
if (e.timer <= 0) (e.state = "walk"), (e.timer = rand(0.3, 0.8));
return;
}
const spot = vec2(clamp(player.pos.x + e.side * 52, camLeft() + 20, camLeft() + 620), player.pos.y);
const near = Math.abs(spot.x - e.pos.x) < 8 && Math.abs(spot.y - e.pos.y) < 6;
if (e.state === "walk") {
e.color = rgb(220, 90, 80);
if (!near) e.moveTo(spot, 95);
else if (e.timer <= 0) (e.state = "windup"), (e.timer = 0.45);
} else if (e.state === "windup") {
e.color = rgb(255, 210, 120); // telegraph
if (e.timer <= 0) {
const dx = Math.abs(player.pos.x - e.pos.x);
if (dx < 64 && Math.abs(player.pos.y - e.pos.y) < LANE && player.hurtTime <= 0) {
player.hp--;
player.hurtTime = 0.7;
shake(4);
if (player.hp <= 0) go("end", false);
}
e.state = "walk";
e.timer = rand(0.8, 1.6); // cooldown before the next attack
}
}
});
}
function hurtEnemy(e, hit) {
e.hp -= hit.damage;
e.state = "stun"; // getting hit cancels its own attack (hitstun)
e.timer = 0.4;
e.color = rgb(255, 255, 255);
e.knock = hit.knock * player.dir;
if (e.hp <= 0) destroy(e);
}
// #endregion
// #region waves
// The street is split into fights. On reaching a fight the camera locks and
// you cannot move on until every enemy is down, then a sign tells you to go.
const WAVES = [
{ x: 400, enemies: 2 },
{ x: 1000, enemies: 3 },
{ x: 1600, enemies: 4 },
];
let wave = 0;
let lockX = null; // camera x while a fight is on
let fighting = false;
onUpdate(() => {
if (!fighting && wave < WAVES.length && player.pos.x >= WAVES[wave].x) {
fighting = true;
lockX = camPos().x;
for (let i = 0; i < WAVES[wave].enemies; i++) spawnEnemy(i % 2 ? -1 : 1, i * 0.6);
}
if (fighting && get("enemy").length === 0) {
fighting = false;
lockX = null;
wave++;
banner.text = wave < WAVES.length ? t({ es: "¡Avanza! →", en: "Go! →" }) : t({ es: "¡Hacia la salida! →", en: "To the exit! →" });
wait(1.5, () => (banner.text = ""));
}
if (wave === WAVES.length && player.pos.x > LEVEL_WIDTH - 90) go("end", true);
});
// #endregion
// #region camera
// The camera only moves forward: going back is not allowed (classic belt scroll).
let camX = 320;
function camLeft() {
return (lockX ?? camX) - 320;
}
onUpdate(() => {
if (lockX === null) camX = clamp(Math.max(camX, player.pos.x), 320, LEVEL_WIDTH - 320);
camPos(lockX ?? camX, 180);
});
// #endregion
});
// #region scenes
function screen(title, subtitle, next) {
add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 40)), anchor("center")]);
add([
text(subtitle, { size: 16, font: FONT, align: "center", width: 560 }),
pos(center().add(0, 30)),
anchor("center"),
color(200, 210, 255),
]);
onButtonPress("punch", next);
onKeyPress("enter", next);
onClick(next);
}
scene("title", () =>
screen(
t({ es: "Beat 'em up", en: "Beat 'em up" }),
t({
es: "Flechas para moverte por la calle (también hacia el fondo), espacio o Z para golpear: tres golpes seguidos hacen un combo. Vence cada grupo para avanzar.\nPulsa espacio o toca para empezar.",
en: "Arrows to move along the street (into the depth too), space or Z to punch: three hits in a row make a combo. Beat each group to move on.\nPress space or tap to start.",
}),
() => go("game"),
),
);
scene("end", (won) =>
screen(
won ? t({ es: "¡Calle despejada!", en: "Street cleared!" }) : t({ es: "Te derribaron", en: "You were knocked out" }),
t({ es: "Pulsa espacio para jugar otra vez.", en: "Press space to play again." }),
() => go("game"),
),
);
go("title");
// #endregionRetos de diseño
- Que se lea la profundidad: sombras bajo los personajes y dibujar delante a quien está más abajo en la pantalla, para que el jugador sepa si está en la franja del enemigo.
- Que golpear se sienta contundente con muy poco: una pausa breve al impactar, retroceso, parpadeo y una sacudida de cámara.
- Una inteligencia artificial que rodea sin atacar toda a la vez: turnos de ataque y avisos (telegrafía) antes de cada golpe para que el jugador pueda reaccionar.
- Evitar la monotonía: variar los tipos de enemigo, las armas y los escenarios para que pelear lo mismo durante media hora siga siendo interesante.
Historia
- 1984OrigenKung-Fu Master
Kung-Fu Master (Irem, 1984), inspirado en películas de artes marciales de Hong Kong, se considera el primer beat 'em up; en él solo te movías a izquierda y derecha.
Kung-Fu Master (Wikipedia) - 1986Renegade
Nekketsu Kōha Kunio-kun (Renegade en Occidente) introduce el formato de desplazamiento en cinta, en el que también te mueves hacia el fondo de la escena.
Renegade (Wikipedia) - 1987Double Dragon
Double Dragon fija el modo cooperativo para dos jugadores y abre la edad de oro del género.
Beat 'em up (Wikipedia) - 1989Final Fight
Final Fight, de Capcom, suma agarres, lanzamientos, armas de pocos usos y comida escondida en barriles, y es aclamado como uno de los mejores del género.
Final Fight (Wikipedia) - 1992Streets of Rage 2
Streets of Rage 2 es de los primeros juegos de consola que igualan el prestigio de los beat 'em ups de arcade.
Beat 'em up (Wikipedia) - 1996
Die Hard Arcade, de Sega, es el primer beat 'em up con gráficos 3D de polígonos texturizados; poco después el género casi desaparece de los arcades.
Beat 'em up (Wikipedia) - 2008Castle Crashers
Castle Crashers, de The Behemoth, lleva el género a la distribución digital con un cooperativo aclamado para cuatro jugadores, en local o en línea, y personajes que suben de nivel.
Castle Crashers (Wikipedia) - 2020Streets of Rage 4
Streets of Rage 4 renueva el interés por la saga y por el género, y supera los 2,5 millones de copias en un año.
Beat 'em up (Wikipedia)
Juegos de referencia
1984
Kung-Fu Master
Recorres de lado a lado los cinco pisos de un templo golpeando y pateando a grupos de enemigos, con un jefe al final de cada piso. Se considera el primer beat 'em up.
1986
Renegade
En Japón se llamó Nekketsu Kōha Kunio-kun. Peleas contra pandillas en escenarios por los que también te mueves hacia el fondo, el formato de «cinta» que adoptó todo el género.
1987
Double Dragon
Dos hermanos avanzan juntos por la ciudad a puñetazos; puedes quitarles las armas a los enemigos y usarlas contra ellos. Fijó el cooperativo para dos como forma clásica del género.
1989
Final Fight
Uno o dos jugadores recorren una ciudad tomada por una banda. Puedes agarrar y lanzar a los enemigos, y en los barriles rotos hay armas de pocos usos y comida que te cura.
1992
Streets of Rage 2
Eliges entre cuatro personajes, del luchador lento y fuerte al patinador rápido, y cada uno tiene ataques especiales que cuestan vida. Fue de los primeros juegos de consola que estuvieron a la altura de los beat 'em ups de arcade.
2008
Castle Crashers
Hasta cuatro caballeros pelean juntos, en local o en línea, y suben de nivel para repartir puntos entre sus atributos de combate. Llevó el beat 'em up a la distribución digital con humor de dibujo animado.
2020
Streets of Rage 4
Continuación de la saga de Sega, con unos mil cuadros de animación por personaje y el mismo esquema de avanzar por la calle venciendo grupos de enemigos. Vendió más de 2,5 millones de copias en un año.
2024
Backstreet Warriors
Un beat 'em up minimalista para PICO-8 en el que te enfrentas sin pausa a 99 rivales en un callejón, con un combo de tres golpes, patada en salto y paradas en el momento justo. Admite dos jugadores en el mismo teclado.
Ejercicio
Juega la demo y luego cambia LANE (la tolerancia de profundidad) a 4 y a 40: anota qué pasa con la estrategia en cada caso. Después añade un enemigo que no pueda ser empujado por el tercer golpe y piensa cómo cambia la forma de pelear en grupo.