7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. HTML5

Tutorial del juego HTML5 Avoider: Múltiples enemigos en movimiento

Scroll to top
Read Time: 33 mins

Spanish (Español) translation by Esther (you can also view the original English article)

En la primera parte de esta serie, aprendiste los fundamentos del uso de JavaScript y el elemento canvas para hacer un juego de evasión muy simple en HTML5. Pero es demasiado simple, el único enemigo ni siquiera se mueve, ¡no hay desafío! En este tutorial, aprenderás a crear un flujo interminable de enemigos, todos cayendo desde la parte superior de la pantalla.


Actualización

En la primera parte del tutorial cubrimos bastantes conceptos: dibujar imágenes en la pantalla, interactuar entre HTML y JavaScript, detectar las acciones del ratón y la sentencia if. Puedes descargar los archivos fuente aquí si quieres sumergirte en esta parte del tutorial, aunque recomiendo leer todas las partes en orden.

La página HTML de nuestro juego contiene un elemento canvas, que desencadena una función JavaScript llamada drawAvatar() cuando se hace clic en él. Esa función está dentro de un archivo separado llamado main.js, y hace dos cosas:

  • Dibuja una copia de avatar.png en el lienzo.
  • Establece un receptor de eventos para llamar a otra función, llamada redrawAvatar(), siempre que el ratón se mueva sobre el lienzo.

La función redrawAvatar() también está dentro de main.js; a diferencia de drawAvatar() acepta un parámetro, llamado mouseEvent, que le pasa automáticamente el receptor de eventos. Este mouseEvent contiene información sobre la posición del ratón. La función hace cuatro cosas:

  • Despeja el lienzo.
  • Dibuja una copia de avatar.png en el lienzo, en la posición del ratón.
  • Dibuja una copia de enemy.png en el lienzo, en la posición especificada.
  • Comprueba si el avatar y el enemigo están lo suficientemente cerca el uno del otro como para superponerse, y muestra un alert() si es así.

¿Todo despejado? Si no es así, intenta el reto del calentamiento.


Desafío de calentamiento

Si hace tiempo que leíste la primera parte de la serie (o si simplemente quieres comprobar que entiendes lo que está pasando), haz estos pequeños ejercicios. Son completamente opcionales y están separados del tutorial real, por lo que recomiendo trabajar en una copia de tu proyecto en lugar del original. Puedes completar todos estos ejercicios utilizando solo la información de la primera parte de la serie.

Fácil

Recuerda que drawImage() funciona como un sello de patata. Úsalo para crear un anillo ininterrumpido de enemigos alrededor del borde de tu lienzo, así:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorial

(Si te aburres de copiar y pegar todos esos enunciados, no dudes en hacer un anillo más pequeño; si quieres, también puedes cambiar el tamaño del lienzo).

Medio

Haz que la alerta de "has golpeado al enemigo" aparezca cada vez que el avatar toque el borde del anillo. (Para probarlo, recuerda que puedes pulsar Enter para descartar la alerta; no tienes que pulsar OK).

Difícil

Esa alerta aparecerá cuando intentes mover el ratón de fuera del lienzo a dentro de él, lo que es realmente molesto si ya has hecho clic en el lienzo. Haz que sea posible moverse dentro del lienzo sin que se active la alerta, pero una vez dentro, haz que la alerta aparezca cada vez que el avatar toque el anillo de enemigos.


Haz que el enemigo se mueva

Vamos a hacer que el enemigo caiga desde la parte superior de la pantalla. Por ahora, nos centraremos en hacer que se mueva en lugar de detectar una colisión, así que "comenta" las líneas en redrawEnemy() que se ocupan de las colisiones, así:

Recuerda: dos barras inclinadas le dicen al navegador "ignora todo lo que hay en esta línea a partir de ahora". Antes hemos utilizado esto para crear comentarios, pequeños recordatorios de lo que hacen ciertos fragmentos de código, pero aquí lo utilizamos para otro propósito: detener la ejecución de ciertos fragmentos de código sin eliminarlos por completo. Esto nos facilita volver a poner el código más tarde.

De momento, el enemigo se redibuja, en la misma posición, cada vez que movemos el ratón:

¿Recuerdas la función Math.random() de la primera parte del tutorial? Devuelve un número aleatorio entre cero y uno; multiplicándolo por 300 (la altura del lienzo) nos daría un número entre 0 y 300. ¿Qué pasa si dibujamos al enemigo en una posición y aleatoria entre 0 y 300 cada vez que se mueva el ratón? Probémoslo; modifiquemos esa línea así:

¡Pruébalo aquí!

Parece que el enemigo se mueve aleatoriamente hacia arriba y hacia abajo en una línea determinada, pero solo cuando se mueve el ratón. Por supuesto, no se está moviendo realmente; se está "teletransportando" de una posición a otra, pero esto da la ilusión de movimiento.

Conseguiríamos una mejor ilusión si solo se moviera en una dirección. Podemos conseguirlo haciendo que la posición y del enemigo solo aumente, y nunca disminuya.

Considera el siguiente código:

¿Ves lo que estoy haciendo? Establecí el valor de enemyY a 0 en la parte superior de la función, y luego lo incrementé en un píxel antes de dibujar al enemigo. De esta manera, estoy tratando de hacer que la posición y del enemigo aumente en un píxel cada vez que se ejecuta redrawAvatar().

Sin embargo, hay un fallo en mi lógica. La línea var enemyY = 0; reiniciará la variable enemyY a 0 cada vez que se ejecute redrawAvatar(), lo que significa que siempre se dibujará en una posición y de 1 (porque se incrementará en la línea 12).

Tenemos que ponerlo a 0 solo una vez. ¿Y si lo hacemos en drawEnemy()? Después de todo, esa función solo se ejecuta una vez:

¡Pruébalo aquí!

Por desgracia, esto no funciona en lo absoluto. El problema radica en un concepto llamado ámbito. Si usas la palabra clave var para definir una variable dentro de una función, entonces la variable solo será accesible dentro de esa función. Esto significa que nuestra función redrawAvatar() no puede acceder a la misma variable enemyY que fue definida en drawAvatar().

Sin embargo, si definimos una variable fuera de todas las funciones, ¡se puede acceder a ella desde cualquiera de ellas! Así que, prueba esto:

¡Pruébalo aquí!

Funciona: el enemigo se desliza por la pantalla. Sin embargo, solo lo hace mientras movemos el ratón. Es una mecánica de juego interesante, pero no es lo que pretendía.


Haz que el enemigo se mueva por sí mismo

Sería mucho mejor si el enemigo pareciera moverse por sí mismo, es decir, que se moviera independientemente de si el jugador mueve o no el ratón. Podemos hacerlo activando su movimiento (sus "teleportaciones") en función del tiempo y no del movimiento del ratón.

Podemos hacerlo utilizando la función setInterval(). Funciona así:

Aquí, functionName es el nombre de una función que queremos ejecutar una y otra vez, y period es la cantidad de tiempo (en milisegundos) que queremos que pase entre cada llamada a esa función.

Veamos cómo queda esto:

He movido todo el código que se ocupa de mover y dibujar al enemigo a la nueva función redrawEnemy(), y la he configurado para que sea llamada cada 1.000 milisegundos (cada segundo) usando una llamada a setInterval() en drawAvatar(). (A diferencia de lo que ocurre cuando se utiliza un receptor de eventos, no se pasan parámetros automáticamente a redrawEnemy() cuando lo llamamos desde setInterval()).

¡Pruébalo aquí! Haz clic en el lienzo y no muevas el ratón.

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorial

Hay algunas cosas que no funcionan:

  • El enemigo deja un rastro - esto se debe a que el lienzo no se borra en redrawEnemy().
  • El enemigo se mueve muy lentamente, quizás 1000 milisegundos es demasiado tiempo para esperar.
  • Cuando el avatar se mueve, el enemigo desaparece, esto se debe a que el enemigo solo se dibuja en redrawEnemy(); en redrawAvatar() se limpia el lienzo y se redibuja el avatar, pero no el enemigo.

Arreglemos esto de uno en uno. Primero, borraremos el lienzo en redrawEnemy():

¡Pruébalo aquí!

Hm. Ahora el avatar desaparece cuando se dibuja el enemigo, y viceversa. Por supuesto, esto tiene sentido; limpiamos el lienzo tanto en redrawEnemy() como en redrawAvatar(), pero nunca dibujamos el enemigo y el avatar al mismo tiempo.

¿Qué pasa si movemos al enemigo en redrawEnemy(), aumentando el valor de enemyY, pero realmente lo dibujamos en redrawAvatar()?

¡Pruébalo aquí!

Más o menos funciona, pero volvemos a ese problema de que el enemigo solo se mueve mientras tú mueves el ratón. Sin embargo, esta vez es ligeramente diferente; para hacerlo más evidente, podemos aumentar la velocidad del enemigo reduciendo el period. Ajústalo a 25 (eso es 1/40 de segundo, lo que significa que redrawEnemy() se ejecutará 40 veces por segundo):

¡Pruébalo aquí!

Compáralo con la versión anterior en la que el enemigo solo se movía cuando se movía el ratón. ¿Ves la diferencia? En el nuevo, la posición del enemigo sigue cambiando, pero lo hace "entre bastidores"; la imagen real del enemigo solo se mueve cuando se mueve el ratón. Si esperas un segundo más o menos antes de mover el ratón, la imagen del enemigo salta hacia abajo en la pantalla para alcanzar su posición real.

Separar así la posición real del enemigo de la posición de la imagen del enemigo nos va a permitir resolver nuestro problema.

Antes de continuar, ¿te confunden los nombres de las funciones? Yo sí. redrawEnemy() no está dibujando al enemigo en absoluto. Vamos a renombrarlas a algo más fácil de seguir.

  • drawAvatar() se ejecuta cuando iniciamos el juego, y lo configura todo, así que cambiémosle el nombre a setUpGame()
  • redrawAvatar() se ejecuta cada vez que el ratón se mueve, así que cambiémosle el nombre a handleMouseMovement()
  • redrawEnemy() se ejecuta cada fracción de segundo; es como si hubiera un reloj que hace tictac 40 veces por segundo, y cada tictac activa la función. Así que cambiemos el nombre a handleTick()
  • No olvides que tienes que cambiar el nombre de todas las referencias a las funciones también, en el escuchador de eventos y el setInterval(). Esto es lo que parecerá:

(También tendrás que cambiar la página HTML, para que el atributo onclick del lienzo sea "setUpGame();" en lugar de "drawAvatar();".

Creo que esto hace que sea más fácil ver lo que está pasando:

  • Cuando el ratón se mueve, movemos la posición del avatar, dibujamos el avatar en su posición actual y dibujamos al enemigo en su posición actual.
  • Cuando el reloj marque la hora, movemos la posición del enemigo.
  • Tenemos que dibujar el enemigo y el avatar al mismo tiempo (es decir, en la misma función).
  • Si solo dibujamos al enemigo cuando el ratón se mueve, entonces el movimiento del enemigo no es suave.

Esto facilita, a su vez, ver una posible solución:

  • Cuando el ratón se mueve, mueve la posición del avatar.
  • Cuando el reloj marque la hora, mueve la posición del enemigo, dibuja el avatar en su posición actual y dibuja al enemigo en su posición actual.

Vamos a implementarlo. Todo lo que tenemos que hacer es mover el código de dibujo de handleMouseMovement() a handleTick(), ¿verdad? Así:

Hmm. Eso no está bien. No tenemos nada en handleMouseMovement(). Ah, pero eso es porque no hemos separado la posición de la imagen del avatar de la posición real del avatar, como hicimos con el enemigo. Así que hagamos eso:

Tenemos que crear nuevas variables para almacenar las posiciones x e y reales del avatar, y definir esas variables fuera de cualquier función, para que podamos acceder a ellas desde cualquier lugar.

¡Pruébalo aquí!

Esto funciona. (Si va un poco a tirones, prueba a cerrar algunas pestañas o a reiniciar Chrome; a mí me ha funcionado). Ahora tenemos un enemigo en movimiento. Ha costado un poco llegar hasta aquí, pero el código que hemos conseguido no es demasiado complejo, espero que estés de acuerdo.

Si quieres un reto, intenta reintroducir el cuadro de alerta de detección de colisiones. No te preocupes si tienes problemas; volveremos a hacerlo un poco más tarde.

Mientras tanto, veremos un problema con el que probablemente no te hayas topado todavía...


Cargar las imágenes desde un servidor

Como mencioné en la primera parte de esta serie, si pones tu juego en un servidor web tal y como está ahora, no funcionará correctamente, aunque funcionen bien cuando se ejecuten desde tu ordenador. Mis demos funcionan porque he hecho una ligera modificación en el código; así es como se ejecuta el juego sin ese código:

Pruébala aquí.

¿Qué ocurre? Bueno, tiene que ver con las imágenes. Cada vez que el reloj hace tictac, creamos una nueva imagen y establecemos su origen para que apunte a un archivo de imagen real. Esto no causa problemas cuando el archivo de imagen está en el disco duro, pero cuando está en Internet, la página puede intentar descargar la imagen antes de dibujarla, lo que provoca el parpadeo que podemos ver en la demostración.

Quizás ya puedas adivinar una solución, basándote en lo que hemos hecho en este tutorial hasta ahora. Al igual que con las posiciones del enemigo y del avatar, podemos mover las imágenes del enemigo y del avatar fuera de las funciones, y reutilizarlas una y otra vez, sin tener que definirlas y establecer sus valores en la función handleTick() cada vez.

Echa un vistazo:

Pruébalo aquí: ¡sin parpadeos!

En caso de que te lo preguntes: podríamos haber movido las líneas 9-12 fuera de las funciones también. Elegí ponerlas en setUpGame() simplemente porque parecían estar más relacionadas con, bueno, la configuración del juego.


Haz otro enemigo

En realidad es muy fácil hacer que aparezca otro enemigo en la pantalla. Recuerda que las imágenes son como sellos de patata; eso significa que no hay nada que nos impida dibujar la imagen del enemigo en el lienzo en dos lugares diferentes dentro del mismo tick:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

¡Simple!

Puedes ponerlos a diferentes alturas, así:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

Sin embargo, esto es un poco complicado. En su lugar, ¿qué tal si creamos una variable enemyY2?

Ahora puedes establecer las posiciones iniciales de enemyY y enemyY2 a lo que quieras, sin tener que cambiar el código en handleTick().


Haz cinco enemigos

Intenta ampliar lo que acabamos de hacer para que haya cinco enemigos, todos con diferentes puntos de partida. Echa un vistazo a mi código si lo necesitas. Una pista: solo tienes que añadir código fuera de las funciones y dentro de la función handleTick(), no es necesario tocar setUpGame() o handleMouseMovement().

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

Haz diez enemigos

Oh, esto va a ser tedioso, ¿verdad? Mantener todos esos enemigos, y añadir tres líneas de código para cada uno. Qué asco.

Permítanme presentarles los arrays. Mira esto:

Esto nos da exactamente el mismo resultado que antes, pero:

  • Todas las posiciones y de los enemigos se definen en una sola línea, y
  • Obtenemos una forma fácil de referirse a cualquiera de estas posiciones: enemyYPosition[enemyNumber].

Este tipo de variable se llama array; es una forma de contener una lista de valores (o incluso de otras variables), y nos permite recuperar cualquier elemento de esa lista utilizando un número. Ten en cuenta que el primer elemento de un array es el número 0, el segundo valor es el número 1, y así sucesivamente, por lo que llamamos a los arrays "basados en el cero".

Bucle

Ahora echa un vistazo a esta sección de código:

Estamos haciendo lo mismo, una y otra vez, a diferentes elementos de la array. Cada línea de código es la misma que todas las demás, excepto que el número dentro de los corchetes cambia. Esto es genial, porque podemos escribir código para decir "haz esto mismo cinco veces, pero cambiando un número cada vez". Por ejemplo:

Si pones esto en tu archivo JS (en setUpGame(), por ejemplo), haría que la página mostrara cinco cajas de alerta: la primera diría "0"; la segunda diría "1"; y así hasta "4". En otras palabras, es equivalente a hacer esto:

Esto se debe a que la sentencia while actúa como una sentencia if repetida. Recuerda que el if funciona así:

"Si la condición es verdadera, entonces ejecuta el resultado".

while funciona así:

"Mientras la condición siga siendo cierta, sigue corriendo el resultado".

Es una diferencia sutil, pero realmente importante. Un bloque if se ejecutará solo una vez, si la condición es verdadera; un bloque while se ejecutará una y otra vez hasta que la condición deje de ser verdadera.

Por esta razón, el resultado, el código que se ejecuta dentro del bloque while, suele contener algún código que, eventualmente, hará que la condición deje de ser verdadera; si no lo hiciera, el código se repetiría para siempre. En nuestro ejemplo de la caja de alerta, incrementamos el valor de currentNumber hasta que "currentNumber " dejó de ser verdadero (cuando currentNumber llegó a 5, ya no era menor que 5, por lo que nunca vemos una caja de alerta que contenga el número 5).

Ejecutar el código una y otra vez de esta manera se llama "bucle", y el bloque while se llama "bucle". Tomemos ahora este código:

...y ponerlo en un bucle:

¡Genial! ¿O no?

En realidad, eso no es del todo correcto: no estamos cambiando el valor de currentEnemyNumber. Esto significa que simplemente aumentaremos el valor de enemyYPositions[0] una y otra vez, para siempre, sin cambiar nunca las posiciones y de los otros enemigos ni salir del bucle.

Así que tenemos que hacer esto:

Ahora es genial.

¿Podemos aplicar el mismo pensamiento a nuestro otro código repetitivo? Me refiero a esto:

Por desgracia, algo así no funcionará:

...porque no todos los enemigos tienen una posición x de 250. Pero podemos hacer que funcione, si movemos todos los enemigos x-posiciones a otro array:

Esto tiene la ventaja añadida de mantener todas las posiciones x e y de los enemigos en una sola ubicación, en lugar de repartirlas en varias líneas.

Veamos el código en su contexto:

Observa que, en la línea 22 de arriba, he puesto currentEnemyNumber a 0; si no lo hiciera, el bucle para dibujar las imágenes de los enemigos no se ejecutaría ni siquiera una vez, ya que la condición ya sería falsa desde el bucle anterior que mueve a los enemigos. También hay que tener en cuenta que, cuando hago esto, el bucle que dibuja a los enemigos no detecta inmediatamente que la condición es ahora verdadera de nuevo y empieza a mover a todos los enemigos.

Todo funciona igual que antes.

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

El mayor beneficio de esto está en lo fácil que es añadir otros cinco enemigos. Solo tenemos que cambiar cuatro líneas de código:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

¡Simple!


Hacer quince enemigos

Aquí tienes otro ejercicio: modifica el código para que cree quince enemigos. De nuevo, solo tienes que modificar cuatro líneas de código. Echa un vistazo a mi código si no estás seguro:

Obviamente podríamos seguir así. Pero ya me resulta irritante cambiar las líneas 14 y 23 de arriba, y me he olvidado de hacerlo un par de veces.

Afortunadamente podemos automatizar esto, en cierto modo. El número, 5, 10, 15 o lo que sea, es igual al número de elementos de los array enemyXPositions[] o enemyYPositions[]. Llamamos a esto la longitud del array, y podemos recuperarla de cualquier array usando la propiedad .length, así:

O, para ser un poco más ordenado:

Ahora puedes hacer tantos enemigos como quieras, simplemente añadiendo nuevos números a los arrays enemyXPositions[] y enemyYPositions[].


Reintroducir la detección de colisiones

¿Recuerdas cómo funcionaba la detección de colisiones? Hemos tenido el código en handleMouseMovement() (aunque comentado) durante un tiempo:

Básicamente se trata de comprobar si dos rectángulos, uno que se mueve con el cursor y otro que se queda quieto en el lienzo, están superpuestos. Pero es difícil de entender a partir de ese código, así que vamos a echar un nuevo vistazo.

En primer lugar, veámoslo en términos de solapamiento horizontal:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorial

Aquí, el avatar y el enemigo no se superponen. Tenemos: avatarX

HTML5 avoider game tutorial

Aquí, el avatar y el enemigo se superponen. Tenemos: avatarX

HTML5 avoider game tutorial

Todavía se superponen. Tenemos: enemigoX

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorial

Ya no se superponen. Tenemos: enemyX

Tomando todo esto en conjunto, podemos ver que el enemigo y el avatar se superponen horizontalmente si:

avatarX y enemyX

...o:

enemyX y avatarX

En otras palabras, para el solapamiento horizontal, esta condición debe ser cierta:

(avatarX

(Recuerda que && significa "y"; || significa "o").

No es difícil encontrar una condición similar para el solapamiento vertical:

(avatarY

(Utilizamos 33 aquí porque el avatar tiene 33 píxeles de alto, pero sólo 30 de ancho).

Para que el enemigo y el avatar se superpongan, deben hacerlo tanto horizontal como verticalmente, por lo que utilizamos el operador && para combinar estas dos condiciones:

( (avatarX

Es una condición muy larga. Pero en realidad es bastante sencilla, ahora que la hemos desglosado. Pongámosla en nuestro código. En primer lugar, elimina el antiguo código de detección de colisiones de handleMouseMovement(); lo pondremos en handleTick(), después de haber movido y redibujado al enemigo y al avatar, para que parezca más justo.

Para empezar, solo comprobaremos la colisión entre el avatar y el primer enemigo:

Parece desordenado (así que probablemente deberías añadir un comentario para recordar cómo funciona más tarde), pero tiene todo lo que necesitamos. ¿Funciona?

¡Pruébalo aquí!

¡Funciona! Ahora debemos hacer que funcione para todos los enemigos, y por supuesto podemos usar un bucle para hacerlo:

Ten en cuenta que esto va dentro de handleTick(), al final, por lo que tenemos que restablecer currentEnemyNumber a 0. También tenemos que cambiar el texto de la caja de alerta, ya que podría no ser el primer enemigo que hace aparecer la alerta.

¡Pruébalo aquí!

Muy bien, ¡esto se está convirtiendo en un juego! De acuerdo, claro, el cuadro de alerta es un poco molesto, pero sirve a nuestros propósitos por ahora.

Hay otra gran adición que me gustaría que hiciéramos en esta parte...


Haz infinitos enemigos

Podemos añadir más y más posiciones de enemigos a nuestros arrays, cien, mil, lo que sea, pero al final el suministro se agotará y el jugador no tendrá más enemigos que esquivar. Necesitamos poder crear nuevos enemigos y añadir sus posiciones a los arrays mientras el juego está en marcha.

Cuando queremos cambiar el valor de un elemento específico dentro de enemyXPositions, es fácil: simplemente escribimos enemyXPositions[3] = 100, o lo que sea. ¿Pero cómo podemos añadir algo al array? Si escribimos enemyXPositions = [100] (o lo que sea) simplemente reemplazaremos el array por uno nuevo, que contendrá un solo elemento.

La respuesta está en la función .push() de los arrays, que nos permite añadir un elemento a un array sin crear uno nuevo. Para demostrarlo, vamos a borrar todos los elementos de nuestros arrays y luego usaremos .push() para añadir otros nuevos:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

Funciona bien; el enemigo sigue moviéndose y la detección de colisiones sigue funcionando. Es exactamente lo mismo que si hubiéramos empezado el código con:

Entonces, ¿qué ocurre si introducimos los nuevos valores en los arrays dentro de handleTick(), en lugar de dentro de setUpGame()?

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

Hm. Está creando nuevos enemigos en las mismas posiciones a un ritmo tan rápido que se están superponiendo. (El que está en la parte superior de la pantalla parece estar por encima de todos los demás porque es el último en ser dibujado).

Intentemos arreglar esto creando los enemigos en posiciones x iniciales aleatorias. Recuerda que Math.random() nos da un número aleatorio entre 0 y 1, así que para obtener un número aleatorio entre 0 y 400, el ancho del lienzo, podemos usar Math.random() * 400:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

¡Argh!

Muy bien, así que tal vez todavía están llegando a un ritmo demasiado rápido ...

Menos enemigos por segundo, por favor

Ahora mismo, los enemigos se crean a un ritmo de un nuevo enemigo por tic, y como un tic son 25ms, hay 40 tics por segundo, y por tanto 40 nuevos enemigos por segundo.

Reduzcamos esto a algo más manejable: unos dos enemigos por segundo.

Como eso es 1/20 de nuestra tasa actual, podríamos conseguirlo llevando la cuenta de cuántos ticks han pasado, y creando uno nuevo en el tick número 20, tick número 40, tick número 60, y así sucesivamente. Pero creo que será más divertido si en lugar de eso hacemos que haya una probabilidad de 1/20 de que se cree un nuevo enemigo en cualquier tic. De este modo, a veces crearíamos más de dos enemigos nuevos en un segundo, y otras veces crearíamos menos, pero la media sería de dos por segundo. La incertidumbre haría que el juego fuera un poco más emocionante (aunque tal vez "emocionante" sea una mala elección de palabras en esta fase inicial del desarrollo del juego...).

¿Cómo podemos hacer esto, entonces? Bien, sabemos que Math.random() crea un número aleatorio entre 0 y 1. Como estos números aleatorios están repartidos uniformemente entre el 0 y el 1, eso significa que hay una probabilidad de 1/20 de que el número generado esté entre el 0 y... bueno, 1/20.

En otras palabras, la probabilidad de que Math.random() sea verdadera es de 1/20.

Así que vamos a cambiar nuestro código para hacer uso de este hecho:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialHaz clic para probarlo.

¡Mucho mejor! Siéntete libre de experimentar con esa condición para encontrar un valor que funcione para ti.


Conclusión

Eso es todo para esta parte de la serie. Ahora hemos construido un juego rudimentario, no un juego pulido, no un juego particularmente divertido, pero un juego al fin y al cabo.

Si quieres desafiarte a ti mismo antes de la siguiente parte, prueba a hacer estos cambios:

  • Fácil: los enemigos "saltan" a la parte superior del lienzo; haz que se deslicen, en cambio.
  • Fácil: Algunos de los enemigos se crean parcialmente fuera del lienzo; evita que esto ocurra.
  • Medio: Los enemigos se mueven todos exactamente a la misma velocidad, lo cual es bastante aburrido; permite que tengan diferentes velocidades.
  • Difícil: El avatar puede salirse del borde derecho del lienzo, haciendo imposible que ningún enemigo lo toque (suponiendo que hayas completado el segundo desafío fácil). Asegúrate de que el avatar se mantiene dentro de los límites.

¡Que lo disfrutes!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.