Cómo Crear un Juego Móvil con HTML5 y Cordova

En un mundo donde el tiempo se ha vuelto escaso ya nadie tiene tiempo para cuidar ni darle de comer a una mascota. Por alguna extraña razón (los humanos somos un poco raros) se inventaron las virtual petses decir, aplicaciones que simulan una mascota de verdad y permiten cuidarla y darle de comer. Todo desde la comodidad de tu móvil.

Dada la popularidad de este género, que comenzó como llaveritos "Tamagochi" y evolucionó a mega-éxitos de Android como Pou, pensé que sería interesante crear un tutorial de mascota virtual para enseñar desarrollo de videojuegos con HTML5 y Cordova, utilizando la librería de juegos de HTMLdesarrollo de videojuegos con HTML5 y Cordova, to cover the basics of the Open Source Phaser, la cual es gratis y de código abierto.

HTML5 es una de mis áreas de especialización, y este tutorial está pensado como punto de partida para desarrolladores que nunca han creado juegos pero que quieren empezar a crear sus propios juegos móviles multi-plataforma. Pueden obtener más información sobre mis recursos educativos en mi sitio web Zenva y también pueden visitar mi curso online gratuito de Desarrollo de Juegos en HTML5.(en inglés) en mi plataforma de cursos Zenva Academy.

Mis otros tutoriales de Phaser:

Código Fuente del Tutorial

Descarga el código de este tutorial en un archivo ZIP acá. Puedes también clonarlo de Github.

Si quieres ver el producto terminado puedes verlo acá.

¿De dónde salieron las imágenes de este juego?

Todo el arte de este juego fue creado por mi Zenva y lo puedes utilizar en proyectos comerciales y no comerciales, no es necesaria la atribución. (aunque si creas el próximo Candy Crush no me quejo si me compras un Ferrari :)

Metas de Aprendizaje

En este tutorial vas a aprender a crear un juego sencillo de mascota virtual para Android (aunque exportable a otras plataformas también) utilizando la librería de juegos en HTML5 Phaser y Cordova

Luego de completar este tutorial estarás familiarizado (espero!) con lo siguiente:

  • Trabajo con sprites, animaciones, tweens y "estados" en Phaser.
  • Recepción de input del usuario en Phaser.
  • Creación de un juego sencillo de mascota virtual.
  • Cómo agregar Cordova en tu proyecto y utilizar el plugin de vibración del teléfono.
  • Testeo de tu juego en un móvil utilizando el Intel XDK.
  • Cómo construir tu aplicación para Android con y sin Crosswalk.

Aplicaciones Híbridas

Antes de mencionar los requerimientos de este tutorial y el entorno de desarrollo me gustaría hablar sobre las APP híbridas.

Una aplicaciones web común y corriente está hecha con HTML, CSS y JavaScript, más un backend o lado del servidor. Para abrirla, basta un navegador.

Una aplicación híbrida es una aplicaciones web empaquetada como aplicación nativa en un "webview". Un webview es un componente nativo (de Android, iOS y otras plataformas) que permite mostrar HTML en el teléfono. Cordova es una libraría de código abierto que nos permite empaquetar nuestros juegos y apps de HTML5 en plataformas nativas utilizando este enfoque. Cordova nos entrega además la posibilidad de acceder a muchas API nativas del teléfono, como la cámara, acelerómetro, sistema de archivos y mucho más, de manera que podamos utilizar estas funcionalidades en nuestros juegos y apps de HTML5.


Image: (CC) Laig- http://commons.wikimedia.org/wiki/File:Roti_Wrap_Trafasie.jpg

Durante el transcurso del tiempo, algunas de estas APIs se han vuelto estándares reconocidos por el W3C y son soportados por los principales navegadores (sin tener que incluir Cordova).

Mediante la creación de plugins de Cordova, se puede extender e incluir prácticamente cualquier característica nativa en nuestras apps de HTML5.

¿Cordova o Phonegap?

Esta es una pregunta que escucho muy seguido y quiero aclararlo antes de continuar. El proyecto original se llamaba Phonegap, luego la empresa Adobe compró a la empresa que estaba creando este producto, y se quedó con los derechos de la marca registrada Phonegap. El proyecto de código fuente pasó a la fundación Apache y se cambió de nombre a Cordova. Phonegap hoy en día no es nada más que Cordova + algunas configuraciones predeterminadas + servicios adicionales en la nube que ofrece Adobe.

En este tutorial hablaré sólo de Cordova, pero todo aplica también para Phonegap (porque es lo mismo!).

(No, no pasó a llamarse Cordova porque los desarrolladores eran de Argentina. Se llamó de esta manera porque es el nombre de la calle donde tenían su oficina).

Requisitos de este Tutorial

Este tutorial asume conocimientos básico-intermedios de JavaScript. Si no sabes JavaScript o sientes que necesitas un reforzamiento puede ver nuestro curso online en Zenva Academy Cómo Programar para Emprendedores - JavaScript que cubre

No se necesita de experience previa con Phaser, desarrollo de juegos, Cordova ni Android.p>

Los juegos creados con Phaser no se puede ejecutar simplemente haciendo doble click en el archivo index.html. Para ejecutarlos necesitas de un servidor local. Si dejas de lado el uso de Cordova, te basta cualquier alternativa de servidor web: WAMP para Windows , MAMP para Mac , Python simple HTTP server o el paquete http-server de Node.js.

El juego que construiremos utiliza el plugin de Vibración de Cordova de manera que el teléfono vibre cuando hacemos girar a la mascota virtual. Cuando corras este ejemplo en tu computador éste no vibrará, pero el juego no se caerá y funcionará bien, por lo que puedes correrlo desde cualquier navegador.

Para correr juegos que utilicen Cordova, éstas son algunas alternativas:

  • El Intel XDK viene con Cordova, el emulador Ripple Emulator y herramientas para testear en el teléfono.
  • El Ripple Emulator se puede descargar por separado como extensión de Chrome.
  • Si instalas el Android SDK y la línea de comandos de Cordova puede utilizar el emulador y el resto de las herramientas de Android.

En este tutorial vamos a utilizar el Intel XDK ya que nos permite emular, construir y testear en el teléfono sin tener que instalar SDKs o líneas de comando. El XDK también se puede utilizar como editor de código ya que incluye el editor Brackets. En mi caso yo prefiero desarrollar con Sublime Text.

Desarrollo de Juegos con HTML5

A lo largo de la historia humana, cada vez que aparece una nueva tecnología (la electricidad, los computadores, Internet) siempre habrá alguien con mucho tiempo libre que creará un juego con esta nueva tecnología. HTML5 por supuesto no fue la excepción.

Se pueden crear juegos sin ninguna librería adicional, utilizando solamente HTML y JavaScript, pero este camino es recomendado sólo si quieres crear una librería de juegos en vez de un juego. La mayoría de las cosas necesarias para crear un juego (mostrar imágenes, animar, mover una imagen, input del usuario, etc) ya han sido empaquetas en cómodas librerías. Quienes hemos participado en el desarrollo de estos frameworks nos aseguramos que todo lo anterior funcione para que tú te concentres en tu juego y no en las herramientas.

En un mundo ideal los frameworks de juegos se podrían tratar como una "caja negra", es decir ignorar su interior. Pero en el mundo real si vas a crear juegos de HTML5, debes entender cómo fuciona tu framework.

Si te familiarizas con el código fuente de la librería que estás utilizando, tendrás una enorme ventaja al momento de trabajar en tus juegos. Como estamos hablando de última tecnología, no todo está en Google ni en Stack Overflow. Aprender cómo funciona tu framework te ahorrará mucho tiempo y dolores de cabeza.

Algunos frameworks populares, gratis y de código abierto son Phaser, Quintus, MelonJs, Crafty, Babylon.js para juegos en 3D. Yo utilizaré Phaser en este tutorial porque es adecuado tanto para principiantes como para usuarios más avanzados. Phaser tiene una de las communities para juegos en 3D. Yo utilizaré Phaser en este tutorial porque es adecuado tanto para principiantes como para usuarios más avanzados. Phaser tiene una de las

Antes de Comenzar - Ten todo esto listo!

Como siempre, estoy transformando un simple tutorial en un ebook de proporciones faraónicas, pero así me gusta escribir así que resumamos todo lo que he mencionado hasta ahora. Ten lo siguiente listo para comenzar:

Todo Comenzó con un Hola Mundo

Nuestro archivo index.html, punto de entrada al proyecot:

<!DOCTYPE html>
 
<html>
 
            <head>
 
                        <meta charset="utf-8" />
 
                        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
 
                        <title>Learn Game Development at ZENVA.com</title>
 
                        <script src="js/phaser.js"></script>             
 
                        <style>
 
                          body {
 
                            padding: 0px;
 
                            margin: 0px;
 
            background-color: black;
 
                          }
 
                          </style>
 
            </head>
 
            <body>  
 
                        <script src="js/main.js"></script>
 
            </body>
 
</html>

phaser.js es la librería que descargaste de Github o la que venía con el proyecto. Puedes usar también la versión minificada (.min.js). Yo prefiero desarrollar con la versión no minificada para familiarizarme con el código fuente

main.js es el punto de entrada de nuestro juego, veamos una primera versión de este archivo:

 
//this game will have only 1 state
 
var GameState = {
 
  //load the game assets before the game starts
 
  preload: function() {
 
  },
 
  //executed after everything is loaded
 
  create: function() {
 
  },
 
  //game loop, executed many times per second
 
  update: function() {
 
  }
 
};
 
//initiate the Phaser framework
 
var game = new Phaser.Game(360, 640, Phaser.AUTO);
 
game.state.add('GameState', GameState);
 
game.state.start('GameState');
 
Let's go step by step:
 
1) We create a new Game object like so:
 
var game = new Phaser.Game(360, 640, Phaser.AUTO);

El objeto Phaser está disponible en nuestro ámbito ya que incluímos phaser.js. Cuando creamos un nuevo juego podemos definir el ancho, alto y el renderer que se va a utilizar para mostrar el juego en pantalla.

Phaser utiliza una librería llamada Pixi.js que permite renderizar en WebGL, y si el navegador no lo soporta, utiliza CANVAS. En nuestro caso dejamos esto como automático para que se intente utilizar WebGL y si no está presente, Canvas.

2) Necesitamos de un estado para nuestro juego.

game.state.add('GameState', GameState);

Un estado en Phaser es un tipo de objeto de JavaScript que tiene ciertos métodos necesarios para el flujo del juego. Los métodos principates son los siguientes (puedes ver el resto en la documentación del objeto State):

  • preload() en esta método se cargan los activos del juego (imágenes, sonidos, archivos JSON, etc) Se puede mostrar una barra de progreso y definir otras configuraciones. En mi tutorial Sidescroller Game en HTML5 Hub implementé una barra de progreso.
  • create()este método se ejecuta cuando los activos del juego han sido cargados. Acá es donde el juego comienza oficialmente.
  • update() este método es llamado hasta 60 veces por segundo. Todo lo que pongas acá se ejecutará entonces muchas veces! acá se evalúan condiciones tales como la existencia de colisiones, input del usuario, sprites en algún lugar del nivel, etc

En el código anterior, se creó un objeto llamado GameState con estos métodos predeterminados, y se le asignó al juego (Phaser.Game).

¿Por qué hay que pre-cargar las imágenes y otros archivos?

El proceso de precargado lo que hace es leer estos archivos del disco y cargarlos en la memoria RAM. Leer por ejemplo una imagen desde la memoria RAM es mucho más rápido que hacerlo desde el disco. Cuando necesitas mostrar un enemigo o un personaje en tu juego, quieres que se muestre de manera inmediata. Si cada vez que necesitamos algo lo tenemos que cargar del disco nuestro juego va a funcionar muy lento. Piensa en la página de Facebook cuando no todo se carga y varios elementos aparece como que se están cargando. Algo así en un juego sería inaceptable. Por eso pre-cargamos los archivos!

3) Iniciar nuestro estado:

game.state.start('GameState');

Esto va a llamar al método preload del estado GameState.

Más Sobre los "Estados" en Phaser

En mi tutorial Sidescroller Gamepublicado en el sitio HTML5 Hub muestro un ejemplo donde se tienen muchos estados para organizar mejor el código del juego.

Desafío: Incorpora una barra de progreso en este juego!

Pre-cargado de Activos o "Assets"

Ahora vamos a cargar algunas imágenes en nuestro método preload. El objeto que realiza el cargado de assets se llama Loader y está disponible en nuestro objeto juego (Phaser Game):

//load the game assets before the game starts
 
  preload: function() {
 
    this.game.load.image('backyard', 'assets/images/backyard.png');   
 
    this.game.load.image('apple', 'assets/images/apple.png');   
 
    this.game.load.image('candy', 'assets/images/candy.png');   
 
    this.game.load.image('rotate', 'assets/images/rotate.png');   
 
    this.game.load.image('toy', 'assets/images/rubber_duck.png');   
 
    this.game.load.image('arrow', 'assets/images/arrow.png');   
 
  },
 

Estamos cargando todas las imágenes y a cada imagen le asignamos una "clave" única. Cuando nos referimos a una imagen en el código utilizamos estas claves (propiedad "key"). Por ejemplo para referirnos a "rubber_duck.png" utilizaremos la clave "toy".

Si abres las imágenes que estamos cargando, notarás un archivo llamado pet.png (que no hemos cargado aún). Esta imagen contiene un spritesheet que incorpora varios "cuadros" para nuestra mascota. El objeto Loader tiene un método llamado spritesheet que nos permite cargar este tipo de imágenes, para poder luego trabajar con cuadros individuales o crear animaciones. Agreguemos lo siguiente en nuestro método preload

this.load.spritesheet('pet', 'assets/images/pet.png', 97, 83, 5, 1, 1);
 

Ver la documentation para mayor información sobre los parámetros.

Sprites

Agreguemos nuestra primera imagen. Vamos a crear un nuevo Sprite en nuestro método create:

this.background = this.game.add.sprite(0,0, 'backyard');

Al recargar la página debería mostrarse la imagen del fondo:

El sistema de coordenadas en Phaser (y también cuando trabajamos directamente en el Canvas o en WebGL) tiene su origen en el punto superior izquierdo. X es positivo hacia la derecha e Y es positivo hacia abajo. Cuando posicionamos un sprite en un cierto punto x, y, la parte del sprite que va a en ese punto será su esquina superior izquierda. La esquina superior izquierda es el punto de anclaje ("anchor point") predeterminado de un sprite.

Lo que hicimos en el código de arriba fue posicionar el fondo del juego en la coordena (0, 0), de manera que la esquina superior izquierda del fondo fue posicionada en la esquina superior izquierda de la pantalla.

Adaptando para Móvil

La imagen del fondo se muestra en este momento del mismo tamaño sin important el tamaño de la ventana del navegador. Para adaptar el juego a distintos tamaños de pantalla Phaser nos da a acceso a un objeto llamado ScaleManager que provee diversas opciones de escalamiento de pantalla. Hay 4 modalidades de escalamiento, una de las cuales fue agregada hace poco.

La figura a continuación muestra los distintos modos para escalar el juego (la imagen utilizada corresponde a mi juego RPG retro Huungree, hecho en HTML5 y disponible gratis para iOS y Android, si quieres aprender sobre el proceso de desarrollo de este juego ve este video).

En nuestro ejemplo vamos a utilizar el modo SHOW_ALL, el cual ajusta tanto el ancho como el alto del juego (sin perder la proporción) de manera de ocupar el máximo espacio disponible en la pantalla. El código a continuación va al principio del método create. Además de fijar esta opción para la escala del juego, nos vamos a asegurar que el juego se muestre centrado tanto vertical como horizontalmente, y que el tamaño se ajuste en la medida que cambie el tamaño de la ventana.

//scaling options
 
    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
 
    //have the game centered horizontally
 
    this.scale.pageAlignHorizontally = true;
 
    this.scale.pageAlignVertically = true;
 
    //screen size will be set automatically
 
    this.scale.setScreenSize(true);

Al recargar el juego deberías ver la misma imagen de antes, pero ahora ajustada al tamaño de la ventana.

La Mascota

Ahora vamos a agregar nuestra mascota. Al tratarse de un spritesheet, además de especificar la clave de la imagen debemos especificar el cuadro a mostrar (en este caso el primero, o sea 0), contando de izquierda a derecha

this.pet = this.game.add.sprite(100, 400, 'pet',0);

Al posicionar la mascota en el nivel, quiero posicionarla de acuerdo a su centro, no la esquina superior izquierda (punto de anclaje predeterminado). Por lo que debemos modificar el punto de anclaje de este sprite:

this.pet.anchor.setTo(0.5, 0.5); //it refers to the half in x and y, you can also do this.pet.anchor.setTo(0.5)

Cada vez que la mascota coma algo o tome un juguete vamos a animarla. En tus juegos puedes tener muchos tipos de animaciones, en este caso tendremos sólo una. La animación se llamará "funnyfaces" y consistirá de los cuadros 0, 1, 2, 3, luego de vuelta hasta el 0. El cuadro 4 lo utilizaremos para cuando la mascota muere.

Primero definimos la animación y le damos una clave única (en este caso "funnyfaces"):

this.pet.animations.add('funnyfaces', [0, 1, 2, 3, 2, 1, 0], 7, false);

El arreglo especifica los cuadros, el "7" es la duración de cada cuadro en milisegundos. El último parámetro es para espeficar si se trata de una animación que se repite infinitamente o no. Más info en la documentación de la clase Animation

Para mostrar la animación puedes simplemente usar el método play:

this.pet.animations.play('funnyfaces');

Ve si funciona! luego bórralo ya que no queremos animar la mascota todo el tiempo.

Arrastrando a la Mascota

Para permitirle al jugador arrastrar la mascota tenemos que activar el input del usuario en el sprite de la mascota, luego habilitar arrastrado:

//draggable pet this.pet.inputEnabled = true; this.pet.input.enableDrag();

Ahora deberías poder arrastrar la mascota.

Botones

Vamos a implementar 4 botones como se muestra en la imagen a continuación. Las imágenes ya fueron cargadas en el método preload. Vamos a escuchar eventos del mouse o la pantalla táctil sobre ellas, de manera de poder seleccionarlas. Cuando un botón ha sido seleccionado, si hacemos click en el fondo del nivel, se debería crear un nuevo elemento (ejemplo: toco la manzana, luego toco el fondo, eso debería crear una manzana). Cuando seleccionamos una imagen, vamos a cambiar su valor "alfa" o de transparencia para darle un pequeño efecto de selección.

El único botón que funcionará de manera distinta son las flechas, las cuales serán usadas para rotar la mascota y hacer vibrar el teléfono.

Vamos a crear un sprite para cada botón. Agregar esto luego de la definición de la mascota.

    //buttons
 
    this.apple = this.game.add.sprite(72, 570, 'apple');
 
    this.apple.anchor.setTo(0.5);
 
    this.apple.customParams = {health: 20};
 
    this.apple.inputEnabled = true;
 
    this.apple.events.onInputDown.add(this.pickItem, this);
 
    this.candy = this.game.add.sprite(144, 570, 'candy');
 
    this.candy.anchor.setTo(0.5);
 
    this.candy.customParams = {health: -10, fun: 10};
 
    this.candy.inputEnabled = true;
 
    this.candy.events.onInputDown.add(this.pickItem, this);
 
    this.toy = this.game.add.sprite(216, 570, 'toy');
 
    this.toy.anchor.setTo(0.5);
 
    this.toy.customParams = {fun: 30};
 
    this.toy.inputEnabled = true;
 
    this.toy.events.onInputDown.add(this.pickItem, this);
 
    this.rotate = this.game.add.sprite(288, 570, 'rotate');
 
    this.rotate.anchor.setTo(0.5);
 
    this.rotate.inputEnabled = true;
 
    //this.rotate.events.onInputDown.add(this.rotatePet, this);   keep this commented out until we add rotatePet()
 
    this.buttons = [this.apple, this.candy, this.toy, this.rotate];
 
    //nothing selected
 
    this.selectedItem = null;
 

Para cada botón lo que hacemos es:

  • Crear un nuevo sprite.
  • Fijar el punto de anclaje en el centro del sprite, en vez de la esquina superior-izquierda.
  • Agregamos un objeto llamado "customParams" que utilizaremos para guardar parámetros y valores que tienen un signficado dentro del juego. Ésta no es una característica de Phaser, sino una implementación propia para llevar estos valores sin que se confundad con propiedades propias del framework.
  • Habilitamos input del usuario en el sprite.
  • Definimos un método que será llamado cuando el usuario haga click o toque el sprite. El método será pickItem para los elementos y rotatePet para el giro.

Vamos a guardar los botones en un arreglo llamado "buttons", para así poder iterar por todos los botones más adelante.

Para llevar cuenta de la selección actual utilizaremos this.selectedItem.

Recolectando Items

Cuando hago click en un item llamamos al método pickItem. Vamos a implementar ahora este método en nuestro estado GameState (podemos agregar todos los métodos que queramos en nuestros estados, está todo bien mientras no toquemos los nombres reservados como preload, create, etc).

  //pick an item so that you can place it on the background
 
  pickItem: function(sprite, event) {
 
    if(!this.uiBlocked) {
 
      //clear other buttons
 
      this.clearSelection();
 
      //alpha to indicate selection
 
      sprite.alpha = 0.4;
 
      //save selection so we can place an item
 
      this.selectedItem = sprite;
 
    }
 
  },
 

Este método es un controlador de evento llamado cuando el usuario hace click o toca la manzana, el dulce o el juguete. El primer parámetro nos entrega el sprite, el segundo parámetro nos entrega un objeto con la información del evento (coordenadas y otros, analízalo con console.log para ver qué más trae!).

this.uiBlocked es una variable que utilizaremos para determinar cuando la interfaz del usuario está bloqueada para nuestra interacciones. Por ejemplo mientras transcurre una animación bloqueamos los botones para que el usuario no pueda todavía seleccionar otro elemento.

Lo primero que hacemos es limpiar selecciones previas de botones. Luego, cambiamos el valor alfa (transparencia) del sprite que hemos seleccionado a 0.4. 0 significa 100% transparente (invisible!), 1 significa que no tiene transparencia. Por último, asignamos la selección a this.selectedItem.

El método clearSelection lo único que hace es cambiar las transparencias a 1, y remueve la asignación de this.selectedItem.

  //clear all buttons from selection
 
  clearSelection: function() {
 
    //set alpha to 1
 
    this.buttons.forEach(function(element){element.alpha = 1});
 
    //clear selection
 
    this.selectedItem = null;
 
  },
 

Posicionamiento de Elementos en el Fondo

Luego de la creación del sprite para el fondo del juego en el método create, vamos a habilitar el input del usuario y asignar un controlador de evento:

this.background.inputEnabled = true; this.background.events.onInputDown.add(this.placeItem, this);

El método placeItem va a obtener las coordenadas del evento y va a crear un nuevo sprite en esa ubicación. Más adelante vamos a hacer que la mascota se mueva hacia este elemento y "se lo coma".

//place selected item on the background
 
placeItem: function(sprite, event) {
 
    if(this.selectedItem && !this.uiBlocked) {
 
      //position of the user input
 
      var x = event.position.x;
 
      var y = event.position.y;
 
      //create element in this place
 
      var newItem = this.game.add.sprite(x, y, this.selectedItem.key);
 
      newItem.anchor.setTo(0.5);
 
      newItem.customParams = this.selectedItem.customParams;
 
}

Ahora deberías poder tocar un botón, luego tocar el fondo y el elemento se estaría creando (manzana, dulce o juguete).

Propiedades de la Mascota

Nuestra pequeña mascota tiene dos propiedades: salud (health) y diversión (fun). Si cualquiera de ambas llega a cero, la mascota muere y el juego se acaba.

Vamos a asignarle a la mascota un valor inicial para ambas. Agrega esto luego de la creación del sprite para la mascota:

//custom properties of the pet this.pet.customParams = {health: 100, fun: 100};

Las mascotas virtuales son criaturas demandantes de atención y cariño, y mueren si uno no las alimenta o las ignora. Vamos a hacer que la salud y diversión de nuestra mascota decrezcan con el tiempo. Vamos a crear un método llamado reduceProperties que será llamado cada 5 segundos y va a hacer que las propiedades de la mascota sean reducidas.

Pero antes que eso, es importante que podamos visualizar la salud y diversión actual de nuestra mascota. Agreguemos esto al final de nuestro método create para ver esta información en pantalla.

//stats
 
    var style = { font: "20px Arial", fill: "#fff"};
 
    this.game.add.text(10, 20, "Health:", style);
 
    this.game.add.text(140, 20, "Fun:", style);
 
    this.healthText = this.game.add.text(80, 20, "", style);
 
    this.funText = this.game.add.text(185, 20, "", style);
 
    this.refreshStats();

And add the refreshStats method as well:

//show updated stats values refreshStats: function() { this.healthText.text = this.pet.customParams.health; this.funText.text = this.pet.customParams.fun; }, 

Ahora podemos implementar un contador de manera que cada 5 segundos se reduzcan las propiedades de la mascota. Agregar lo siguiente al final de create:

//decrease health and fun every 10 seconds this.statsDecreaser = this.game.time.events.loop(Phaser.Timer.SECOND * 5, this.reduceProperties, this); this.statsDecreaser.timer.start();

Creamos un contador que se repite cada 5 segundos y llama al método reduceProperties. Guardamos el contador como propiedad en nuestro estado GameState para poder acceder a él y deterlo cuando queramos.

reduceProperties: function() { this.pet.customParams.health = Math.max(0, this.pet.customParams.health - 20); this.pet.customParams.fun = Math.max(0, this.pet.customParams.fun - 30); this.refreshStats(); },

Si recargas el juego ahora, verás que la salud y diversión de la mascota disminuyen cada 5 segundos.

Game Over

Si la salud o la diversión de la mascota llegan a cero, la mascota muere y es game over! el juego se recargará.

Siempre que queramos evaluar una condición "todo el tiempo" (en este ejemplo, quiero estar siempre atento a que la mascota esté viva o no) utilizamos el método update de nuestro estado, el cual se ejecuta hasta 60 veces por segundo. Cuando la mascota muere, cambiamos el cuadro del sprite al cuadro 4. Luego de dos segundos de muerta, reiniciamos el juego.

 

 //game loop, executed many times per second
 
  update: function() {
 
    if(this.pet.customParams.health <= 0 || this.pet.customParams.fun <= 0) {
 
      this.pet.customParams.health = 0;
 
      this.pet.customParams.fun = 0;
 
      this.pet.frame = 4;
 
      this.uiBlocked = true;
 
      this.game.time.events.add(2000, this.gameOver, this);
 
    }
 
  },
 
  gameOver: function() {   
 
    this.game.state.restart();
 
  },

Tomando Elementos y "Tweens"

Hasta ahora la mascota no tiene cómo sobrevivir, qué triste historia! Vamos a hacer de esto algo más positivo permitiendo que la mascota se alimente y se entretenga.

Cuando dejamos un elemento en el fondo, haremos que la mascota se mueva hacia él y lo "consuma", utilizando la animación "funnyfaces" que definimos antes. Las propiedades de la mascota serán actualizadas de acuerdo a lo que el elemento provee (o quita! por ejemplo el dulce quita salud).

Para implementar el movimiento de la mascota utilizaremos un tween. Los tweens nos permiten crear transiciones para el valor de una o más propiedades de un sprite. La implementación de los tweens en Phaser's está basada en otra implementación de código abierto llamada tween.js en caso que quieras profundizar en este tema.

Al crear un tween, se definen las propiedades que cambiarán, la duración de la transición, y se puede especificar una función de devolución que se ejecutará al finalizar la animación.

Cuando el tween finalice, queremos que nuestra mascota muestre la animación funnyfaces y actualice sus propiedades. Ésta es la versión final de placeItem:

//place selected item on the background
 
  placeItem: function(sprite, event) {
 
    if(this.selectedItem && !this.uiBlocked) {
 
      //position of the user input
 
      var x = event.position.x;
 
      var y = event.position.y;
 
      //create element in this place
 
      var newItem = this.game.add.sprite(x, y, this.selectedItem.key);
 
      newItem.anchor.setTo(0.5);
 
      newItem.customParams = this.selectedItem.customParams;
 
      //the pet will move to grab the item
 
      this.uiBlocked = true;
 
      var petMovement = game.add.tween(this.pet);
 
      petMovement.to({x: x, y: y}, 700);
 
      petMovement.onComplete.add(function(){
 
        this.uiBlocked = false;
 
        //destroy item
 
        newItem.destroy();
 
        //animate pet
 
        this.pet.animations.play('funnyfaces');
 
        //update pet stats
 
        var stat;
 
        for(stat in newItem.customParams) {
 
          //make sure the property belongs to the object and not the prototype
 
          if(newItem.customParams.hasOwnProperty(stat)) {
 
            this.pet.customParams[stat] += newItem.customParams[stat];
 
          }
 
        }
 
        //show updated stats
 
        this.refreshStats();
 
        //clear selection
 
        this.clearSelection();
 
      }, this);
 
      petMovement.start();     
 
    }
 
  },
 

Ahora nuestra querida mascota se puede desplazar a los objetos que le dejamos, consumirlos y actualizar sus propiedades!

Nos falta implementar una sóla cosa, el botón giratorio que permitirá que la mascota gire y aumente su "diversión" (le agregaremos también que el teléfono vibre utilizando una API de Cordova).

Rotando a la Mascota

La rotación de la mascota ocurrirá en el método rotatePet. Asegúrate de no tener comentada la línea donde escuchamos al evento de input del usuario para el sprite llamado "rotate".

//rotate the pet
 
  rotatePet: function(sprite, event) {
 
    if(!this.uiBlocked) {
 
      this.uiBlocked = true;
 
      //alpha to indicate selection
 
      this.clearSelection();
 
      sprite.alpha = 0.4;
 
      var petRotation = game.add.tween(this.pet);
 
      petRotation.to({ angle: '+720' }, 1000);
 
      petRotation.onComplete.add(function(){
 
        this.uiBlocked = false;
 
        sprite.alpha = 1;
 
        this.pet.customParams.fun += 10;
 
        //show updated stats
 
        this.refreshStats();
 
      }, this);
 
      petRotation.start();
 
    }
 
  },
 

Como puedes ver, estamos nuevamente utilizando un tween, esta vez para el ángulo del sprite.

Nuestro demo está listo excepto por una cosa: quiero que el teléfono vibre cada vez que presionamos el botón de rotación. Para esto tendremos que incorporar Cordova en nuestro proyecto, lo cual nos dará acceso al API de vibración del teléfono.

Agregando Cordova y el Plugin de Vibración

Vamos a utilizar el Intel XDK para añadir Cordova, el plugin Vibration y para construir para Android. Otra alternativa al XDK vendría siendo la línea de comandos de Cordova junto con el SDK de Android.

Para habilitar el plugin de vibración en nuestro proyecto, ir a Project, luego bajar a Cordova 3.x Hybrid Mobile App Settings, luego Plugins and Permissions, ahí marcar la casilla para "Vibration Plugin".

Para utilizar Cordova hay que incorporar la librería cordova.js en nuestro archivos index.html. cordova.js es una "librería fantasma", no hay un archivo con ese nombre en nuestro proyecto! pero al incluir la referencia vamos a tener acceso a las API nativas del teléfono dentro de nuestro juego de HTML5. Asegúrate de agregar esto es en header de index.html:

<script src="cordova.js"></script>
 

¿No deberíamos escuchar el evento deviceready?

Si has trabajado antes con Cordova, sabrás o deberías saber que nunca se ejecuta código de Cordova sin haber escuchado antes al evento "deviceready", que se gatilla cuando la conexión con las API nativas está lista. Bueno, Phaser detecta que estás usando Cordova y se encarga de que el juego no comience mientras el evento deviceready no se haya gatillado. Por esto, no necesitas preocuparte de escuchar este evento con Phaser y Cordova.

Testeando el Juego en el Teléfono (Android)

En el panel "Test" del XDK puedes subir tu juego a la nube y luego cargarlo en tu teléfono si descargas la aplicación Intel App Previewdesde la Playstore e ingresa con las mismas credenciales de Intel que estás usando en el XDK.

Antes de agregar el código para la vibración, asegúrate de poder correr el juego en el Intel App Preview (si esto no te funciona, puedes utilizar su foro oficial de soporte)

Haciendo que el Teléfono Vibre

Vamos a usar una API nativa para terminar nuestro juego. En rotatePet, luego de asignar el valor alfa de 0.4, agrega lo siguiente:

//vibrate device if present if(navigator.vibrate) { navigator.vibrate(1000); }
 

Ahora prueba el juego nuevamente en tu teléfono y voila! debería vibrar al rotar la mascota. Así de fácil es!

Construyendo en Android

Para construir para Android con el XDK tienes dos opciones: con y sin Crosswalk. Puedes acceder a ambas modalidades desde el panel "Build" en el XDK.

Para explicar cómo funciona el proceso con Crosswalk voy a explicar primero cómo funciona sin Crosswalk: los teléfonos Android tienen un componente nativo "webview" que permite ejecutar apps y juegos de HTML5. Cuando construyes una app con Cordova sin Crosswalk, el webview existente en el teléfono es utilizado. El problema de este enfoque es que distintas versiones de Android y teléfonos tienen distintas implementaciones de estas "webviews". Algunas veces las páginas no se ven iguales, o hay funcionalidades de HTML5 que no funcionan en todas las versiones de Android. Esto es un dolor de cabeza para los desarrolladores.

Cuando utilizas Crosswalk no instalas solamente la app de HTML5 sino además un webview basado en Google Chrome (o mejor dicho su versión de código abierto Chromium), por lo que uno, tienes la misma webview en todas partes, y segundo, cuentas con una webview bastante actualizada y con buen soporte. Si el día de mañana sale una nueva versión de Android tus aplicaciones no dejan de funcionar ya que tienes el control de la webview que se está utilizando.

Como todo en la vida, hay ventajas y desventajas de cada enfoque. Si no usas Crosswalk tu juego puede ser muy liviano, lo cual es una gran ventaja en la Playstore. Cuando usas Crosswalk, estás instalando la webview de Chromium además del juego o app. Por ejemplo, el archivo APK de nuestro ejemplo de mascota virtual (sin ningún esfuerzo por minifcar el código) pesa 837 kb sin Crosswalk, mientras que con Crosswalk pesa 20.5 MB y 17.7 MB (versiones para sistemas x86 y arm).

Conclusiones

Hemos cubierto un buen trecho, partiendo desde casi cero y terminando con un demo de aplicación híbrida utilizando Phaser y Cordova. Espero hayamos roto el hielo con estas tecnologías!

Siéntete libre de agregarle más funcionalidades al juego y publicarlo! comparte en la zona de comentarios cualquier cosa que le hayas agregado.

Espero que les haya gustado este tutorial. Pueden contactarme y también revisen mi curso gratis de desarrollo de juegos de HTML5 (en inglés) Intro to HTML5 Game Development at Zenva Academy.

Biografía del autor

Pablo Farías Navarro es un educador, desarrollador y emprendedor web apasionado por las nuevas tecnologías. Como founder de Zenva (https://zenva.com), Pablo está 100% enfocado a crear contenido sobre desarrollo web, móvil y de videojuegos, siendo HTML5 su tecnología favorita.

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.

2 comentarios

Inicio
Imagen de jose r.

hola est es mi primer juego espero que les guste

Imagen de Ivan Eduardo H.

Buenas noches, primero que nada muchas gracias por el tiempo que se tomo en crear y subir este tutorial que es de gran ayuda para los que recien empezamos en esto.

Mi duda es como puedo agregar mas sprites a mi mascota, para que haga una cosa diferente cada que tome cierto objeto.

Gracias por su atencion.

Agregue un comentario

¿Tiene alguna pregunta técnica? Visite nuestros foros. ¿Tiene problemas con el sitio o con productos de software? Comuníquese con la asistencia.