Skip to content
Alvaro Sarria
Menú
← Todo el trabajo

Dungeon Dweller

Juego infinito de supervivencia en mazmorra con motor propio de paso fijo — JavaScript puro, renderizado en el DOM y cero dependencias.

Problema

Quería entender qué es lo que un framework como React abstrae realmente — así que construí un juego en tiempo real sin framework, sin motor y sin ninguna librería externa.

Enfoque

Un juego de supervivencia en arena con vista cenital: llevas una lanza en una mazmorra de piedra mientras esqueletos y algún bruto acorazado aparecen a tu alrededor y se te echan encima. No hay condición de victoria ni final de la mazmorra — los enemigos no dejan de llegar y la partida acaba a cero de salud, así que tu resultado es la puntuación, las bajas y el tiempo sobrevivido de la pantalla de fin de partida. No hay bundler, ni dependencia que instalar, ni package.json: el navegador carga los archivos fuente tal y como están escritos.

Arquitectura

Tres capas inusualmente finas:

  • HTML declara el DOM que el juego mueve — la arena, el jugador, el HUD, la superposición y los elementos <audio> que hacen de banco de sonidos
  • CSS aporta el arte y las dimensiones físicas de cada entidad, más la selección de sprite y la animación del ciclo de caminata. JavaScript nunca fija un sprite ni un ancho; mide lo que produjo el CSS y cambia atributos a los que el CSS reacciona
  • JavaScript se encarga solo de la simulación — posiciones, colisiones, daño, temporizadores, entrada — tras un único global DD con una lista de exportación explícita, porque los scripts clásicos funcionan sin paso de compilación y sin un servidor que entienda módulos

Decisiones clave

  • Un paso fijo de 16,67 ms con acumulador, limitado a 250 ms de tiempo real por fotograma. La física es idéntica en un portátil a 60 Hz y en un monitor a 144 Hz, y volver a una pestaña en segundo plano acota la deuda a 15 pasos en lugar de acelerar la partida
  • La colisión se divide en un overlaps sin efectos secundarios y unos pasos resolve / blockMovement separados, que es lo que evita que atacar desplace cuerpos y que un enemigo te empuje a una poza de lava
  • Las posiciones del escenario son funciones del tamaño de la arena, así que redimensionar la ventana recoloca el campo en lugar de recargar y destruir la partida
  • Todos los números de balance viven en CONFIG / ENEMY_VARIANTS al principio de un solo archivo, de modo que ajustar el juego toca una pantalla de código en vez del bucle
  • Las escrituras en el DOM se cachean y el estado del sprite lo dirigen selectores de atributo en CSS en lugar de estilos en línea por fotograma — el coste dominante por fotograma en un juego renderizado en el DOM

Resultados

Una partida infinita jugable con estocada de lanza y una bola de fuego que cuesta maná, dos tipos de enemigo sobre la misma clase con números distintos, generación temporizada y por presión colocada lejos del jugador, lava y rocas que bloquean el movimiento de ambos bandos, y un HUD que sigue salud, maná, puntuación y tiempo. La salud solo se recupera matando, que es toda la tensión del juego: retirarse indefinidamente no es una estrategia.

Qué haría diferente

Movería el renderizado a <canvas> para cualquier cosa que fuera más allá de este alcance — manipular el DOM a mano es un buen ejercicio de aprendizaje, pero no escala más allá de un juego pequeño. Además, los fotogramas del ciclo de caminata rondan los 238×356 px pero se muestran a 40×90, así que el conjunto de sprites pesa mucho más de lo necesario, y siguen sin existir controles táctiles ni botón de silencio pese a que la capa de audio lo soporta.