En un blogpost anterior, escribí acerca de una increíble herramienta para hacer videojuegos en JavaScript llamada Three.js, pero déjame decirte que no dependes de ella para crear videojuegos en el navegador.
Para mostrarte que esto es posible, aquí te enseñaré a crear uno de los videojuegos más famosos de Nintendo, tu propio Duck Hunt.
Si no lo conoces, te explico un poco como se juega. Es un videojuego en el cual tu objetivo es cazar a la mayor cantidad de patos posibles, pero conforme avances se tornará cada vez más difícil.
Abre tu Visual Studio Code y pongamos manos a la obra siguiendo estos pasos 👇🏻
1️⃣ Creando tu escena para Duck Hunt
Necesitamos un escenario bonito en el cual sucederá todo el show, y esto lo haremos con solo HTML y CSS. Comienza creando tu archivo indes.html con el siguiente código:
<htmllang="en">
<head>
<metacharset="UTF-8" />
<metahttp-equiv="X-UA-Compatible"content="IE=edge" />
<metaname="viewport"content="width=device-width, initial-scale=1.0" />
<title>Duck huntertitle>
<linkrel="stylesheet"href="./src/styles/main.css" />
head>
<body>
<divid="scene">
<divid="scene--information">
<buttononclick="startGame(event)">Startbutton>
<divclass="container">
<pid="bullets">Bulletsp>
<p>shotp>
div>
<divclass="container">
<divclass="scene--ducks_remaining">
<span>Hitspan>
<ul>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
<li>
<imgsrc="./duck_small.svg"class="remaining_duck white-duck"/>
li>
ul>
div>
div>
<divclass="container">
<pid="points">0p>
<p>scorep>
div>
div>
div>
body>
html>
Ya tenemos todo lo necesario del lado del HTML, pero esto no tiene forma 😵, pasemos a los estilos con CSS.
/* styles.css */
/* Normalizamos los estilos para que nada se rompa */
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
ul {
list-style: none;
}
/* Es importante que nuestra escena tenga ese position y dicho background que te dejé arriba */
#scene {
position: relative;
width: 100%;
height: 100vh;
background: url("./bg-duck-hunter.png") no-repeat;
background-size: cover;
background-position: bottom;
cursor: crosshair;
}
/* Aunque nuestro pato aún no sale a escena vamos a dejar los estilos hechos */
.duck {
width: 90px;
height: 90px;
object-fit: contain;
position: absolute;
left: 20px;
}
.scene--information {
display: flex;
width: 90%;
margin: 10px auto;
justify-content: center;
position: absolute;
bottom: 0;
}
.scene--information.container {
padding: 5px;
border: 3px solid #93c43d;
margin: 020px;
background-color: black;
color: white;
font-size: 26px;
border-radius: 10px;
text-transform: uppercase;
}
/* Cambiar los colores de un svg es un tanto complicado, estás 2 clases te ayudarán */
/* A cambiarlo de blanco a rojo y viceversa */
.white-duck {
filter: invert(100%) sepia(23%) saturate(0%) hue-rotate(122deg) brightness(
110%
) contrast(101%);
}
.red-duck {
filter: invert(30%) sepia(66%) saturate(7290%) hue-rotate(351deg) brightness(
86%
) contrast(141%);
}
.scene--ducks_remaining {
display: flex;
}
.scene--ducks_remainingul {
display: flex;
}
.scene--ducks_remainingulli {
margin: 05px;
height: 30px;
width: 30px;
}
Si juntamos ambas partes vamos a tener como resultado lo siguiente 👇🏻.

2️⃣ Poniendo al pato en escena
Aquí es donde JavaScript entra como protagonista para darle vida a nuestro videojuego.
// nodes.js
//Dentro de este archivo guardaremos todos los elementos del dom para que nuestro código quede más limpio
const scene = document.querySelector("#scene");
const duck = document.querySelector("#duck");
const bulletsParagraph = document.querySelector("#bullets");
const points = document.querySelector("#points");
// En esta parta es importante usar querySelectorAll ya que
// estaremos llamando elementos por clase y no por ID
const remainingDucks = document.querySelectorAll(".remaining_duck");
const width = scene.clientWidth;
const height = scene.clientHeight;
Ahora sí, patos a la obra 🦆
// index.js
// Antes de crear a nuestro pato haremos un par
// de funciones para que todo salga bien
// En esta variable guardaremos el número de patos
// que hemos cazado
let ducksHunted = 0;
// y hacemos lo mismo para los points y bullets
let bullets = 3;
let currentPoints = 0;
// Nuestro pato aparecerá en lugares aleatorios
// por lo que ocuparemos la siguiente función
functionsetVerticalPosition(duck) {
const max = height - duck.height * 1.5;
const min = duck.height * 1.5;
// de esta manera evitamos que el alto sea mayor
// al de nuestra escena y así no se sale
duck.style.top = `${Math.floor(Math.random() * (max - min + 1) + min)}px`;
}
// La función hunted nos ayudará a sacar al pato
// de escena cuando hagamos click sobre el
functionhunted(event) {
// Primero verificamos que tengamos balas
if (bullets >0) {
const target = event.target;
// por cada pato que cazemos tendremos 100 puntos
// más en el contador por lo que es necesario
// aumentarlo
points.innerText = currentPoints += 100;
// Después simplemente eliminamos al pato de la escena
target.remove();
// dependiendo el número de patos cazados serán
// los patos que se pintarán en la sección inferior
remainingDucks[ducksHunted].classList.remove("white-duck");
remainingDucks[ducksHunted].classList.add("red-duck");
ducksHunted += 1;
// La mecánica inicial del juego implica que cada
// que acertemos un tiro tendremos de nuevo 3 bullets
bullets = 3;
// Al final creamos una lógica que no permita
// saber si debemos continuar creando patos o no
if (ducksHunted === 10) {
console.log("Pasaste de nivel");
} else {
setTimeout(() => {
createDuck();
}, 300);
}
}
}
// Los ids no servirán más adelante para eliminar
// el movimiento de los patos
functionrandomId() {
// Con math.random generamos un id aleatorio para cada pato
const id = Math.random().toString(36).substr(2, 9);
return id;
}
functioncreateDuck() {
// Ahora juntamos todo esto para crear nuestro pato
const duck = document.createElement("img");
duck.src = "./src/images/duck_fly.png";
duck.classList.add("duck");
scene.appendChild(duck);
setVerticalPosition(duck);
duck.addEventListener("click", hunted);
duck.id = randomId();
}
// y creamos la función de startgame
functionstartGame() {
createDuck();
}
Agregamos la etiqueta de script con todo el código y tendremos el siguiente resultado 👇🏻

3️⃣ Haciendo que nuestro pato se mueva
Para agregarle ese grado de dificultad, ¿qué te parece si le damos movimiento para que sea difícil el darle caza a los patos?
// Vamos a crear un array en el cual guardaremos todos los patos que se estén moviendo
// ya que debemos de borrar su intervalo una vez estén cazados
let ducksMoving = [];
// La velocidad será importante ya que después de cada nivel será más veloces los patos
let velocity = 40;
// Pero antes de hacer la función de movimiento, debemos de asegurarnos que cuando toque el borde
// de la pantalla, el pato de la vuelta y regrese
functiontouch(duck) {
switch (true) {
case duck.x >= width - duck.width:
duck.style.transform = "rotateY(180deg)";
returntrue;
case duck.x <= 0:
duck.style.transform = "rotateY(0deg)";
returntrue;
}
}
functionmoveDuck(duck) {
let spaceLateral = 0;
// en caso de ser verdadero el movimiento se moverá a la derecha, de ser negativo
// este se moverá a la izquierda
let movement = true;
// Cada pato se va a mover en un intervalo definida por la velocidad
// lo ponemos en un array para que en caso de tener más patos en escena sea posible
// borrar su intervalo y tener problemas de memoria
duckMoving.push({
interval: setInterval(() => {
if (touch(duck)) {
movement = !movement;
}
if (movement) {
spaceLateral += 10;
} else {
spaceLateral -= 10;
}
// La manera en la que se moverá es mediante los estilos
duck.style.left = `${spaceLateral}px`;
}, velocity),
id: duck.id,
});
}
Ahora juntamos esto con nuestra función de createDuck()
y editamos a hunted()
para que quede de la siguiente forma 👇🏻.
functioncreateDuck(){
...
// Aquí solo baste con agregar la función de moveDuck al final de la función
moveDuck(duck);
}
functionhunted(event){
if (bullets >0) {
...
// en esta función solo debemos agregar la lógica para borrar el intervalo
clearInterval(
ducksMoving.filter((elem) => elem.id === target.id)[0].interval
);
// al igual que borrarlo del array
ducksMoving = ducksMoving.filter((el) => {
return el.id !== target.id;
});
...
}
}
Con todo y el movimiento tenemos lo siguiente 👀

4️⃣ Pasando de nivel
Ahora falta la cereza del pastel, ¿qué pasa cuando pasamos de nivel? La dificultad aumentará con más velocidad en los patos, por lo que debemos hacer una función que se ejecute cuando tengamos 10 patos cazados.
// vamos a crear un función super sencilla
functionnextLevel() {
// los patos de la parte inferior ahora serán todos blancos como al inicio
remainingDucks.forEach((item) => {
item.classList.remove("red-duck");
item.classList.add("white-duck");
});
// Vamos a aumentar la velocidad de movimiento
// pero debemos de bajar los milisegundos por eso
// le restamos 5
velocity -= 5;
// y los patos cazados ahora son 0
ducksHunted = 0;
}
¡Pero! ¡Hey!, nos hace falta indicarle esto a nuestro usuario.
¿Qué te parece si creamos un modal para indicarle eso?
functioncreateModal(text) {
// creamos nuestro modal con un div
const modal = document.createElement("div");
// y un contenedor para que se vea bien con los estilos
const container = document.createElement("div");
const paragraph = document.createElement("p");
// también un botón para cerrar el modal
const buttonClose = document.createElement("button");
buttonClose.addEventListener("click", () => {
modal.remove();
});
// le ponemos estilos
modal.classList.add("modal");
paragraph.innerText = text;
buttonClose.innerText = "x";
container.append(paragraph, buttonClose);
modal.appendChild(container);
// y lo desplegamos en el body
document.body.appendChild(modal);
}
Por último modificamos la función de hunted
(una vez más).
functionhunted(event){
...
if (ducksHunted === 10) {
createModal("Pasaste de nivel");
nextLevel();
} else {
setTimeout(() => {
createDuck();
}, 300);
}
}
Y aquí está la obra maestra. 🧐

💻 El repo con el código completo, sin comentarios aquí mismo.
¿Qué más puedes añadir a tu juego de Duck Hunt?
¿Quieres seguir con este videojuego?
Hay muchas oportunidades de mejora, te dejo aquí todo lo que puedes mejorar:
- Mover al pato en diagonal
- Mejorar los estilos
- Mostrar las balas restantes
- Cada que atrapes un pato te despliegue los puntos
- Desplegar múltiples patos en pantalla
- Agregar una tabla de posiciones donde puedas ver tus puntuaciones
- Agregar una barra que te diga la dificultad actual
- Hacer que los patos salgan del medio hacia alguna dirección aleatoria
Solo por mencionar algunas, imagínate agregar un jefe final al pasar determinada cantidad de niveles 🔥
Si logras completar 1 o todas pon tu repositorio y tu demo live en los comentarios, estaremos muy contentos de jugar con tu propia versión de Duck Hunt.
También te invitamos a la comunidad de la Escuela de Videojuegos en Telegram para que nos compartas todas tus dudas y estés al tanto de todo lo nuevo que hay para desarrollo de videojuegos con JavaScript.
¿Quieres empezar tu camino desarrollando videojuegos? Empieza por aquí 👇🏻
Curso de Diseño de Videojuegos