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: Llevando la cuenta

Scroll to top
Read Time: 22 mins

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

Hasta ahora, tenemos un flujo interminable de enemigos que nuestro avatar debe evitar; un toque, y se acabó el juego. Pero, ¿y qué? Como no hay forma de seguir el progreso del jugador, éste no tiene ni idea de si lo ha hecho mejor en su última ronda que antes. En este tutorial, aprenderás a llevar la puntuación, a mostrarla en el lienzo y a avisar al jugador cuando haya superado sus propios récords.


Actualización

En la primera y segunda parte de este tutorial, hemos cubierto una serie de conceptos: dibujar imágenes en el lienzo, detectar acciones del ratón, utilizar sentencias if y while, almacenar variables en arrays, y la idea de alcance de las variables.

Puedes descargar los archivos fuente de la serie hasta este punto si quieres sumergirte directamente en ella, aunque recomiendo leer todas las partes en orden.

El archivo JavaScript de nuestro juego inicializa un montón de variables (incluyendo dos matrices para almacenar las coordenadas x e y del enemigo) fuera de todas las funciones, de modo que su contenido está disponible para todas las funciones. También contiene una función llamada setUpGame() que se ejecuta cuando el jugador hace clic por primera vez en el lienzo; esto carga las imágenes, comienza a escuchar cualquier movimiento del ratón, y establece una función "tick" que se ejecuta cada 25 milisegundos.

Cuando se mueve el ratón, movemos la posición del avatar, tal y como se almacena en dos variables, una para la coordenada x y otra para la coordenada y, pero no redibujamos inmediatamente la imagen del avatar en esta nueva ubicación; todo el redibujado se maneja en la función tick.

La función tick hace cuatro cosas:

  • Hay una posibilidad entre veinte de que se añada un nuevo enemigo, introduciendo nuevas coordenadas x e y en las matrices correspondientes.
  • Incrementa las coordenadas y de cada enemigo haciendo un bucle a través de ese array. 
  • It redraws the avatar and enemy images according to their current coordinates.
  • Comprueba si hay una colisión entre el avatar y cada enemigo, dando una alerta si se produce una. 

¿Todo claro?


Desafío de calentamiento

Si hace tiempo que no lees la segunda 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 su proyecto en lugar del original. Puedes completar todos estos ejercicios utilizando solo la información de la serie hasta ahora.

Fácil

Cambia las imágenes del avatar y del enemigo para que el jugador controle una cara sonriente que está evitando las calaveras que caen.

(¿De cuántas maneras diferentes puedes imaginar cómo hacer esto? A mí se me ocurren tres, de la cabeza).

Medio

En este momento, hay una probabilidad fija de uno entre veinte de que se cree un nuevo enemigo en cualquier tic. Quiero que lo hagas de manera que haya una posibilidad entre uno en el primer tick, una entre dos en el segundo, una entre tres en el tercero, y así sucesivamente.

Para hacer esto más desafiante, hazlo al revés: una posibilidad entre 1.000 en el primer tick, una posibilidad entre 999 en el segundo, una posibilidad entre 98 en el tercero, y así sucesivamente. (Después del milésimo tick, hazlo con una probabilidad constante de uno en uno).

Difícil

En lugar de esperar a que los enemigos aparezcan uno a uno, haz que el juego comience con veinte enemigos ya en pantalla.

Para que esto sea más difícil, haz que se extiendan por el lienzo y no permitas que se superpongan al avatar o entre sí.


Mantener el tiempo

¿Cuál es la forma más sencilla de medir lo bien que lo está haciendo el jugador en esta ronda? Lo más sencillo que se me ocurre es llevar la cuenta de cuánto tiempo ha pasado desde que golpearon a un enemigo. Y como golpear a un enemigo significa el fin de la partida, solo tenemos que llevar la cuenta del tiempo que llevan jugando.

Para hacer esto, simplemente crearemos una variable, la pondremos a 0 cuando la ronda comience, y la incrementaremos en cada tic. Vamos a llamar a esta variable ticksSurvived. Y piensa: como necesitamos acceder a ella una y otra vez, es necesario definirla fuera de todas las funciones, en la parte superior del archivo JS:

Ahora, le daremos a handleTick() otra tarea que hacer: incrementar ticksSurvived. Pon esto después de la detección de colisiones; después de todo, si el avatar golpea a un enemigo, no ha sobrevivido al tic:

Para mostrar esto, por ahora, solo alteraremos la alerta que aparece cuando el avatar golpea a un enemigo:

Esto es un poco raro; hemos utilizado el operador + para añadir una cadena a un número. Podemos añadir otra cadena al final:

Quizás esto no te parezca extraño, pero algunos lenguajes de programación odian esto. A JavaScript no le importa. Sabe que "some" + "string" es igual a "somestring", y asume que quieres tratar ticksSurvived como una cadena en esta situación.

(Algunas cosas para probar:

12 + 6"12" + "6""12" + 612 + "6"

¿Hacen todos lo que esperas que hagan?)

De todos modos, vamos a probar este nuevo código.

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

¡Genial!

Al igual que antes, al hacer clic en Aceptar, vuelve a aparecer el cuadro de alerta, porque lo único que hace es pausar los ticks en lugar de detenerlos realmente. Pero fíjate en que el número de la caja aumenta en uno; esto es una prueba de que JavaScript no convierte el número en una cadena permanentemente; solo lo utiliza como cadena para añadirlo a otra cadena.

Ya que hablamos de este cuadro de alerta: ¿no te parece molesto?


Inténtalo de nuevo

Actualmente, la única manera de empezar una nueva ronda es refrescar la página. Hagamos que sea más fácil y menos irritante volver a intentarlo, haciendo que el botón "OK" del cuadro de alerta reinicie el juego.+

Dado que la alerta pone en pausa el juego, cualquier cosa que pongamos en la línea después de la alerta se ejecutará una vez que el jugador pulse OK. Hagamos que llame a una nueva función, startNewGame():

Entonces, ¿qué necesitamos que haga startNewGame()?

Tal vez deberíamos llamar de nuevo a setUpGame(), pero no, nada de eso necesita hacerse dos veces: no necesitamos cargar las imágenes o añadir el receptor de eventos del ratón o configurar los ticks de nuevo.

Piensa en lo que se necesitaría y prueba tus ideas. Mi solución está abajo; échale un vistazo cuando estés listo.

Eso es todo. Puedes hacer más si lo deseas, pero esto es lo mínimo requerido.

Observa que no tenemos que hacer nada con el lienzo, no necesitamos limpiarlo, ni dibujar nada en él, ni manipular las imágenes, porque todo esto se hace en handleTick(). Tampoco tenemos que recorrer todos los elementos de las matrices, uno por uno, porque las matrices son simplemente una lista de coordenadas que utilizamos para estampar las imágenes enemigas en el lienzo; los enemigos no existen como objetos reales.

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

Esto es genial: el jugador puede jugar una y otra vez para seguir intentando mejorar su puntuación. Pero... ¿cómo saben si han superado su puntuación anterior? Por el momento, solo tienen que recordar su máxima puntuación hasta el momento, o anotarla. Podemos hacerlo mejor.


Recuerda la mejor puntuación

¿Cómo debemos almacenar la puntuación máxima? ¡En otra variable, por supuesto!

Naturalmente, tiene que vivir fuera de las funciones; a estas alturas, seguro que entiendes por qué.

Para empezar, lo ponemos a 0, por supuesto, el jugador ni siquiera ha completado una ronda. Cuando el avatar golpea a un enemigo, vamos a actualizar la nueva puntuación como sea necesario:

Digamos también al jugador que ha superado su antigua puntuación máxima:

Echa un vistazo:

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

Excelente. Ahora vamos a dar al jugador un poco más de información sobre lo que hizo mejor:

Observa que ticksSurvived y mostTicksSurvived se tratan como números cuando se resta uno del otro, pero la expresión resultante (ticksSurvived - mostTicksSurvived) se trata como una cadena cuando se suma a las otras cadenas. Esto puede ser realmente confuso, si no se tienes cuidado.

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

Muy bien, así que ahora el jugador sabe lo bien que lo estaba haciendo, después de conseguir el game over, deberíamos darle una indicación de lo bien que lo está haciendo, mientras todavía está jugando.


Dibujar la partitura en el lienzo

Es muy fácil escribir un texto en un lienzo, y no, no hace falta juntarlo con imágenes de diferentes letras.

Recuerda que para dibujar una imagen en el lienzo llamamos a canvas.getContext("2d").drawImage(). Escribir texto es muy similar: llamamos a canvas.getContext("2d").fillText().

Debemos pasar tres argumentos a fillText():

  • Una cadena que contiene el texto a escribir.
  • La coordenada x en la que se debe escribir.
  • La coordenada y en la que se debe escribir.

Hay un cuarto argumento opcional:

  • El ancho máximo que puede ocupar el texto en la pantalla.

...pero no nos preocuparemos de eso por ahora.

Para probar esto, dirígete a handleTick(), encuentra la línea donde dibujamos el avatar al lienzo, y dibuja una línea de texto de muestra justo después:

Pruébalo:

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

Ten en cuenta que, como esto se dibuja después del avatar y antes de los enemigos, los enemigos se dibujan encima de él, mientras que el avatar va debajo:

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

Como digo, el primer argumento requerido es una cadena, y, como hemos visto, podemos construir una cadena añadiendo una cadena a un número. Por lo tanto, esto debería funcionar:

...¡y de hecho lo hace!

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

Pero es un poco desordenado, flotando ahí debajo de los enemigos con esa fuente diminuta. Vamos a arreglar eso.


Poner en orden el texto

Primero, movamos el texto para que los enemigos se muevan debajo de él. Esto significa dibujarlo después de que todos los enemigos estén dibujados, así que mueve la línea relevante hacia abajo:

Ahora, vamos a moverlo a la parte superior izquierda de la pantalla. Eso es, (0, 0) así que esto debería funcionar, ¿verdad?

Hmm. No. No podemos ver la puntuación en absoluto. Esto es porque está usando (0, 0) para colocar la esquina inferior izquierda del texto. Tenemos que mover el texto un poco hacia abajo, entonces:

Compruébalo:

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

Bien, hasta aquí todo bien. Ahora, vamos a cambiar la fuente misma. Lo hacemos estableciendo la propiedad font del contexto del lienzo a una cadena CSS que represente la fuente. Si no estás familiarizado con el CSS, no te preocupes; a estas alturas, todo lo que necesitas saber es que contiene el tamaño de la fuente y el fontface:

("px" significa "píxeles").

¡Observa que tenemos que establecer el tipo de letra antes de dibujar el texto!

Echa un vistazo:

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

Funciona, pero la fuente Impact es realmente difícil de leer a ese tamaño. Hagámosla más grande:

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

Por desgracia, esto no funciona tan bien, porque la fuente sigue estando a 10px de la parte superior del lienzo, ¡pero ahora tiene 16px de altura!

Podríamos seguir cambiando la posición y de la fuente para solucionar esto, pero hay una alternativa: haremos que la posición que especifiquemos determine la esquina superior izquierda del texto, en lugar de la esquina inferior izquierda. Es fácil:

Por supuesto, ahora hay un espacio de 10px entre la parte superior del texto y la parte superior del lienzo:

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

...pero eso es fácil de arreglar ahora:

Bueno, en realidad, me gusta un poco de relleno, así que vamos a hacer esto:

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

Mucho mejor.

Diferentes tipos de fuentes

Tal vez te preguntes si existe una lista de fuentes que podamos utilizar. Pues bien, sí y no. Verás, si eliges un tipo de letra que el jugador no tiene instalado en su ordenador, entonces el texto se mostrará en la fuente por defecto. Recuerda que JavaScript está dibujando el texto sobre la marcha, utilizando los recursos del ordenador del jugador.

No hay problema en utilizar Impact, porque está instalada en todos los ordenadores, pero ¿significa eso que no podemos utilizar ninguna otra fuente que no esté en esta lista?

Afortunadamente, no. Podemos usar la fuente que queramos, siempre y cuando le demos al usuario acceso a ella de alguna manera. Y para ello, vamos a utilizar ese archivo CSS, ya sabes, el que no hemos tocado desde el inicio de la serie.

Supongamos que tenemos una fuente llamada "Really-Awesome". Ésta existirá en su ordenador, en algún lugar, como un archivo, probablemente un archivo .TFF ("True Type Font"). Supongamos que ese archivo se llama ReallyAwesomeFont.ttf.

Supongamos que subes esta fuente a tu sitio web, reallyawesomewebsite.com, para que haya una URL directa a ella: http://reallyawesomewebsite.com/fonts/ReallyAwesomeFont.ttf. 

A continuación, puede hacer que el navegador del reproductor lo sepa añadiendo esto a tu archivo CSS:+

Con esta línea en tu CSS, puedes alterar tu código así:

...y funcionará, porque su navegador buscará la fuente "Really-Awesome" en la hoja de estilos, y encontrará la URL del TTF. ¡Genial!

No voy a demostrar esto porque no poseo los derechos de redistribución de ninguna fuente; si subo algunas y te doy el enlace al TTF como parte de este tutorial, no es realmente justo. Pero hay una alternativa...

Fuentes web de Google

Google ha reunido una gran colección de fuentes que puedes utilizar en tu proyecto en tu CSS, utilizando un principio similar al anterior. Echa un vistazo a la colección.

Hay algunos criterios por los que puedes buscar fuentes, y puedes introducir algún texto de muestra para ver cómo se mostrará:

HTML5 avoider game tutorialHTML5 avoider game tutorialHTML5 avoider game tutorialFuentes web de Google.

Voy a elegir Islandia. Cuando hago clic en Uso Rápido, me da este HTML:

Si cargas http://fonts.googleapis.com/css?family=Iceland en tu navegador, verás que es el mismo tipo de cosa que escribimos antes desde cero:

Tiene algunos detalles más, y el tipo de letra está en formato WOFF en lugar de TTF, pero se entiende la idea.

Puedes usar la fuente que quieras (y no tiene por qué ser una fuente web de Google), pero para el propósito de este tutorial voy a asumir que estás usando Islandia. Así que, edita game.html y pega la referencia de la fuente en él:

Ahora, de vuelta en main.js, cambia la fuente de Impact a Iceland:

Echa un vistazo:

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

¡Genial!

Reto: Mostrar la mejor puntuación

Ahora que la puntuación actual está en pantalla en todo momento, es natural que el jugador quiera ver lo que está tratando de superar.

Utilizando lo que has aprendido, dibuja tu mejor puntuación en la parte superior derecha del lienzo. Esto es un poco más complicado de lo que parece: tendrás que decidir qué hacer (si es que hay que hacer algo) cuando la puntuación actual supere a la puntuación máxima actual.


Guardar las puntuaciones entre sesiones

Seguramente ya te habrás dado cuenta de que la puntuación máxima se restablece al recargar la página. Esto tiene sentido, después de todo, ejecutamos esta línea justo al principio:

...pero incluso sin esa línea, la alta puntuación no persistiría. Todas las variables se reinician y se desasignan cuando se sale de la página.

Sin embargo, hay una alternativa: todos los navegadores reservan 5MB de almacenamiento para cada dominio de sitio web. Puedes almacenar cualquier cadena que quieras en este almacenamiento de 5MB, y permanecerá allí incluso si el usuario cierra su navegador y reinicia su ordenador.

Se llama almacenamiento local y es muy fácil de usar. Para guardar algo en él, solo tienes que darle dos cadenas:

  • un nombre para el artículo, y
  • el valor del artículo.

Para recuperarlo, solo necesitas el nombre que usaste originalmente.

Un ejemplo rápido

He aquí un ejemplo rápido: vamos a añadir algo al almacenamiento local justo al comienzo de la función setUpGame():

Guarda el archivo y carga tu juego. A continuación, cierre la ficha.

Ahora, edita de nuevo tu archivo JS, elimina la línea que acabas de añadir y sustitúyela por una línea que recupere el elemento:

Así que, para que quede claro, ahora no hay nada en el código que establezca el valor de "exampleItem".

Cuando cargues el juego esta vez, deberías ver una alerta con "Este es un gran ejemplo", prueba de que la cadena se ha guardado entre sesiones.

Podemos eliminar este elemento del almacenamiento local utilizando localStorage.removeItem():

Te recomiendo que lo hagas ahora y que luego elimines la línea por completo.

También puedes borrar todo lo que hay en el almacenamiento local de una vez, usando localStorage.clear() (no requiere argumentos). Vale, pero no todo. Tu página solo puede afectar al espacio de almacenamiento local asignado al dominio en el que está alojada; no puedo borrar el almacenamiento local de google.com desde una página alojada en tutsplus.com, y viceversa.

Guardando la mejor puntuación

Ahora que hemos visto un ejemplo, vamos a ponerlo en práctica.

Cada vez que se establece una nueva puntuación máxima, vamos a guardarla en el almacenamiento local. Solo se establece en un lugar: cuando el avatar colisiona con un enemigo y la puntuación actual es mayor que la mejor puntuación. Así que añade la llamada a localStorage.setItem() en el lugar apropiado:

Cuando la página se carga por primera vez, debemos comprobar si ya hay una puntuación alta guardada en el almacenamiento local, y asignar mostTicksSurvived a ese valor, si es así. (No es necesario repetir esta comprobación una vez cargada la página, a menos que te preocupe que el usuario juegue en dos pestañas distintas a la vez).

¿Cómo comprobamos si un valor existe? Todo lo que tenemos que hacer es ponerlo dentro de una condición if:

La alerta() anterior nunca se llama porque localStorage.getItem("thisItemDoesNotExist") no existe. Fácil, ¿verdad? Así que al comienzo de setUpGame(), podemos escribir:

Inténtalo. Carga la página, establece una puntuación alta y luego bájala. Vuelve a cargar la página y obtén una puntuación más baja: ¿te dice que has superado tu antigua puntuación?

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

¡Muy bien! Por cierto, si has hecho el reto antes, podrás ver tu mejor puntuación anterior en la esquina superior derecha del lienzo, y ésta se mantendrá de una sesión a otra.


Nota: Tipificación fuerte y débil

Estamos a punto de terminar esta parte de la serie, pero solo quiero señalar que, una vez más, hemos estado tratando una cadena como un número y un número como una cadena: el almacenamiento local solo guarda valores de cadena, sin embargo, guardamos un número en él cuando guardamos la mejor puntuación, y usamos el valor que recuperamos de él como un número más tarde.

Esto es aceptable, porque JavaScript es lo que se llama "weakly-typed". Otros lenguajes de programación están "fuertemente tipados", lo que significa que si se dice que algo es un número, entonces sigue siendo un número; si se dice que algo es una cadena, entonces sigue siendo una cadena.

En un lenguaje fuertemente tipado, si quieres añadir la cadena "Score: " al número 32, entonces tienes que decirle explícitamente al lenguaje que trate 32 como una cadena, tal vez así:

"Puntuación: " + (32 como cadena)

Además, en un lenguaje fuertemente tipado, cuando se define una variable, también se especifica de qué tipo es:

Pero JavaScript no se preocupa de estas cosas. Esto no lo hace mejor o peor que un lenguaje fuertemente tipado, solo diferente.


Conclusión

Eso es todo para esta parte del tutorial. Ahora tu juego tiene una condición de finalización del juego y un medio para mantener la puntuación. Además, has aprendido a dibujar texto en un lienzo, a elegir fuentes y a utilizar el almacenamiento local.

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

  • Fácil: Dibuja la mejor puntuación en el lienzo, antes de que el jugador haga clic.
  • Medio: Guarda las cinco mejores puntuaciones del jugador.
  • Difícil: Guarda las posiciones de todos los enemigos en el almacenamiento local, y restablécelas en la siguiente sesión (¡puede que tengas que hacer una investigación extra aquí! Pista: busca en JSON).

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