Crea tu propio Duck Hunt usando solo tecnologías web

Curso de Diseño de Videojuegos

Take the first classes for free

SHARE THIS ARTICLE AND SHOW WHAT YOU LEARNED

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

En este repositorio podrás descargar las imágenes.

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 👇🏻.

https://static.platzi.com/media/user_upload/Screenshot%202022-07-07%20at%2010-55-09%20Duck%20hunter-92d58aa5-8b61-4c3e-93ea-0ebafc0809e1.jpg

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 👇🏻

https://i.imgur.com/FDgSOwf.gif

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 👀

https://i.imgur.com/cx5K2iI.gif

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. 🧐

https://i.imgur.com/8vPOncV.gif

💻 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:

  1. Mover al pato en diagonal
  2. Mejorar los estilos
  3. Mostrar las balas restantes
  4. Cada que atrapes un pato te despliegue los puntos
  5. Desplegar múltiples patos en pantalla
  6. Agregar una tabla de posiciones donde puedas ver tus puntuaciones
  7. Agregar una barra que te diga la dificultad actual
  8. 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

Take the first classes for free

SHARE THIS ARTICLE AND SHOW WHAT YOU LEARNED

0 Comments

to write your comment

Related articles