P5 Play El Dinosaurio de Google parte final

Anteriormente:

Hasta el momento ya tenemos todas las clases que son necesarias para nuestro juego, hemos creado el dinosaurio, los cactus y las aves, hemos observado que tanto los cactus y las aves tienen los mismos métodos con poca variación en su algoritmo, finalmente lo que resta es colocar todas las instancias de cada una de las clases en el fichero main.js, y ejecutar sus métodos en las funciones preload(), setup() y draw().

Al inicio del fichero es donde hemos de crear las instancias, una instancia de una clase en JavaScript se hace de la siguiente forma:

var rex= new Dinosaurios();
var planta=new Cactus();
var ave=new Aves();

JavaScript nos hace sencillo la creación de las instancias, pero ¿Notas algo diferente?, ahora estamos usando el prefijo var en lugar de let ¿Cuál su diferencia y cuando usarlo?, pues su principal diferencia radica en el alcance de la variable, cuando nosotros declaramos una variable con el prefijo let, limitamos la variable al bloque donde esta se encuentre; sin embargo, al usar el prefijo var, la variable es de alcance global no importando el bloque en el que se encuentre y dado que deseamos que nuestras instancias estén disponibles para cualquier función las declaramos de alcance global (esto es un tanto desaconsejado en términos que solo se debería usar variables de alcance global cuando ya sea inevitable no hacerlo), aquí encontrarás una pequeña reseña de ello. Ahora bien, hasta el momento solo tenemos las funciones setup() y draw  en el main, ahora debemos de agregar la función preload(), recordemos que preload se llama antes de setup() y hemos de usarlo para la carga de los recursos de nuestro juego, entonces inicialmente queda así:

final1Recordemos que en cada clase hay un método que se llama cargar, el cual está enfocado en la carga de los recursos necesarios para el juego, también tenemos un método que hace referencia a la creación, en la clase Dinosaurio se llama crear y en el cactus y las aves se llama crearGrupo, todos los métodos de carga debemos de ubicarlos en la función preload y los de creación dentro de setup quedando de la siguiente forma:

final2 Entonces ya hemos cargados los recursos y creado los sprites; sin embargo, tenemos que colocar los métodos que supervisan el movimiento, la muerte y las colisiones, estas funciones deben ir en draw()  quedando de la siguiente forma:

final3

Vemos que hay una función que no está asociado a alguna instancia, esta función pertenece a P5 Play (drawSprites)  y recibe un parámetro de grupo, pero en caso de no recibirlo se encargará de ejecutar todos los sprites en el orden que se encuentren.

Hasta este punto ya hemos colocado todos los métodos de las clases que hemos creado, pero tenemos aun pendientes unas cuantas correcciones, recordemos que cuando explicábamos la muerte del dinosaurio vimos que al ser verdadera la colisión esta llamaba a la función  juegoTerminado(), pero esta función aún no la hemos creado y tampoco se iba a crear dentro de la clase dinosaurio, ya que esta función estaría encargada de detener todo el juego, entonces su campo de acción es global por lo que debe ser creada en el fichero main.

juegoTerminado

Esta función debe estar en capacidad de hacer lo siguiente, debe cambiar la animación del dinosaurio a muerto, así también debe comunicar a quien lo requiera que se ha producido un gameOver por así decirlo y sobretodo debe poder detener la actualización de los sprites, para ello observemos lo siguiente:

final4

El cambiar la animación del dinosaurio a través de su instancia “rex“, rex debe acceder a la propiedad “dino“, recordemos que dino es el sprite de la clase y sobre él ha girado todos los métodos de P5 Play, para cambiar la animación debemos de usar el método changeAnimation() que recibe como parámetro el key que le asignamos a cada una de las animaciones al momento de crearlas, también el uso de una variable denominada gameOver esta debe ser declarada e inicializada en false en al mismo nivel de las instancias de las clases de la siguiente forma:

var gameOver=false;

Se inicia en falso ya que al iniciar el juego es imposible que se tenga un gameOver pero cuando el juego finalice, gameOver debe ser verdadero, cuando finalice el juego de la misma forma debemos de comunicar a p5play que debe detener la actualización de los sprites esto se hace con la función updateSprites() indicando como parámetro “false“. Ahora ocurre lo siguiente, tenemos a nuestro dinosaurio funcionando y de pronto este muere, y entonces surge la inquietud ¿Cómo hacemos que inicie un nuevo juego?, para ello debemos de crear una función similar a juegoTerminado y la llamaremos juegoNuevo.

juegoNuevo

Esta función debe estar en capacidad de remover todos los sprites que se encuentren en la escena, ya que se trata de un juego nuevo todas dinámicas deben de reiniciarse, la variable gameOver debemos asignarle un valor de falso y  tenemos que reactivar la actualización de los sprites, esto lo hacemos de la siguiente forma:

 

final5

Tan igual que en “rex” la planta y el ave deben acceder a quien representa al sprite, el cual es cactusGrupo y aveGrupo respectivamente, debe entonces usarse en ellos el método removeSprites() que removerá todos los sprites que contenga el grupo, de forma similar con las aves; cambiamos el valor de gameOver, pues al iniciarse un juego este debe ser falso,  y entregamos como parámetros a la función updateSprites un true; para que nuevamente todo sea puesto en marcha; ahora no es que apenas el dinosaurio muera es que el juego lanza un evento que llama a la función juegoNuevo, debemos hacer que exista un botón en el cual un evento esté a la escucha de cuando se hizo click en él, además podemos de agregar un letrero de gameOver y ¿Por qué no? el puntaje, entonces vamos en un pequeño orden, agreguemos el puntaje:

Puntaje

El puntaje es un texto que hará uso de una fuente, la fuente debe ser cargada en el preload, entonces declaramos una variable global denominada fuente, esta fuente va ser afectada directamente por el puntaje que se logre mientras el dinosaurio esté vivo, entonces podemos decir que existe un contador que inicia en cero cuando arranca el juego:

var texto;
var fuente;
var contador=0;

Para cargar la fuente vamos a preload y hacemos:

fuente=loadFont("./recursos/imagenes/GeBody.ttf");

loadFont es un método de P5Js encargado de cargar las fuentes. Este texto debe ir con cierto estilos, para ello creamos una función miTexto(), dentro de miTexto colocamos:

function miTexto(){
     textSize(36);
     fill("#ED225D");
     textFont(fuente);
}

y a esta función la colocamos en el setup(), es decir:

function setup(){
.....
.....
miTexto();
}

Como el puntaje se ha de actualizar constantemente y este aumenta en función del tiempo o distancia que haya recorrido el dinosaurio, significa que si hemos de escribir un texto y este se encuentre en constante modificación, para ello la mejor forma es hacerlo dentro del draw, entonces en draw hacemos:

text(""+contador,width-100,50);

Pero esto por si solo no es suficiente, ya que el contador debe detenerse cuando gameOver sea true,  además si observas solo estamos concatenando el texto vacío con el contador, todo ello a través del método text de P5Js, entonces lo que debemos hacer es implementar un setInterval en el setup que revise cada cierto tiempo si el gameOver es true o false y en caso de ser false el contador aumente en uno, entonces en el setup hacemos:

setInterval(function(){if(gameOver==false){contador++;}},100);

Además cuando gameOver sea true ya no se va aumentar el valor del contador, pero debemos también que cuando inicie un nuevo juego este contador deba volver a cero, entonces en la función juegoNuevo() agregamos:

contador=0;

Entonces todo lo del puntaje queda de la siguiente forma:

final6

El Botón

Hasta el momento tenemos el puntaje, pero no tenemos como llamar a la función juegoNuevo, esto comentamos que lo haríamos a través de un botón, entonces manos a la obra, conjuntamente con el botón debe salir una imagen que hable del game over , entonces creamos una variable a la altura de las instancias de las clases de esta forma:

var boton;
var imagenGameOver;

En la función preload hemos de cargar el recurso de la  imagen de game over de la siguiente forma:

imagenGameOver=loadImage("./recursosimagenes/gameOver.png");

Para el botón hemos de usar un artilugio en el que a una imagen le daremos la acción de un botón, para ello a la variable botón la inicializamos en setup de la siguiente forma además de agregar la posición:

boton=createImg("./recursos/imagenes/reset.png" ).position(width/2,height/2);

El método que hemos empleado para crear la imagen es createImg  es un método de P5DOM el cual crea un tag img en el DOM y recibe como parámetro la dirección de la imagen, a esto hemos encadenado la posición a través del método position también de p5DOM y que recibe como parámetros la ubicación en X e Y. Queremos que nuestro botón esté al centro del canvas, pero que al iniciar el juego el botón esté escondido, para esconderlo debemos de usar el método hide de P5DOM entonces en setup de igual forma:

boton.hide();

Además de que esté escondido, debemos de hacer que esté a la escucha del evento click (es decir cuando sea presionado), entonces al ser un elemento de p5DOM podemos agregarle un oyente a través de la  función mousePressed el cual recibe como parámetro una función que se llamará cuando el evento ocurra, y la función que se llamará sera a juegoNuevo, entonces:

boton.mousePressed(juegoNuevo);

Ahora debemos de asegurarnos que cuando la variable gameOver sea true el botón deje de estar escondido y además que se agregue la imagen de game over, para agregar la imagen hemos de usar el método  image de P5Js que recibe como parámetros la imagen que está pre-cargada en preload y la posición (como verás la posición está un poco desfasada del centro con el fin dar la apariencia que se despliega en el centro) , como debemos de verificarlo en cada iteración lo más conveniente es colocarlo dentro del draw():

if(gameOver){
boton.show();
image(imagenGameOver,width/2-30,height/2+30);
}

Por último, cuando el usuario presione el botón  y se llame a juegoNuevo, el botón debe de ocultarse de nuevo, entonces el método hide debe ser agregado dentro de la función juego nuevo:

function juegoNuevo(){
.......
.......
boton.hide();
}

 

Un último detalle

Para agregarle mayor interés al juego vamos hacer que la velocidad de los cactus y de las aves esté en un aumento progresivo tal que cuando se pierda le restauramos su velocidad inicial, para hacer ello debemos de recurrir nuevamente al setInterval, entonces vamos a la función setup y hacemos:

function setup(){
........
......
setInterval(function(){planta.velocidadCactus-=0.2;ave.velocidadAve-=0.2},3000);
}

 Es decir estamos haciendo ambas velocidades más negativas en 0.2 cada 3 segundos, pero ahora cuando se pierda debemos de restaurar las velocidades base, entonces en juegoNuevo debemos:

function juegoNuevo(){
.....
......
planta.velocidadCactus=-4;
ave.velocidadAve=-4;
.......
......
updateSprites(true);
boton.hide();
}

Esta modificación o restauración de la velocidad debe estar antes de la actualización de los sprites, entonces finalmente el juego queda de la siguiente forma:

final7

final8

 

Ahora solo basta con ejecutar en el terminal la instrucción http-server y se creará un servidor y copiamos el enlace y lo abrimos en el navegador, para ello debes de tener instalado el modulo http-server, sino lo tienes en tu terminal de node la cual está incorporada en VSCODE haces los siguiente:

npm i http-server

final9

Eso sería todo respecto al juego del dinosaurio de google, espero que lo hayan podido disfrutar, saludos.

 

P5 Play El Dinosaurio de Google parte 4

Anteriormente:

Hasta el momento hemos definido el dinosaurio y los cactus, observamos que sustancialmente tanto el dinosaurio y los cactus tienen un comportamiento diferente y que su tratamiento es distinto desde el simple hecho en que uno es un sprite y el otro un grupo de sprites, así como que los cactus no tienen animaciones ya que solo son imágenes. Ahora veremos al ave, el ave es casi totalmente similar a los cactus a excepción que sí posee una animación (aleteo). Entonces la estructura será similar que la del cactus:

dino7

Para las aves tal como hicimos con el cactus nos vemos en la necesidad de inicializar las propiedades de aparición y velocidad del ave, y su inicialización está dado por el setInterval, este método redefinirá la variable pasado 4 segundos; sin embargo, para la aparición de las aves en los métodos de aparecerMover requiere que haya un valor inicial para ejecutar la salida del ave y en cadena ocurre de similar forma con el método muerte que requiere evaluar un arreglo de aves, si esperamos los 4 segundos para inicializar la variable estos métodos arrojarían error, el primero al solo estar declarada la variable sin valor alguno y el segundo por encontrarse con arreglo sin elemento alguno.

cargar

Este método responde a la necesidad de cargar preliminarmente todos los recursos para desplegar el ave, y los recursos del ave es una animación compuesta de dos imágenes, entonces hemos de emplear el método loadAnimation que recibe como parámetros la cantidad de imágenes que compongan la animación, este método nos devuelve un objeto Animation el cual asignamos a una propiedad de la clase quedando de la siguiente forma:

dino8

 crearGrupo

Dado que las aves tienen un comportamiento similar es más eficiente agruparlas con el fin de asignar acciones sobre el grupo y que repercuta sobre cada uno de los elementos del grupo.

dino9

Creado el grupo, debemos implementar la función setInterval el cual recibe como parámetros una función y un tiempo en mili segundos, tal como comentamos al inicio es en setInterval donde redefinimos cada 4 segundos la propiedad aparición, así también usamos un artilugio con el fin de indicar correctamente el contexto sino simplemente se interpretará que la propiedad no existe.

aparecerMover

Este método no posee la misma complejidad que tuvieron los cactus pero tomamos algo de su lógica para su implementación; tal como hemos visto en el juego las aves aparecen a intervalos en el eje X, la cual está determinada por el condicional que evalúa si el resto entre el frameCount y la propiedad aparición es cero, pero también varia su posición en Y, alguna aves salen al ras del suelo otras más arriba, tenemos que intentar  imitar ese comportamiento, entonces para ello debemos de desfasar la posición en Y del ave, este desfase para que aparente cierta naturalidad debe generarse de forma aleatoria, cuando ello suceda creamos el sprite (createSprite()), agregamos la animación (addAnimation), definimos el collider (setCollider()), habilitamos el debug, definimos la velocidad (setVelocity) y agregamos al grupo, quedando finalmente:

dino10

muerte

Un ave muere cuando sale de la escena, tal como ya habíamos comentado que la razón de eliminarla obedece a desocupar el espacio en memoria que ocupa un sprite y que si no lo gestionamos estos pueden llegar a saturarla con el tiempo, para eliminar los elementos que no están en la escena, recorremos con un bucle el grupo de aves, si alguna de las aves su posición es menor a menos un cuarto del ancho del canvas este debe ser eliminado:

dino11

P5 Play El Dinosaurio de Google parte 3

Anteriormente:

Hasta el momento tenemos ya definido nuestro dinosaurio, queda entonces definir a nuestros enemigos que son las aves y los cactus, para ello tal y como hicimos con el dinosaurio debemos de crear clases para cada uno de ellos, entonces empecemos con los cactus.

dino-1

Vamos a crear una clase Cactus, esta de igual forma que el dinosaurio debe tener un método que le permita cargar sus recursos, crear sus sprites, controlar su movimiento y definir cuando este muere. Para crear una clase en JavaScript debemos de usar la palabra reservada Class (ya anteriormente hemos revisado esto); sin embargo, en esta ocasión haremos uso de una novedad que en otros lenguajes ya s muy común que es el uso del constructor, que es el encargado de inicializar parámetros de los objetos o propiedades de los mismos, entonces por el momento como un acto de fe hemos de crear dos propiedades dentro del constructor, la primera se llamará aparicion y la segunda velocidadCactus, con valores que realmente son arbitrarios ya que la principal función de estas variables es de regular el comportamiento de los cactus (ya lo entenderán), quedando de la siguiente forma:

dino1

Cargar

¿Qué tipo de recursos es los que tenemos que cargar para los cactus?, ¿Animaciones o imágenes?, la respuesta se decanta por si sola, en el juego los cactus no poseen animación alguna; sin embargo, existen 3 tipos de cactus, lo que implica que debemos de cargar 3 imágenes correspondientes a cada uno de los cactus, para la carga de imágenes usamos el método de P5Js loadImage que recibe como parámetro la ruta de la  imagen y asignaremos a cada una de esas imágenes a una propiedad de la clase para que estén disponibles para los demás, entonces queda de la siguiente forma:

dino2

CrearGrupo

Existe una particularidad en cuanto a los cactus, todos tienen el mismo comportamiento, con lo cual no seria pertinente crear una instancia de cactus por cada cactus que haya de aparecer; cuando un conjunto de sprites comparten  un mismo comportamiento estos se agrupan con la finalidad de poder medir su comportamiento, para ello P5Play tiene una clase denomina Group, entonces podemos crear el grupo de la siguiente forma:

this.cactusGrupo=new Group();

Hasta aquí todo bien; sin embargo, debemos en este método agregar una función que tiene relación con el comportamiento de los cactus, es decir, nuestros cactus deben aparecer en intervalos relativamente diferentes, esto con la finalidad que el usuario pueda demorar en resolver los tiempos de aparición (no sé el término técnico a ello, pero todos al jugar un juego estamos resolviendo una serie de variables tales como cada cuantos intervalos de tiempo el enemigo lanza un ataque, este ejercicio mental es uno de los componentes que hace interesante el juego), dado que en el constructor hemos definido e inicializado una propiedad llamada aparición, esta debe redefinirse cada intervalo de tiempo, ya que esta variable incidirá directamente en la aparición de los cactus en la escena, este comportamiento debe estar garantizado independientemente de los demás métodos es por ello que para hacerlo hemos de usar una función estrictamente de JavaScript llamada setInterval() Esta función evalúa una expresión o ejecuta una función en un intervalo de tiempo especificado en mili segundos, su constructor recibe como parámetros una función y un número que será el tiempo, entonces el método queda de la siguiente forma:

dino3

Dentro de la función anónima vemos que la propiedad aparición es redefinida a un valor entero comprendida entre 40 y 90 y que esto ha de suceder cada 4 segundos (4000), pero también podemos apreciar otra particularidad que es el uso de una variable denominada “self“, la cual se usa como un artilugio en la programación de JavaScript ya que setInterval está asociada al  objeto window y cuando nosotros usamos la palabra reservada this, estamos haciendo referencia a este objeto o contexto y en JavaScript esto puede ir cambiando ya que es dinámico; sin embargo, en el objeto window no existe la propiedad aparición, es por ello que debemos de indicarle a JavaScript a que contexto u objeto estamos haciendo referencia referencia que es la clase Cactus (diferencia entre this y self).

ApareceryMover

Este método podría decirse que es el core de la clase, ya que en el se han de usar las variables que están en el constructor y han de definirse otras dentro de la misma, ¿Qué debe ocurrir dentro de este método?,  pues el método debe ser capaz de elegir que cactus de los 3 que existen se creará y cuando debe crearse,  dado que todas las imágenes son diferentes debe poder crear un sprite, calcular su posición y agregarlo al grupo this.cactusGrupo,  entonces veamos como queda y analizaremos cada una de las líneas:

dino6.png

Para variar el cactus necesitamos de una variable (variarCactus) que cada vez que sea llamada deba de generar un número entero entre 1 y 3, declaramos una variable para la imagen (imagenCactus) que dependiendo del valor de variarCactus ha de recibir el cactus 1, 2 o 3; para establecer una rudimentaria forma de generar los cactus hemos de usar el contador de frames de P5Js denominado frameCount, cuando el resto de la división entre la cantidad de frames que han ocurrido (frameCount) y la propiedad this.aparicion (la cual está cambiando de valor cada 4 segundos) de cero, procedemos a crear un cactus; sin embargo, este cactus está en un condicional que dependiendo de su valor (de 1 a 3) elegirá una de las imágenes ya cargadas, también observamos que hemos declarado 3 variables más (desfasePosicion, dxCollider, dyCollider), estas variables responden a lo siguiente, la primera es que dado que las imágenes tienen distinto alto y ancho, no podemos colocarlas todas en una misma posición en Y, por cada tipo de imagen debemos de agregar un desfase, por ejemplo si no usamos esta variable veríamos que el cactus 1 estaría en la posición correcta, pero el cactus 2 y 3 estarían más abajo, así como la posición requiere un desfase por el alto y ancho, lo mismo ocurre con los colliders (recordemos que los colliders definen los límites del sprite con lo cual podemos evaluar si existe colisión o no) ya que las imágenes no son de la misma dimensión necesitan ser calibradas a través de dxCollider y dyCollider, cuando ya se ingreso a alguno de los condicionales las variables han sido definidas y pasan a los métodos de createSprite para crear un sprite, a ese mismo sprite le agregamos una imagen con addImage(imagenCactus) y definimos sus colliders con setCollider, habilitamos el debug para observar si lo colocado es correcto y configuramos la velocidad haciendo uso de las propiedades ya definidas en el constructor, esto lo hacemos con setVelocity(this.velocidadCactus,0) y finalmente agregamos el cactus al grupo de cactus con el método add.

Muerte

¿Cuándo muere un cactus?, ¿Cuando impacta con el dinosaurio?, pues no, cuando impacta con el dinosaurio el que muere es el dinosaurio, es mas finaliza el juego, pero un cactus muere cuando sale de la escena, pues ya no tendría razón de ser, ¿Y por qué no simplemente dejarlo en paz si ya salió de la escena?, pues porque aunque salió de la escena este sigue ocupando un espacio en memoria, y en determinado momento esto afectará al desempeño del juego por la acumulación de recursos en memoria que no están siendo usados, es por ello que debemos de eliminar a los  elementos que ya no nos sirvan, ¿Cómo hacemos esto?, dado que los cactus están agrupados podemos recorrer ese agrupamiento y verificar si cada uno de los elementos se encuentra aun en la escena y si no lo está pues removerlo, el remover se realiza con el método de sprite remove(), quedando finalmente de la siguiente forma:

dino5

En el condicional observamos que si un cactus en su posición en X está a  menos un cuarto del ancho del canvas, podemos asegurar que está ya fuera de la escena, entonces podemos eliminarlo.

 

En todo caso las aves comparten similar comportamiento, pero lo haremos en otra publicación

P5 Play El Dinosaurio de Google parte 2-2

Anteriormente:

Habíamos definido que nuestra clase de Dinosaurios debía de tener  5 métodos: cargar, crear, movimiento, controles y muerte (creo que los nombres son muy descriptivos), en la parte 2.1 resolvimos tanto la carga de los recursos como la creación de los sprites. Hoy vamos a resolver los 3 métodos restantes:

Movimiento

Para realizar el método del movimiento tenemos que ponernos a pensar en lo siguiente: ¿Qué tipos de movimientos inerciales realiza el dinosaurio?, si ya hemos jugado el juego (desafortunados los que perdimos alguna vez la conexión) el dinosaurio puede saltar, agacharse y avanzar; el movimiento de avance será resuelto por el movimiento que realiza el fondo, permitiendo dar la apariencia de avance del dinosaurio, siendo entonces que es el fondo el que ha de realizar el movimiento inercial, un cálculo menos sobre el dinosaurio, respecto a que el dinosaurio se agache o no está sujeto directamente a un control, entonces no es inercial, sobre el salto podríamos pensar lo mismo, pero debemos analizarlo, cuando el dinosaurio salta lo realiza a través de un control, este salto se manifiesta en una resta de su posición en Y y que por inercia el dinosaurio busque recuperar la posición inicial en Y, algo así como si estamos en tierra y saltamos la gravedad nos empujará nuevamente hacia el centro de la tierra pero que se expresa en que recuperamos nuestra posición inicial en Y que es básicamente el piso, entonces debemos de garantizar que siempre el dinosaurio vuelva al piso, debemos garantizar que siempre exista una gravedad que nos empuje hacia abajo (al menos para esta situación), para ello observemos como queda el método y explicaremos que hace cada línea.

t6

Obvia por un momento a la propiedad estaenPiso; haciendo ello, lo primero que debemos de asegurar es que el dinosaurio siempre caiga, para ello le asignamos una velocidad en Y con la propiedad velocity y que esta velocidad esté en aumento en +1, ya que la gravedad en su definición más básica es una aceleración y la aceleración es la variación de la velocidad respecto al tiempo, en nuestro caso podría decirse como una variación de la velocidad por frame es por ello que tomamos la decisión del aumento de la velocidad, entonces:

this.dino.velocity.y+=1;

Pero si dejamos que siempre caiga el dinosaurio saldrá de la escena, así que lo siguiente que debemos de asegurar es que no pueda pasar de la línea que hemos definido como tierra, la posición inicial del dinosaurio en Y (tierra) es height-40, entonces cada vez que el dinosaurio esté en la misma posición o quizá haya pasado por poco la misma, debemos de re asignarle la posición del piso (tierra, height-40), es ahí que la propiedad estaenPiso (inicia en false arbitrariamente, aunque podría iniciar en true) cobra validez, esta propiedad ha de informar a los controles que se está en el piso y en consecuencia podría ejecutarse un salto (es por ello de su naturaleza booleana),  entonces el condicional debe re definir la posición y la condición de que se está en el piso.

if(this.dino.position.y>=height-40){

this.dino.position.y=height-40;

this.dino.estaenPiso=true;};

 

Esto sería suficiente respecto al dinosaurio y su movimiento en Y, para su movimiento en X hemos decidido que sea el fondo el que se desplace y así dar la sensación de movimiento hacia adelante del dinosaurio, para ello recordemos que habíamos agrupado a los 3 fondos en un mismo grupo, así que es por ello que lo hemos tratado como un arreglo, también debemos de recordar que ya hemos asignado una velocidad a cada uno de los elementos del grupo en el método crear, así que en este método (movimiento) debemos de asegurar la sensación de un fondo continuo, esto lo logramos a través de que cuando un elemento del grupo en su posición en X esté fuera más allá de la mitad de la dimensión del canvas (width), este debe de re posicionarse a dos veces y medio del canvas, esto logra una concatenación de imágenes que dan la sensación de continuidad, entonces:

for(let i=0;i<this.fondoGrupo.length.y;i++){

if(this.fondoGrupo[i].position.x<-width/2){

this.fondoGrupo[i].position.x=2*width+width/2;

};

Y esto es todo lo relacionado al movimiento del dinosaurio en X e Y, ahora pasemos a los controles.

Controles

Respecto a los controles, será el método encargado de los movimientos que no son inerciales sino que son controlados por el usuario, estos movimientos son el salto (en uno de sus componentes, el que dispara el evento) y agacharse, veamos el código final y comentemos las líneas:

t7.png

Respecto al salto debemos de preguntarnos, ¿En qué condiciones puede saltar?, para que pueda saltar debe cumplirse al menos dos condiciones, la primera es que esté en el piso, y la segunda es que no esté agachado, la información de si está en el piso o no es proporcionada a través de la propiedad del sprite estaenPiso; sin embargo, para saber si está agachado o no, esta se expresa a través de la presión de un tecla, así también disparar el salto se hará a través de una tecla que asignemos, entonces nuestro primer condicional debe evaluar que se está en el piso, luego de ello, debemos de evaluar que se presionó la tecla asignada de salto y que no está presionada la tecla asignada a agacharse, eso se expresa en estas líneas

if(this.dino.estaenPiso){ i=0;i<this.fondoGrupo.length.y;i++){

if(keyWentDown(32) && !keyDown("s")){}};

Vemos el uso de dos funciones que están a la escucha del teclado, pero son diferentes, keyWentDown(“tecla”) está a la escucha de que si al menos una vez se ha presionado una tecla específica durante el último ciclo y para el salto esto calza perfecto, pues solo necesitamos disparar el evento una vez, recordar que los ciclos están determinados por el draw() (el cuál es una de las funciones principales de p5js – parte 1); sin embargo, esto no ocurre de igual forma con el agacharse, ya que el agacharse debe suceder mientras la tecla siga presionada, es por ello que el método keyDown(“tecla”) detecta si la tecla está presionada independientemente del ciclo, ambos métodos pueden recibir un Number o String, el tipo Number se obtiene de la  siguiente tabla de códigos  y el String es la tecla que representa, “s” por ejemplo.

Cuando ocurre el salto, debe de reducirse la velocidad, arbitrariamente por ensayo y error se redujo en -12:

this.dino.velocity.y-=12;

Cuando se está agachado y mientras se esté agachado debe cambiarse la animación por defecto, esto se logra a través del método del sprite changeAnimation, este método recibe un String  que es la etiqueta que hemos asignado a las animaciones en el método crear, y cuando ya no se esté presionando la tecla de agachado esta debe retornar a la animación por defecto que es correr.

this.dino.changeAnimation("agachado");

Pero no solo cambia la animación sino también cambia los límites del sprite, esto pues cuando está corriendo su alto es mayor que su ancho, pero cuando está agachado el ancho es mayor que el alto:

this.dino.setCollider("rectangle",0,0,45,30);

Quedando finalmente ese segmento de código de esta manera:

t8

Muerte

La muerte del dinosaurio se produce por la colisión del mismo con otros elementos (enemigos) como el cactus o las aves, pero la colisión se interpreta operativamente como la intersección de los sprites, esta intersección se da cuando los colliders se intersecan, para evaluar la intersección debemos de tener acceso a las instancias de aves y cactus, entonces nuestro método ha de requerir parámetros que soliciten los cactus o las aves, vamos a imaginar que ya tenemos acceso a las aves y los cactus, entonces lo siguiente es evaluar a través de un condicional la intersección de los sprites quedando el código final de la siguiente forma:

t9

Vemos que utilizamos un método definido como overlap(grupo,[callback]), este overlap devuelve un booleano en el que evalúa la intersección, recibe un grupo, aunque también puede recibir un sprite único, entonces la lógica es simple, si se intersecan un cactus o un ave ejecutan la función juegoTerminado (este será definido en el fichero principal main.js), función que aún no hemos desarrollado, con esto hemos definido completamente a la clase dinosaurio, ahora lo que queda es implementar estos métodos a lo largo de las funciones principales setup y draw de p5js. Saludos.

P5 Play El Dinosaurio de Google parte 2-1

Anteriormente:

Primera parte

d1

Hasta el momento hemos configurado el entorno de nuestro videojuego y hemos determinado la necesidad de crear ficheros separados por cada una de las instancias que han de intervenir. Entonces para este artículo hemos de desarrollar al dinosaurio.

El dinosaurio es una instancia de la clase Dinosaurios, las clases en JavaScript se declaran a través de la palabra reservada class, estas fueron introducidas en ECMAScript 2015 y significaron una mejora sintáctica ya que en términos de fondo siguen siendo lo mismo. Nuestro dinosaurio tendrá que ser capaz de desplegar una serie de comportamientos y  realizar acciones, si nos ponemos a pensar en ello podemos notar que nuestro dinosaurio debe poder:

  • Cargar todos sus recursos y que estos estén disponibles.
  • Crear todos las animaciones necesarias.
  • Tener un movimiento y control a través del teclado de saltar y agacharse.
  • Poder morir.

Todos estos requerimientos se expresan a través de los métodos de la clase; además por el simple hecho de no crear un fichero demás, hemos de crear el comportamiento del fondo en el fichero del dinosaurio:

class Dinosaurios{
        constructor(){ }
        
    cargar(){}
    crear(){}
    movimiento(){}
    controles(){}
    muerte(){}
}

 

Método Cargar

Tal como hemos mencionado, el fichero principal (main.js) hará uso de la función preload para la carga de todos los recursos, todas las instancias deben hacer uso de esta función, en consecuencia debemos de crear los accesos necesarios, para ello el primer paso es que todos nuestros recursos estén en la misma área de trabajo, para este ejemplo se ha llamado una carpeta recursos y dentro de ella una carpeta imágenes. P5 Play para la carga de sprites hace uso del método loadAnimation, este método tiene como parámetro un sprite (si uno revisa su definición incluso indica que está hecho para ser usado típicamente en la función preload), como se tratan de recursos, podemos definir que un sprite para este método será el conjunto de frames que tenga nuestro sprite, entonces debemos de indicar la ruta de cada una de las imágenes, así también debemos de crear una animación por cada uno los comportamientos que tiene el dinosaurio (inicio, correr, morir y agacharse), quedando de la siguiente forma:

t1.png

Como todas las imágenes comparten una ruta en común creamos una constante con la dirección base (dirección), luego de ello listamos a cada una de las imágenes por su nombre, todo el método en su conjunto es asignado a una propiedad de la clase, pero ¿Qué es lo que devuelve esta función?, este método nos devuelve una animación. Respecto al fondo al tratarse de una imagen, hemos usado el método de p5 js loadImage.

Método Crear

En este método la lógica es, debemos de crear un sprite y agregar las animaciones respectivas, la forma de crear un sprite es a través del método createSprite,este método tiene el siguiente constructor:

createSprite(x,y,[width],[height])

De los parámetros: x e y representan la posición inicial del sprite; width y height representan el ancho y el alto de los límites del sprite; sin embargo, este se mantiene hasta que una imagen sea colocada, para tal efecto por lo general no se coloca dichos valores. Entonces podemos crear una propiedad y asignarle el sprite que se genera de la siguiente forma:

this.dino=createSprite(width/2,height-40))

Sobre width y height son variables proporcionadas por P5 js y que almacenan tanto el ancho como el alto de nuestro canvas, entonces le estamos diciendo que nuestro dinosaurio (this.dino) se posicione en la mitad del ancho del canvas y al final del canvas en el eje Y pero con -40 pixeles (sobre el porqué -40, por el momento tengamos un acto de fe, todo será revelado en su momento). Luego que hemos creado nuestro sprite debemos de asignarle las animaciones respectivas, recordar que JavaScript se lee de arriba hacia abajo en su flujo de ejecución, es por ello que  cuando asignemos una animación la primera animación que observaremos será la primera que asignemos al sprite, conviene entonces que esta animación sea la animación de inicio, para asignar las animaciones debemos de hacer uso del método addAnimation que pertenece a la clase sprite de p5 play, este método tiene el siguiente constructor:

addAnimation(label,Animation)

También tiene sobrecarga de constructores (revisar la documentación), entonces, el constructor principal recibe como parámetro un identificador (“label“) y un objeto animación (brevemente recordemos que una animación son una serie de imágenes que pueden ser desplegadas secuencialmente,  tal como lo hemos ido creando), entonces debemos de asignar un nombre de animación a cada una de las animaciones que hemos creado, quedando de la siguiente forma:

t2

Al ser un sprite, se le asigna una serie de propiedades, una de estas propiedades nos resultará muy útil para visualizar los límites de nuestro sprite, esta propiedad es debug, debug es de tipo booleano y por defecto es falso, entonces debemos de hacer que sea verdadero.

this.dino.debug=true;

Esto sería todo respecto a la creación del dinosaurio; sin embargo, también es aquí que debemos de ocuparnos de la creación de los elementos del fondo, partimos del hecho que el fondo solo es una imagen que es repetitiva  y que su función principal es darnos la sensación de movimiento del dinosaurio.

fondo

Para ello debemos de obtener esto:

t3

Imaginemos que el cuadro rojo es el cuadro que representa el canvas, hemos de crear 3 imágenes una de ellas dentro del canvas y dos fuera de el, ¿Con que fin?, pues dado que el fondo ha de colaborar con la sensación de movimiento del dinosaurio, el fondo se irá desplazando de derecha izquierda

t4

Y en algún momento las 3 imágenes saldrán de la escena, y el dinosaurio correrá en el vacío, entonces debemos de evitarlo, para ello cuando una de las imágenes del fondo salga completamente de la escena hemos de crear un método que este censando que si esto ocurre la imagen debe de ser trasladada hacia atrás con el fin de hacer la sensación de movimiento infinito. Ahora, ¿recuerdan de porqué -40?, pues la línea de la base del fondo (la línea dibujada) del fondo está a -40 pixeles, este valor se obtiene de ir con la técnica de ensayo y error (lo digo riendo, pero es cierto). Dado que el fondo serán tres imágenes que tienen el mismo comportamiento de desplazamiento, no sería conveniente ir creando estas de una en una, para ello P5 Play tiene una clase denominada Group. Observemos el código de como se implementa ello y explicaremos cada una de las líneas:

t5

Declaramos un grupo, básicamente un grupo es un arreglo (al menos en su mínima expresión).

this.fondoGrupo=new Group();

Por cada elemento del grupo hemos de crear un sprite, es por ello que usamos la instrucción For, dentro de la instrucción hacemos:

let fondo=createSprite(width/2+width*i,heigh/2);

En cada iteración posicionaremos el sprite a una distancia equivalente al ancho del canvas tal que el primero esté centrado en medio del canvas y los demás estén fuera de él.

Agregamos la imagen al sprite con el método addImage, este método tiene el siguiente constructor (también tiene sobrecarga de constructores):

addImage(label,Image)

Se usa:

fondo.addImage("fondo",this.fondoImagen);

Como el fondo es un sprite, podemos utilizar uno de sus métodos para delimitar las fronteras del sprite (colliders), estas fronteras serán importantes cuando consideremos las colisiones entre cada uno de los sprites, P5 Play nos permite configurar dos tipos de colliders (rectangular o circular), esto se expresa a través del método setCollider, el cual tiene sobrecarga de constructores. Este método tiene como uno de sus constructores:

setCollider(type,[offsetX],[offsetY],[width],[height]);

Sobre sus parámetros:

  • type: rectangle o circle,  tipo string
  • offsetX y offsetY: desfase en x e y
  • width y height: ancho y alto del collider

Como se usa (recordar que deseamos que el collider en sus dimensiones sean las mismas que tiene la imagen):

fondo.setCollider("rectangle",0,0,this.fondoImagen.width,this.fondoImagen.height);

Queremos que el collider sea de ancho y alto de las dimensiones de la imagen de fondo. Activamos el debug y ahora colocamos otra propiedad que hace referencia a la profundidad, esta propiedad es depth. Dado que JavaScript sigue un flujo de ejecución de arriba hacia abajo, recordemos que ya hemos declarado el sprite del dinosaurio y ahora hemos declarado (líneas más abajo) un fondo, entonces el fondo se sobrepondrá sobre el sprite del dinosaurio, para que esto no suceda debemos de indicar la profundidad del sprite que por defecto es 0, como queremos que el fondo esté detrás le colocaremos una profundidad de -1.

fondo.depth=-1;

Luego de la profundidad debemos de agregar una velocidad, esto lo hacemos con el método setVelocity, este método tiene el siguiente constructor:

setVelocity(x,y)

Sobre los parámetros x e y indican las velocidad en sus respectivos ejes del sprite, en nuestro caso lo hemos usado de la siguiente forma:

fondo.setVelocity(this.velocidadFondo,0);

Sobre el parámetro this.velocidadFondo su valor es -2 y fue declarado dentro del constructor de la clase. Y finalmente agregamos los elementos al grupo con la instrucción add:

this.fondoGrupo.add(fondo);

 

Hasta este punto hemos observado el método Cargar y Crear de la clase dinosaurios, continuaremos en otra publicación para no hacer extensa esta.

P5 Play – El Dinosaurio de Google parte 1

Dicen que todos los caminos te llevan a Roma,  y esto se cumple en la programación, anteriormente hemos revisado el como hacer videojuegos con una librería (quizá porque no decir framework) Phaser, realizamos un pequeño FlappyBird, observamos que la librería de Phaser es robusta en términos de videojuegos y que es muy compatible con otro tipo de librerías. A partir de este momento desarrollaremos una serie de apartados con otra librería que si bien es cierto no es tan robusta para el desarrollo de videojuegos, nos ofrece una gama mucho más amplia en la interacción con otros tópicos como podría ser el IoT. Para hablar con propiedad de P5 Play, debemos de referirnos a la biblioteca base que es P5 js. Tal como mencionan ellos en su descripción, ellos surgen con el objetivo principal de acercar el proceso de hacer código a personas que de por sí no tienen una formación robusta en él, como por ejemplo, artistas, diseñadores, educadores y etc. P5 js podemos decir es o busca ser la versión en JavaScript de Processing, Processing es un metalenguaje de Java que sigue estos mismos lineamientos de acercamiento a todo aquél que quiera acercarse a la programación; sin embargo, Processing estaba hecho en Java, ya que en el tiempo en el que se creo este era el lenguaje más de “moda” por así decirlo y ahora con todos los avances que representa JavaScript y al ser el lenguaje por defecto del navegador, se decidió hacer una conversión de Java a Javascript y de esto surge P5Js.

p51.png

P5js se implementa bajo la siguiente lógica, en un archivo index.html se agrega las librerías que se requieran tal como haríamos con cualquier librería de JavaScript

herencia

Se define un archivo cualquiera para el trabajo (en la imagen superior será main.js); y se definen las dos funciones esenciales para P5Js que son:

p52.png

La función setup, es la función que se llama una vez iniciado el programa, como se mencionó es el encargado de definir las propiedades iniciales del entorno, como tamaño del fondo, color, carga de elementos, fuentes, etc.

La función draw, es la función que se llama inmediatamente después de setup(), esta función se encarga de ejecutar constantemente todas las líneas de código que se encuentren dentro de él hasta que el programa se detenga o una función noLoop() sea invocada.

El Canvas

P5js se renderiza  o dibuja sobre un lienzo, por ello es el primer elemento en ser creado dentro de la función setup(), sino definimos el canvas este será creado por defecto por P5Js pero con una dimensión de 100×100 pixeles, crear un Canvas se logra a través de la instrucción:

p55

Formas Básicas

Podríamos decir sin ser estrictos que P5js su resultados es muy visual, esto se logra gracias a que se dibuja un canvas, pero lo que se dibuja sobre él son una serie de formas geométricas predefinidas que P5js las clasifica como formas primitivas 2D y 3D, algunas de estas formas le son aplicables ciertos atributos:

p53

En todo caso entremos en materia, si quisiéramos dibujar una circunferencia, una circunferencia es una elipse donde los diámetros son iguales, en consecuencia si se revisa su constructor de referencia veremos:

p54

Donde X e Y son las posición y w y h son el ancho y alto de la elipse, si ambos son del mismo tamaño solo basta con indicar el ancho. Entonces si quisiéramos dibujar una circunferencia seria de la siguiente forma:

p56

La única instrucción nueva es background(), que su función principal es pintar de un color en RGB o escala de grises al canvas, el resultado obtenido es:

p57.png

Ahora bien si quisiéramos darle movimiento deberíamos hacer:

p58.png

Cuando observes en el navegador verás la circunferencia en movimiento en el eje X; y esto se da por la declaración de una variable X que se agrega como componente del eje X el cual aumenta de 1 en 1 en cada iteración del draw(), otro detalle es el background(), recordemos que draw() hace que se dibuje todo el código que contiene constantemente, esto significa que estamos pintando el fondo de negro en cada iteración, si el background no estuviera en draw() solo se pintaría una vez y se vería como se redibuja la circunferencia constantemente:

gifbola

P5js puede reconocer inputs de tipo teclado o mouse, no de touch (quizá una de sus limitaciones), aunque existe una versión de Processing que está centrada para su uso en Android por aún no se encuentra robusto. Entendiendo que esto es lo básico (puedes encontrar más ejemplos aquí), debemos de centrarnos en la librería hecha para videojuegos. P5js dispone de muchas librerías entre las más importantes tenemos:

p59

Para los videojuegos exactamente usaremos el p5.Play. P5play tiene como objeto principal los sprites, en consecuencia es el primer objeto que debemos de crear y sobre el hemos de generar las interacciones, como los recursos podrían resultar pesados, debemos de asegurarnos que estos estén disponibles para su consumo, es por ello que haremos uso de la función preload(). Pero debemos de usar un concepto sobre la programación orientada objetos referido a la modularidad que básicamente es el agrupamiento por clases y que sobre esas clases generamos instancias, entonces se hace necesario reconocer los elementos que participan en el juego:

 

Fácilmente se puede reconocer que los elementos principales son el dinosaurio, el ave, el cactus, el terreno y el puntaje, quizá haya alguno más, pero con el desarrollo del juego podremos darnos cuenta y agregarlo, es obvio que el personaje principal es el dinosaurio, el cual entonces será definido en una clase, de igual forma con las aves y los cactus, respecto a los otros elementos, ya que no requieren un comportamiento elaborado podríamos expresarlos directamente sin tener que crear una clase para cada uno de ellos, entonces definidos los 3 elementos que tendrán una clase solo queda crear las clases e ir explicando el significado de cada uno de los métodos que se empleen. Entonces lo primero que debemos de hacer es definir el index.html, vamos a crear un archivo por cada clase y un archivo principal donde todo ha de gestionarse.

d1

Lo que ha cambiado al fichero inicial del index es que ahora hemos agregado la librería p5 Play y un fichero por cada clase que hemos de necesitar, finalmente un archivo main que es de donde se ha de gestionar todo el juego (en consecuencia donde se ha de declarar las funciones setup() y draw() y las instancias de cada una de las clases).  La definición del dinosaurio lo seguiremos en una siguiente publicación, nos vemos.

 

FlappyBird en Phaser-Las Colisiones y el GameOver – Parte 3

Hasta el momento hemos desarrollado:

En este apartado hemos de tratar las colisiones, básicamente ya que hemos habilitado el cuerpo físico de los sprites podemos revisar si los sprites colisionan, decir que los sprites colisionan es relativo, ya que la colisión es interpretada en función de la superposición de los sprites, para nosotros será el ave contra las tuberías, como cada frame por segundo debemos de revisar si los sprites se superponen implica que esta función debe estar en el método update.

Método Update

Debemos hacer referencia al sistema arcade, y esto lo hacemos a través de this.game.physics.arcade, arcade posee un método llamado overlap que tiene el siguiente constructor:

overlap(object1,object2,[overlapCallback],[processCallback],[callbackContext]);

El método requiere de dos objetos, estos objetos incluso pueden ser arreglos (grupos),  además se requiere de un callback que sea llamado cuando el evento suceda, no requerimos de una función que revise de mejor forma la superposición (entonces es null) y finalmente debemos de otorgarle un contexto, en nuestro caso queda de la siguiente forma:

this.game.physics.arcade.overlap(this.ave,this.tubos,this.tocoTubo,null,this);

Como se observa, el callback es this.tocoTubo, por lo cual debemos de crear dicho método.

Método tocoTubo

En este método debemos informar de alguna forma al juego que el ave ha muerto, para ello Phaser dispone de una propiedad de Phaser.Sprite denominada alive. Esta propiedad no afecta las propiedades lógicas, pero es muy usada para informar que el objeto ya no está “vivo“. Entonces ya que queremos usarlo el método nos queda de la siguiente forma:

tocoTubo:function(){
 if(this.ave.alive==false){return;}
 else{
   this.ave.alive=false;
   this.tubos.forEachAlive(function(t){t.body.velocity=0;});
   this.game.time.events-remove(this.repeticion);
   this.game.state.pause();
   this.game.state.start("GameOver"); 
  }
}

Expliquemos el algoritmo que hemos implementado; si el ave está muerta (this.ave.alive==false) entonces la función no devuelve nada y vuelve al hilo de ejecución, pero sino, le asignamos un valor de false a la propiedad, luego a cada elemento del grupo que esté vivo indicamos  que su velocidad sea cero, también removemos el evento de tiempo (this.game.time.events.remove), finalmente ponemos en pausa el estado con this.game.state.pause() y llamamos al estado “GameOver“, recordemos que ambos métodos pertenecen a la clase Phaser.StateManager; con todo ello ya estamos controlando la superposición de los sprites.

La superposición de sprites no es la única forma en la cual se pierde en el juego, otra forma es cuando el ave choca con la base del fondo, entonces debemos de indicarle dicha condición:

if(this.ave.position.y>=this.fondo.height-this.ave.height/2-115){
 this.ave.alive=false;
 this.game,state.start("GameOver");
}

El algoritmo, revisamos la posición en Y del ave, si esta es el fondo disminuido en 115 (posición relativa en +10 o -10 de la base cuando se insertó como tileSprite) y en la mitad del sprite del ave, ya que el centro de gravedad del ave está en dicho punto y desde ahí se toma la posición, ¿Por qué no hemos usado overlap? pues debido a que no hemos asignado un cuerpo físico a la base, podríamos hacerlo, pero aumentaría el cálculo sin sentido por parte de la librería cuando se puede resolver de esta forma.

colisionFondo.png

Con ello nuestra ave, morirá ya sea tocando alguna tubería o tocando la base.

Los puntajes

Luego de ello es importante poder colocar algún puntaje, tal como habíamos mencionado en el estado Menu un texto sigue siendo un objeto y como tal es parte del Phaser.GameObjectFactory, pero este texto, tendrá un valor inicial que será cero, que luego irá cambiando de acuerdo a la condición que coloquemos, además, este puntaje debe ser comunicado al estado GameOver, en donde se colocará como parte de su mensaje final, para ello debemos de crear una variable global denominada puntos:

var puntos;

Puntos debe ser declarada fuera del objeto estado Main, en el método create inicializamos la variable puntos y creamos un texto de la siguiente forma:

punto=0;
 this.txtPuntos=this.game.add.text(20,20,"0",{font:"30px Arial",fill:"#FFF"});

¿En que condiciones cambia el valor de punto?, para nuestro juego tomaremos que se hace un punto cuando una tubería sale de la escena y se destruye, entonces en el método encargado de ello agregamos:

this.tubos.forEach(function(tubo){
 if(tubo.position.x<=tubo.width){
   tubo.destroy();
   puntos+=0.5;
   this.txtPuntos.text=puntos;
}
},this);

De la escena salen siempre dos tuberías, por lo cual si colocáramos +1 siempre estaría sumando dos puntos por juego de tubería, pero lo que queremos es que solo sume +1 por juego de tubería y es por ello que solo aumenta en +0.5 por tubería, luego de ello, pasamos al texto los puntos en su propiedad text. Hecho esto podemos ver como va quedando el código.

var puntos;
let Main={
    init:function(){
        this.scale.scaleMode=Phaser.ScaleManager.SHOW_ALL;
        this.scale.pageAlignHorizontally=true;
        this.scale.pageAlignVertically=true;
    },
    preload:function(){
        this.load.image("fondo","./recursos/sprites/background-day.png");
        this.load.spritesheet("ave","./recursos/sprites/flappyazulsprite.png",34,24,3);
        this.load.image("tubo","./recursos/sprites/pipe-green.png");
        this.load.image("base","./recursos/sprites/base.png");
    },

    create:function(){
      
        https://photonstorm.github.io/phaser-ce/Phaser.GameObjectFactory.html#tileSprite
        this.game.world.setBounds(0,0,this.game.width+50,this.game.height-115)
        this.game.physics.startSystem(Phaser.Physics.ARCADE);
        this.fondo=this.game.add.tileSprite(0,0,this.game.width,this.game.height,"fondo");
       this.base=this.game.add.tileSprite(0,this.game.height-115,this.game.width,145,"base");
       
       this.ave=this.game.add.sprite(150,20,"ave");
        this.ave.anchor.setTo(0.5);
        this.ave.animations.add("vuelo",[0,1,2],10,true);
        //habilitamos la fisica de nuestra ave
        this.game.physics.arcade.enable(this.ave);
        this.ave.body.gravity.y=1200;
        
        this.ave.body.collideWorldBounds=true;//ave se queda en el mundo

        this.fondo.inputEnabled=true;
        this.fondo.events.onInputDown.add(this.saltar,this);
      
        this.tubos=this.game.add.group();
       
        
       this.repeticion=this.game.time.events.loop(3000,this.crearTubo,this);
       //puntajes
       puntos=0;
       this.txtPuntos=this.game.add.text(20,20,"0",{font:"30px Arial",fill:"#FFF"});
                
   },
    update:function(){
        /****************TODO LO RELACIONADO AL FONDO******************** */
        if(this.ave.alive){
            this.fondo.tilePosition.x-=1;
            this.base.tilePosition.x-=1;
    
        }
      /********************************************************************* */
       // console.log(this.ave.alive)

       /****************TODO LO RELACIONADO AL AVE *************************** */
       this.ave.animations.play("vuelo");
        if(this.ave.angle<20){this.ave.angle+=1;}
        //Si el ave choca con el limite inferior se termina el juego y pasa al siguiente estado
        if(this.ave.position.y>=this.fondo.height-this.ave.height/2-115){
            this.ave.alive=false;
            this.game.state.start("GameOver");
        }
        
          
       /*********************************************************************** */
       /************************* TODO LO RELACIONADO A LOS TUBOS ************************** */
        
        
      this.tubos.forEach(function(tubo){
          if(tubo.position.x<=-tubo.width){
              tubo.destroy();
             /* this.puntos+=1;
              this.txtPuntos+=this.puntos;*/
              puntos+=0.5;
        this.txtPuntos.text=puntos;
            }
        },this)
       
      /***************************************************************************************** */
       /*************TODO LO RELACIONADO A LAS COLISIONES */
       this.game.physics.arcade.overlap(this.ave,this.tubos,this.tocoTubo,null,this);
        /***********************TODO LO RELACIONADO A LOS PUNTAJES */
        console.log(puntos);
        
        
        

    },
    crearTubo:function(){
        let separacionRandom=Math.floor(Math.random()*125);
        let orientacion=0;
        if(Math.random()*2<1){orientacion=-1}else{orientacion=1;}
         const SEPARACIONBASICA=275 + Math.floor(Math.random()*20);
        
         const altoAve=(this.ave.height)*1.5; 
         const Total=SEPARACIONBASICA +altoAve;
        

         this.tubo1=this.game.add.sprite(this.game.width+50,this.fondo.height+Total+separacionRandom*orientacion,"tubo");
         this.tubo1.anchor.setTo(0.5,1);
         this.game.physics.arcade.enable(this.tubo1);
         this.tubo1.body.velocity.x=-60;
      
        this.tubo2=this.game.add.sprite(this.game.width+50,0-Total+separacionRandom*orientacion,"tubo");
         this.tubo2.anchor.setTo(0.5,1);
         this.tubo2.scale.y*=-1;
         this.game.physics.arcade.enable(this.tubo2);
         this.tubo2.body.velocity.x=-60; // recuerda que avanza a pixeles por segundo en cambio el fondo avanza a fps
         //agregamos al grupo
         this.tubos.add(this.tubo1);
         this.tubos.add(this.tubo2);


    },

   
    saltar:function(){
       
        this.ave.body.velocity.y=-350;
      // this.game.add.tween(this.ave).to({angle:-20},100).start();
        this.game.add.tween(this.ave).to({angle:-20},100,null,true);
    },
    tocoTubo:function () {
        if(this.ave.alive==false){
            return;
        }else{
            this.ave.alive=false;
            
            console.log("tocaste un tubo");
            this.tubos.forEachAlive(function(t){
                t.body.velocity=0;
            });
            this.game.time.events.remove(this.repeticion);
            this.game.state.pause();
            this.game.state.start("GameOver");
            

        }
      }
}

EL ESTADO GAME OVER

GameOver es el último estado de nuestro juego, en el debe de mostrarse el puntaje obtenido, así como los textos y botones que permitan volver al estado Menu, este estado no difiere mucho de Menu, es así que podemos hacer un copiar y pegar y haremos algunas modificaciones:

let GameOver={
    init:function(){
        this.game.scale.scaleMode=Phaser.ScaleManager.SHOW_ALL;
        this.game.scale.pageAlignHorizontally=true;
        this.game.scale.pageAlignVertically=true;
    },

    preload:function(){
        this.game.stage.backgroundColor="#fff";
        this.game.load.image("boton","./recursos/sprites/boton1.png"); 
    },
    create:function(){
        
        this.botonPrincipal=this.game.add.button(this.game.width/2,this.game.height/2,"boton",this.ReiniciarJuego,this);
        this.botonPrincipal.anchor.setTo(0.5);
        this.botonPrincipal.scale.setTo(0.25);
        
        this.textoInicio1=this.game.add.text(this.game.width/2,this.game.height/2-125,"Juego Terminado",{font:"bold 24px sans-serif",fill:"black",align:"center"});
        this.textoInicio2=this.game.add.text(this.game.width/2,this.game.height/2-155,"Flappy Bird",{font:"bold 30px sans-serif",fill:"black",align:"center"});
        this.puntosTexto=this.game.add.text(this.game.width/2,this.game.height/2-95,"Puntos:"+puntos.toString(),{font:"bold 24px sans-serif",fill:"black",align:"center"});
        this.puntosTexto.anchor.setTo(0.5);
        this.textoInicio1.anchor.setTo(0.5);
        this.textoInicio2.anchor.setTo(0.5);

    },
    ReiniciarJuego:function(){
        this.game.state.start("Menu");
    }
}

 

Revisando el código, los métodos init y preload son los mismos que el del estado Menu, podríamos variar la imagen que se carga, pero por el momento esto está bien, en create sucede casi similar, hemos agregado el mismo botón solo que ahora hace un llamado al método ReiniciarJuego, en el cual hemos de llamar al estado Menu. Por último hacemos observación en la siguiente sentencia:

this.puntosTexto=this.game.add.text(this.game.width/2,this.game.height/2-95,"Puntos:"+puntos.toString(),{font:bold 24px sans-serif",fill:"black",align:"center"});

Los puntos (la variable global declarada en el archivo main.js), son agregados directamente al texto del objeto a través de la función toString(), al ser global aseguramos que esta variable pueda ser llamada por otros estados, con eso estaría finalizado la configuración básica de nuestro juego, es observable a través del celular o de cualquier otro dispositivo.