Construyamos el juego Dig Dug usando MelonJS

Recientemente tuve la oportunidad de ver Stranger Things Season 2. Después de verlo, estaba muy emocionado de ver uno de mis juegos favoritos durante mi infancia, "Dig Dug", que aparece en la serie. Al mismo tiempo, estaba buscando un juego que pudiera construir para practicar mis habilidades de desarrollo de juegos. Así que hoy he decidido que Dig Dug es el juego.

Esta versión de Dig Dug no es una versión completa. Es solo el mecanismo básico del juego, que se puede expandir a una versión completa de Dig Dug más adelante.

MelonJS es el marco elegido, no por ninguna razón en particular. Lo elegí al azar de los muchos marcos que existen.

A continuación se muestran los pasos que seguiré para el desarrollo de este juego:

  1. Configurar el marco
  2. Creando el suelo
  3. Creando la excavadora
  4. Creando el Monstruo
  5. Creando la lógica de colisión
  6. Agregar pantalla de unidad principal
  7. Agregar efectos de sonido y música de fondo
  8. Agregar la pantalla de introducción
  9. Ajuste final
  10. Que sigue

Paso 1: configuración del marco

MelonJS recomienda utilizar el texto estándar proporcionado por ellos para iniciar el desarrollo de un juego. Primero, necesito descargar el texto estándar de GitHub.

Voy a clonar el texto estándar en mi directorio local de elección:

#terminalgit clone //github.com/melonjs/boilerplate.git mylocalfolder

Luego, tendré que configurar mi propio repositorio remoto para el juego usando esta guía. También es recomendable seguir su tutorial para familiarizarse con el uso del marco.

A continuación, tendré que descargar los recursos del juego proporcionados por MelonJS en sus páginas de tutoriales. Esto puede resultar útil si necesita algunas imágenes o mosaicos y no tiene tiempo para diseñarlos y crearlos. Estos activos se pueden descargar en cualquiera de las páginas de tutoriales de MelonJS aquí y aquí.

Ahora me gustaría discutir un poco sobre algunos archivos esqueléticos importantes proporcionados en el modelo estándar. MelonJS está utilizando la biblioteca de herencia Jay Extend. Así que también necesito familiarizarme con los archivos.

js/game.js:

Aquí está el espacio de nombres global de mi juego que se define como game(que puede ser cualquier cosa que me guste).

De las líneas 1 a 8, puedo definir cualquier información que requiera como objeto.

Luego, de las líneas 12 a 25 es donde puedo configurar la resolución del juego, cómo se comporta la pantalla y cargar todos los recursos del juego, como imágenes y sonidos.

Necesito cambiar algunos detalles, como la resolución de pantalla y el método de escala para el rendimiento del juego en la línea 14.

Por último, las líneas 28 a 37 es cuando el juego ejecutará todo.

js/screen/play.js:

Este archivo será cargado por lo game.jsque maneja la pantalla de reproducción.

De las líneas 5 a 13, toda la ejecución ocurre cuando comienza el juego. Aquí es donde especificaré renderizar todas las entidades del juego más adelante.

Pero, las líneas 18 a 21 es donde se eliminarán todas las entidades. Voy a editar mucho estos dos archivos a lo largo del camino.

Entonces, antes de crear cualquier otro objeto, necesito instalar todas las bibliotecas npm necesarias ejecutando el siguiente comando:

#terminalnpm install

y necesito instalar lo grunt-clique se requiere:

npm install -g grunt-cli

Finalmente, para ejecutar el juego, puedo ejecutar el siguiente comando y acceder al servidor local para ver cómo se ejecuta el juego:

grunt serve

Por ahora, solo puedo ver una pantalla negra en blanco cuando el juego se está ejecutando.

Paso 2 - Creando el terreno

Después de conocer un poco sobre el texto estándar proporcionado, ahora es el momento de crear mi primera entidad, el suelo. Hay varios tipos de objetos que podría construir a partir de este marco.

Entonces, debido a que este suelo chocará con la excavadora y el monstruo en el juego, necesito crear un Entityobjeto para el suelo. El suelo es el objeto donde se desarrolla el juego y que el excavador necesita excavar para que el excavador pueda pasar.

Originalmente, el suelo es un pequeño dibujo cuadrado de 15 x 15 píxeles que luego se puede renderizar repetidamente en la pantalla para hacer un área más grande. Podría usar un software llamado Tile Map Editor para este propósito, pero para este juego lo haré manualmente.

Así que así es como lo hago. Primero cree un archivo en la jscarpeta llamada ground.js. Luego crearé una nueva entidad de objeto como se muestra a continuación:

En la línea 2, crearé un nuevo objeto llamado game.Groundque se extiende desde el objeto Entity proporcionado por MelonJS.

En la siguiente línea, inicializaré el objeto a través del objeto principal con todos los argumentos requeridos. Este objeto necesitará xy se valorará ycomo la ubicación del objeto.

La anchura y la altura se definen en las líneas 37 y 38.

Para renderizar algo, puedo usar un objeto de imagen para este propósito. Pero en este caso, haré uso de la drawfunción del HTML5 Canvas. Esto se hizo en las líneas 9 a 28. Aquí programaré para dibujar un rectángulo con lunares en él. El color del cuadrado y los puntos estará definido por las variables declaradas en las líneas 5 y 6, respectivamente.

En las líneas 30 a 35 es donde está la updatefunción del objeto . Aquí necesito configurar la entidad para que se actualice en la línea 32 cada vez updateque se llame a la función. Y finalmente devuelve un valor verdadero para asegurarte de que la entidad se vuelva a dibujar cada vez que se actualice el juego.

En el siguiente paso, tendré que hacer una referencia a este archivo en el index.htmlarchivo de la línea 40 y:

registrar la entidad al pool en la game.jslínea 33. No necesitaré el código previamente en el game.jsque se registró game.PlayerEntityporque crearé la entidad jugador manualmente más tarde.

Dado que es necesario dibujar el suelo varias veces, es bueno para mí crear un contenedor para todo el suelo que se encargará de todo el trabajo. Para crear un contenedor, necesitaré crear un nuevo objeto y extender el objeto contenedor provisto por MelonJS.

Daré nombre a este contenedor game.LevelManager.

Como antes, necesitaré inicializar los argumentos. Defina el nombre de este objeto y defina los datos que se utilizarán para colocar todos los cuadrados en la pantalla en las líneas 2 a 21.

Luego, crearé una función personalizada que ejecute el trabajo de renderizado en función de los datos de las líneas 24 a 37. En la línea 30 se muestra cómo agrego el cuadrado a este contenedor, y después de renderizar todos los cuadrados, necesito actualizar el área del límite del contenedor en línea 36.

Finalmente, necesitaré renderizar el contenedor en la pantalla de reproducción, por lo que también se renderizará todo lo que esté debajo de este contenedor.

Antes de hacer eso, necesitaré crear una instancia del levelManagerobjeto en las líneas 9 a 11 a continuación:

También debo recordar hacer siempre una referencia al nuevo objeto creado en el index.htmlarchivo.

Ahora, si ejecuto el servidor, debería obtener una vista como esta:

Paso 3: creación de la excavadora

Primero necesitaré un objeto de imagen para mi excavadora. Al principio iba a usar los recursos del juego proporcionados por MelonJS, pero afortunadamente mi hijo creó una imagen de píxeles para que yo la usara con la excavadora:

Luego necesito colocar esta imagen en la data/imgcarpeta del directorio repetitivo. Cuando ejecuto el servidor ahora, Grunt construirá y agregará automáticamente el archivo de recursos en la build/jscarpeta con los datos de la imagen de arriba.

Para crear el Diggerobjeto, tendré que extenderlo nuevamente Entity.

Crearé un nuevo archivo llamado digger.js en la js carpeta y haré la referencia enindex.html.

En la línea 3, cargo la imagen del archivo de recursos que hice anteriormente y la asigno a una variable. En la línea 5, inicializo el objeto con los argumentos y configuraciones requeridos. Para el excavador, asignaré la imagen con la imagen definida anteriormente.

A continuación, en la línea 12, doy la vuelta al objeto cuando se renderiza por primera vez.

También tendré que hacer el gravity to 0en la línea 13 porque este no es un juego de plataformas que requiera gravedad para que el personaje actúe correctamente. En este caso, la excavadora estará flotando.

En la línea 14, la velocidad de la excavadora se inicializa para que pueda moverse más tarde. Defino el tipo de colisión para esta entidad para el uso de la lógica de colisión más adelante.

De las líneas 17 a 22, defino y administro la animación del sprite. Los números en la matriz de la addAnimationfunción son para determinar qué fotograma particular de la imagen se utilizará para la animación. El número al lado es para definir la velocidad. Finalmente, configuro la animación inicial que se usará cuando comience la pantalla de reproducción.

Ahora tendré que definir el movimiento de la excavadora. Noto en el juego original de Dig Dug que cada vez que el excavador gira hacia arriba o hacia abajo, siempre rotará su personaje apropiadamente con la base del suelo. Necesito tomar nota de esto para implementarlo correctamente en mi excavadora. Esta será una sección de código bastante larga.

Descubro que para que el excavador actúe correctamente cada vez que giro el objeto, tendré que ajustar el límite de la entidad y también la forma de la caja de colisión.

Inicialmente, el sprite excavador tiene un tamaño de 48 x 24. Esto se debe a la imagen de la excavadora disparando su arma. Sin embargo, durante el movimiento normal, solo necesitaré que la excavadora tenga un tamaño de 24 x 24.

Esto se maneja cambiando la forma de colisión inicialmente a 24 x 24 y haciendo que se transforme a 48 x 24 cuando el excavador dispara su arma, en las líneas 17 a 19 a continuación:

De las líneas 28 a 33, defino varios indicadores booleanos que usaré en la función de movimiento.

Comenzando en la línea 36 está la updatefunción de excavadora que también contiene la lógica de movimiento de la excavadora basada en la entrada del teclado de las líneas 40 a 134.

En esta lógica, necesito considerar muchas cosas como lo que sucede cuando se presiona o suelta la tecla de movimiento, la última posición de la excavadora antes de presionar y soltar una dirección o un botón de disparo, y los diferentes estados de animación requeridos. No es una función compleja, pero la lógica es un poco larga (aunque básicamente sencilla).

De las líneas 143 a 283 está la movementfunción para arriba, abajo, izquierda y derecha.

Para renderizar la excavadora en la pantalla y hacer posible el movimiento, necesito agregar el objeto excavador al game.worldcontenedor en la línea 12 y registrar la tecla del teclado de las líneas 19 a 23 para el movimiento a play.jscontinuación. También tendré que desvincularme cuando el juego salga de la pantalla de juego para otros usos si es necesario en las líneas 30 a 34.

Si ejecuto el servidor ahora, puedo ver al excavador en acción y moverlo hacia arriba, abajo, izquierda y derecha.

Sin embargo, puedo ver una imagen final cuando la excavadora se está moviendo, así como la línea de la imagen de inicio de MelonJS.

La imagen final se debe al dibujo que se ejecuta cada vez que se actualiza el bucle del juego. Esto se puede resolver agregando una capa de dibujo antes de cada vez que se vuelva a dibujar la excavadora en la línea 12 a continuación:

Paso 4: creación del monstruo

Hecho con la excavadora por ahora, crearé los monstruos a continuación. Será básicamente el mismo proceso para los monstruos. Necesitaré crear un objeto Entitypara los monstruos, agregar los monstruos al levelManagercontenedor y finalmente renderizarlo en la pantalla.

A continuación se muestra el objeto Entitydel monstruo:

Primero inicializaré el objeto en las líneas 5 a 9. Esta vez, solo usaré un sprite proporcionado por MelonJS de su tutorial del juego de plataformas que modifiqué para agregar más cuadros a continuación.

Este objeto debe estar en la misma carpeta que el objeto de excavación:

Luego nombro el objeto en la línea 11, defino el tipo de colisión en la línea 12, restablezco el cuadrado de colisión y lo hago más pequeño en las líneas 14 a 15, y establezco la velocidad y gravedad del monstruo en las líneas 17 a 18. También defino la animación grupo que se utilizará antes de configurar la animación inicial que se utilizará en las líneas 20 a 22.

A continuación, defino una función para el movimiento del monstruo. Es un algoritmo de movimiento muy básico que manipula el valor de velocidad del objeto X para el movimiento horizontal y Ypara el movimiento vertical en las líneas 26 a 43. Finalmente, creo la updatefunción del objeto que contendrá solo la actualización del cuerpo por ahora en las líneas 45 a 52.

Antes de continuar, nuevamente necesito recordarme siempre que debo hacer una referencia index.html y un registro game.jspara cualquier nuevo objeto de entidad creado.

Después de crear el objeto, necesitaré actualizar el LevelManagercontenedor para incluir los datos del monstruo y también la creationfunción.

A continuación se muestra el código actualizado:

De las líneas 21 a 28 están los datos de la ubicación del monstruo. La función para la creación de monstruos o la adición a este contenedor se agrega en las líneas 48 a 56. Por último, para que aparezca en la pantalla, es necesario agregar algunas líneas play.js.

A continuación se muestra la adición en la línea 11 que llama a la función para crear todos los monstruos:

Ahora, si ejecuto el servidor, puedo ver aparecer dos pequeños monstruos lindos en la ubicación específica de la pantalla. Por el momento no se moverán.

Parte 5: Creación de la lógica de colisión

Comenzaré con la lógica de colisión de la excavadora con el suelo y el monstruo. Para que el marco verifique cualquier colisión en el objeto de la entidad, necesito incluir el método de verificación de colisión en la updatefunción. Después de eso, ahora puedo incluir la onCollisionfunción que proporciona la información sobre los objetos específicos que chocan entre sí.

A continuación se muestran los códigos de objeto de excavadora actualizados:

En la línea 138, se realiza la verificación de código para detectar cualquier colisión para este objeto.

En las líneas 144 a 166, una función proporciona una respuesta cuando los objetos chocan. Cuando la excavadora colisiona con el suelo en las líneas 147 a 150, la entidad terrestre específica se eliminará del levelManagercontenedor.

Sin embargo, no quiero que el suelo desaparezca cuando la excavadora está disparando su arma, así que puse una excepción en la línea 148.

Lo siguiente es la lógica de la colisión con los monstruos. Si el excavador choca con un monstruo mientras dispara su arma, el monstruo parpadeará y será eliminado. Aparte de eso, la excavadora parpadeará, se eliminará y el juego se reiniciará de las líneas 151 a 163. Si se vuelve verdadero en la collisionfunción, otros objetos que chocan con la excavadora se solidificarán. En otras palabras, la excavadora no atravesará otro objeto durante la colisión. Para este caso, quiero que devuelva falso.

Para terminar esto, luego crearé la lógica de los límites. Actualmente, la excavadora puede viajar fuera de la pantalla. Para hacer esto, definiré la distancia máxima que la excavadora puede viajar en el eje xy yen la función de inicio del objeto en las líneas 16 a 17 a continuación.

Luego, en la updatefunción, estableceré los límites usando el clampmétodo incorporado en las líneas 105–106.

Ahora para los monstruos. Después de hacer la verificación de colisión en el monstruo, necesito definir la lógica de colisión con el suelo y también el límite. Esta lógica también debe incluir algún tipo de Inteligencia Artificial (IA) para que el monstruo persiga al excavador.

El monstruo no podrá cavar el suelo, por lo que rebotará en la dirección en la que está el excavador cuando golpee el suelo o el límite. Para que la colisión de límites funcione, necesito definir el movimiento de distancia máxima para el monstruo y configurarlo. No tengo que definir la lógica de colisión con la excavadora porque el Diggerobjeto ya la está manejando . También hice que el monstruo se moviera hacia la derecha cuando comienza el juego.

A continuación se muestra el último Monsterobjeto:

De las líneas 138 a 159 hay una función que definí que se ejecutará cuando el monstruo choque con el suelo en la onCollisionfunción. La lógica es hacer que el monstruo se mueva hacia la excavadora cuando golpea el suelo.

De las líneas 76 a 136, definí una función que representa la lógica de colisión del monstruo con el límite, que se llama en la updatefunción de la línea 57.

Siempre que el monstruo no golpee el límite, siempre comprobará la ubicación del excavador y se moverá hacia él. Luego, si el monstruo golpea el límite, se volverá hacia el excavador y continuará persiguiéndolo. No se me ocurrió el algoritmo de IA, es una combinación de los scripts que encontré en Internet con los míos. También en esta función, debo acceder a las propiedades del objeto de la excavadora, como la posición Xy Y. Para obtener este acceso, necesito hacer algunos cambios sobre cómo se representa la excavadora en la pantalla de reproducción.

En la línea 13 declaro una gamepropiedad que representa a la excavadora registrada de la piscina antes de agregarla a la pantalla de reproducción. Esta propiedad se utilizará para acceder a las propiedades de la excavadora dentro del objeto monstruo.

Finalmente, incluiré el código que reiniciará el juego si todos los monstruos chocan con la excavadora en modo disparo. En otras palabras, si el excavador logra matar a todos los monstruos. Esta verificación se realizará en el levelManagercontenedor.

Verificaré la matriz que contiene todos los monstruos de las líneas 60 a 62. Si la matriz está vacía, reiniciaré el juego.

Pero antes de eso, también debo crear una bandera booleana en la línea 56 que confirmará que el monstruo ya está creado cuando comienzo el juego. De lo contrario, el juego seguirá reiniciándose antes de que se puedan crear los monstruos.

Paso 6: agregar la pantalla de la unidad principal

Primero necesito crear una carpeta de fuentes en el directorio de datos:

data/fnt

luego use la fuente proporcionada por MelonJS en los recursos del juego descargados anteriormente:

PressStart2P.pngPressStart2P.fnt

y colóquelos en la nueva carpeta.

Luego, necesito agregar un script en el gruntfileorden para que genere los datos de recursos para la fuente a continuación en las líneas 22 a 28:

Cuando ejecuto el servidor, la fuente estará en los datos del recurso:

De forma predeterminada, el objeto Head Unit Display (HUD) ya está creado en el modelo estándar. Puedo acceder al archivo en formato js/entities/HUD.js. Solo necesito definir la fuente agregada anteriormente y crear una drawfunción para ella.

A continuación se muestra el código actualizado:

Defino e inicializo la fuente en las líneas 42 a 48, luego creo la drawfunción que representará la puntuación del juego en la ubicación específica como se define en la línea 71.

Por último, agregaré una pantalla de puntuación alta y su lógica. La lógica es simplemente guardar y sumar la puntuación actual a la highScorepropiedad cada vez que se reinicia el juego. O el excavador mata a todos los monstruos o el excavador muere.

Primero creé la highScorepropiedad en la línea 9:

Luego, en la onCollisionfunción de diggerI, aumentaré el punto cada vez que un monstruo muera en la línea 14 y sumaré los puntos actuales al puntaje más alto si el excavador muere en la línea 26.

También haré un pequeño ajuste a lo que sucederá cuando el monstruo golpee el fuego. Haré que el monstruo deje de moverse, justo después de que golpee el fuego, para evitar colisiones innecesarias en la línea 11.

Paso 7: agregar efectos de sonido y música de fondo

Configurar esto es muy sencillo. Todo el código requerido ya está ahí en el texto estándar. Lo que tendré que hacer es colocar el archivo de música o sonido requerido en la carpeta correspondiente y hacer algo con la música.

Basado en el juego original cuando la excavadora se mueve, se reproducirá la música de fondo. Es necesario implementar una lógica simple para que la música de fondo no intente comenzar repetidamente cuando se presione una tecla de dirección.

A continuación se muestra el objeto actualizado de la excavadora:

En la línea 37 creo una bandera booleana para usar en la lógica del movimiento y la música de fondo.

En las líneas 45 a 47 está la lógica para que la música de fondo no comience repetidamente si se presiona continuamente una tecla de dirección.

Respectivamente en las líneas 114, 200, 224, 249 y 288, la bandera se establece para que la lógica funcione correctamente.

Se hizo que la música de fondo se detuviera cuando la excavadora se detuvo en la línea 115.

En cuanto a los otros sonidos, también agrego un sonido para la excavadora en modo disparo y un sonido pop cuando el monstruo muere. En la línea 69, activo el sonido cuando se presiona la tecla de disparo, y lo detengo cuando se suelta la tecla de disparo en la línea 140. El sonido pop se activará cuando el monstruo choca con la excavadora durante el modo de disparo justo después de que se retire del pantalla en la línea 174.

Paso 8: agregar la pantalla de introducción

Primero, abriré game.jsy modificaré un fragmento de código. En lugar de cambiar el estado del juego a JUGAR, cambiaré el estado a MENÚ en la línea 40.

Esto cargará el title.jsarchivo cuando se cargue el juego:

A continuación, editaré el archivo title.jsen la js/screenscarpeta:

Aquí, en onResetEventreproduzco la música de fondo cuando aparece la pantalla en la línea 8.

A continuación, cubro la ventana gráfica con una capa de color marrón en la línea 10.

Luego, creo un Renderableobjeto que contiene el título y algunas redacciones de la línea 13 a la 43.

Este Renderable objeto se desplazará hacia arriba desde el exterior hasta el centro de la pantalla utilizando las Tweenlíneas 22 a 23.

Finalmente, necesitaré enlazar la tecla ENTER para activar un evento que iniciará el juego en las líneas 47 a 57.

Paso 9 - Ajuste final

No haré mucho por el ajuste final. Solo agregaré otro sonido de fondo al monstruo y colocaré la excavadora de manera similar al juego original. Primero agregaré otro Tweenpara que la excavadora se mueva al centro de la pantalla cuando comience el juego.

Incluiré el nuevo archivo de sonido en la carpeta correcta y luego actualizaré el diggerarchivo.

Declararé algunas banderas booleanas más para usar en las líneas 38 a 40, reproduciré la música de fondo al comienzo del juego en la línea 30 y ejecutaré el movimiento de excavación inicial llamando a la función definida en la línea 29.

A continuación se muestra la nueva función:

Desde la línea 4, definiré la Tweenanimación con una función de devolución de llamada que detiene la música de fondo, establece algunas banderas para la lógica y mueve el registro de la tecla de enlace aquí desde el play.jspara evitar cualquier movimiento adicional al presionar cualquier tecla durante la interpolación.

Finalmente, a continuación se muestra la función para crear el sonido del monstruo cada 5 segundos. Esta función se llamará en la updatefunción del diggerobjeto.

Paso 10 - ¿Qué sigue?

A continuación se muestran los elementos que podría seguir desarrollando para este juego:

  1. Crea la versión fantasma del monstruo que atraviesa el suelo.
  2. Crea un segundo y siguiente nivel para el juego.
  3. Cree elementos adicionales donde el excavador podría ganar más puntos.
  4. Crea una base de datos local con cookies donde el juego recordará la puntuación más alta del jugador.
  5. Refactorizar, refactorizar y refactorizar.
  6. Mejora el rendimiento del juego.

Gracias por leer hasta este final. Si tiene más sugerencias en la lista anterior, no dude en comentar esta publicación a continuación.

El código completo se puede extraer de GitHub.

No dudes en probar la demostración del juego.

Notas : Probablemente hay muchas formas de implementar esta función, pero esta fue la más fácil para mí. Cualquiera es libre de comentar cualquier error o mejora que pueda aplicar. Esta guía es inicialmente para que aprenda y recuerde lo que he hecho. No obstante, cualquier persona puede seguir esta guía si le resulta útil.