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 👇🏻
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 👇🏻.
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 limpioconst 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 IDconst 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 cazadolet ducksHunted = 0;
// y hacemos lo mismo para los points y bulletslet bullets = 3;
let currentPoints = 0;
// Nuestro pato aparecerá en lugares aleatorios// por lo que ocuparemos la siguiente funciónfunctionsetVerticalPosition(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 elfunctionhunted(event) {
// Primero verificamos que tengamos balasif (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 noif (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 patosfunctionrandomId() {
// Con math.random generamos un id aleatorio para cada patoconst id = Math.random().toString(36).substr(2, 9);
return id;
}
functioncreateDuck() {
// Ahora juntamos todo esto para crear nuestro patoconst 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 startgamefunctionstartGame() {
createDuck();
}
Agregamos la etiqueta de script con todo el código y tendremos el siguiente resultado 👇🏻
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 cazadoslet ducksMoving = [];
// La velocidad será importante ya que después de cada nivel será más veloces los patoslet 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 regresefunctiontouch(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 izquierdalet 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 👀
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 sencillafunctionnextLevel() {
// 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 divconst modal = document.createElement("div");
// y un contenedor para que se vea bien con los estilosconst container = document.createElement("div");
const paragraph = document.createElement("p");
// también un botón para cerrar el modalconst 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 bodydocument.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.
¿Quieres seguir con este videojuego?
Hay muchas oportunidades de mejora, te dejo aquí todo lo que puedes mejorar:
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í 👇🏻
Genial Leo!! 💥🦆
Super genial, Gracias
Está brutal el ejercicio para sumar algo en el protafolio
De verdad que todos estos blogs son una absoluta locura, muchas thanks
Que nivel! muy brutal!