Advertisement
  1. Web Design
  2. HTML/CSS
  3. HTML

Introducción a Phaser: Creación de "Monster Wants Candy"

Scroll to top
Read Time: 26 min

() translation by (you can also view the original English article)

En este extra-largo tutorial, voy a quebrar la fuente de Monster Wants Candy, un juego multi-plataforma que mi colega y yo construimos con Phaser, el motor de juego HTML5. De esta forma, obtendrás una introducción práctica al motor y aprenderás conceptos que puedes usar para crear tus propios juegos para móviles y navegadores HTML5.

Introducción

Si desea crear juegos HTML5, es bueno elegir un marco o motor. Usted podría, por supuesto, hacerlo en JavaScript, pero el uso de un marco acelera enormemente el desarrollo, y se encarga de las cosas que no son tan emocionantes, pero que tienen que hacerse.

Phaser es un nuevo y popular marco de desarrollo de juegos HTML5 con una comunidad dedicada, así que si aún no lo has oído, ¡definitivamente deberías probarlo!

Artículos Relacionados

¿Qué es Phaser?

Phaser es un marco para crear juegos de escritorio y móviles HTML5. Fue creado por Photon Storm. El marco está escrito en JavaScript puro, pero también contiene archivos de definición TypeScript, en caso de que estes en eso.

El código Phaser está basado en la plataforma Flash Flixel, por lo que los desarrolladores de Flash pueden sentirse como en casa. Bajo el capó, utiliza el motor Pixi.js para hacerse cargo de todo en la pantalla usando Canvas, o WebGL si es posible.

Phaser logoPhaser logoPhaser logo

Es bastante nuevo, pero crece rápido rápidamente con la ayuda de la comunidad activa en los foros HTML5GameDevs. Ya hay muchos tutoriales y artículos disponibles, y también puede consultar la documentación oficial y una gran colección de ejemplos que pueden ser muy útiles durante el desarrollo. Es de código abierto y libremente disponible en GitHub, por lo que puede mirar directamente en el código fuente y aprender de el.

La última versión estable de Phaser, en el momento que se escribe este articulo, es la versión 2.0.7.

¿Qué es Monster Wants Candy?

Cuando empiezo a trabajar en un juego, pienso primero en la idea central y trato de configurar rápidamente un prototipo funcional. En este estudio de caso, comenzamos con una demostración bastante simple de un juego llamado Monster Wants Candy.

En lugar de trabajar desde un prototipo, te mostraré la estructura del proyecto primero, para que puedas entender toda la idea. Seguiremos los pasos cronológicos de nuestro juego: desde cargar los activos hasta crear el menú principal y el bucle del juego real. Puedes ver la demostración de Monster Wants Candy ahora mismo para ver en qué estaremos trabajando juntos.

La codificación fue atendida por Andrzej Mazur de Enclave Games (¡que soy yo!), Y todos los activos gráficos fueron creados por Robert Podgórski de Blackmoon Design.

Monster Wants Candy demoMonster Wants Candy demoMonster Wants Candy demo

La historia de Monster Wants Candy es simple: un rey malvado ha secuestrado tu amor y tienes que recoger bastantes caramelos para recuperarla. El juego también es simple: los dulces están cayendo y puedes tocarlos para comerlos. Cuantos más puntos gane de comer el caramelo, mejor. Si te pierdes alguno y se caen de la pantalla, perderás una vida y el juego terminará.

Como puedes ver, es un juego muy sencillo, pero la estructura está completa. Encontrarás que el uso más importante del framework es para tareas como cargar imágenes, renderizar sprites y detectar la actividad del usuario. También es un buen punto de partida desde el cual puedes copiar el código, modificarlo y construir tu propio juego.

Configuración y estructura del proyecto

Puede leer este práctico artículo del propio autor del framework acerca de cómo empezar con Phaser o copiar el archivo phaser.min.js desde el repositorio GitHub en el directorio del proyecto y empezar a trabajar desde cero. No necesitas un IDE—simplemente puedes abrir el archivo index.html en tu navegador e instantáneamente ver los cambios que hiciste en el código fuente.

Nuestra carpeta de proyectos contiene el archivo index.html que incluye la estructura HTML5 y todos los archivos JavaScript necesarios. Hay dos subcarpetas: img, que almacena todos nuestros activos gráficos, y src, que almacena el código fuente del juego.

A continuación se muestra cómo se ve la estructura de carpetas:

Folder structure for Monster Wants Candy Phaser projectFolder structure for Monster Wants Candy Phaser projectFolder structure for Monster Wants Candy Phaser project

En la carpeta src, encontrará los archivos JavaScript—aquí es donde ocurre la magia. En este tutorial, describiré el propósito y el contenido de cada archivo de esa carpeta.

Puede ver el origen de cada archivo en el repositorio de GitHub para este tutorial.

index.html

Comencemos con el archivo index.html. Parece un sitio web HTML5 básico, pero en lugar de agregar el texto y muchos elementos HTML, inicializamos el marco de Phaser, que hará que todo sea un elemento Canvas.

1
<!DOCTYPE html>
2
<html>
3
<head>
4
  <meta charset="utf-8" />
5
	<title>Monster Wants Candy demo</title>
6
	<style> body { margin: 0; background: #B4D9E7; } </style>
7
	<script src="src/phaser.min.js"></script>
8
	<script src="src/Boot.js"></script>
9
	<script src="src/Preloader.js"></script>
10
	<script src="src/MainMenu.js"></script>
11
	<script src="src/Game.js"></script>
12
</head>
13
<body>
14
<script>
15
(function() {
16
	var game = new Phaser.Game(640, 960, Phaser.AUTO, 'game');
17
	game.state.add('Boot', Candy.Boot);
18
	game.state.add('Preloader', Candy.Preloader);
19
	game.state.add('MainMenu', Candy.MainMenu);
20
	game.state.add('Game', Candy.Game);
21
	game.state.start('Boot');
22
})();
23
</script>
24
</body>
25
</html>

Definimos la estructura habitual del documento HTML con el doctype y alguna información en el <head>: codificación del conjunto de caracteres, título de la página y estilo CSS. Por lo general, haríamos referencia al archivo CSS externo en el que ponemos todo el estilo, pero no lo necesitamos aquí—como ya he mencionado, todo se mostrará en un elemento Canvas, por lo que no tendremos ningún elemento HTML a estilar .

Lo último que debemos hacer es incluir todos nuestros archivos JavaScript: desde el archivo phaser.min.js con el código fuente del framework Phaser, hasta todos nuestros archivos que contengan el código del juego. Es bueno minimizar el número de solicitudes en el navegador combinando todos los archivos JavaScript en uno, para que su juego se cargue más rápido, pero para el propósito de este tutorial simplemente los cargaremos por separado.

Pasemos al contenido de la etiqueta <body>, donde inicializamos el framework y comenzamos nuestro juego. El código está dentro de una función de auto-invocación; la primera línea se ve así:

1
var game = new Phaser.Game(640, 960, Phaser.AUTO, 'game');

Este código inicializará Phaser con algunos valores predeterminados:

640 es el ancho de Canvas del juego en píxeles y 960 es la altura del juego.

Phaser.AUTO informa al framework cómo queremos que nuestro juego sea renderizado al Canvas. Hay tres opciones: CANVAS, WEBGL y AUTO. La primera ejecuta nuestro juego en el contexto 2D de la lona; El segundo usa WebGL para hacerlo donde sea posible (sobre todo el escritorio ahora mismo, pero el soporte móvil está mejorando); Y el tercero deja esta decisión al framework, que verificará si WebGL está soportado y decide si el juego se puede renderizar en este contexto—si no es así, se utilizará el renderizado 2D Canvas.

La inicialización del marco se asignará al objeto único denominado game, que usaremos al referenciar la instancia de Phaser.

Las siguientes líneas tratan de agregar estados a nuestro juego:

1
game.state.add('Boot', Candy.Boot);

'Boot' es un nombre de estado y Candy.Boot es un objeto (definido en los siguientes pasos) que se ejecutará cuando iniciemos ese estado. Estamos agregando estados para Boot (configuración), Preloader (carga de activos), MainMenu (lo has adivinado, el menú principal de nuestro juego) y Game (el bucle principal del juego). La última línea, game.state.start ('Boot'), Inicia el estado de Boot, para que se ejecute la función correcta del objeto Candy.Boot.

Como se puede ver, hay un objeto de juego JavaScript principal creado, con muchos otros asignados dentro para fines especiales. En nuestro juego tenemos los objetos Boot, Preloader, MainMenu y Game que serán nuestros estados de juego, y los definiremos usando sus prototipos. Hay algunos nombres de funciones especiales dentro de los objetos reservados para el propio framework (preload(), create(), update() y render()), pero también podemos definir nuestro propio (startGame(), spawnCandy(), ManagePause()). Si no está seguro de entender todo esto, entonces no se preocupe—explicaré todo usando los ejemplos de código más adelante.

El juego

Olvidémonos de los estados Boot, Preloader y MainMenu por ahora. Se describirán en detalle más adelante; Todo lo que tienes que saber en este momento es que el estado Boot se encargará de la configuración básica del juego, Preloader cargará todos los elementos gráficos, y MainMenu le mostrará la pantalla donde podrá iniciar el juego.

Vamos a centrarnos en el juego en sí y ver cómo se ve el código del estado Game. Antes de pasar por todo el código Game.js, sin embargo, vamos a hablar sobre el concepto del juego en sí y las partes más importantes de la lógica desde el punto de vista de un desarrollador.

Modo Retrato

El juego se juega en modo retrato, lo que significa que el jugador tiene su móvil verticalmente para jugar.

Rotate device promptRotate device promptRotate device prompt

En este modo, la altura de la pantalla es mayor que su ancho—a diferencia del modo horizontal, donde el ancho de la pantalla es mayor que su altura. Hay tipos de juegos que funcionan mejor en modo retrato (como Monster Wants Candy), tipos que funcionan mejor en modo horizontal (incluyendo juegos de plataformas como Craigen), e incluso algunos tipos que funcionan en ambos modos, aunque suele ser mucho más difícil codificar tales juegos.

Game.js

Antes de pasar por el código fuente del archivo game.js, hablemos de su estructura. Hay un mundo creado para nosotros, y hay un personaje del jugador dentro de cuyo trabajo es agarrar el caramelo.

Mundo del juego: El mundo detrás del monstruo es estático. Hay una imagen de la Candyland en el fondo, podemos ver el monstruo en primer plano, y también hay una interfaz de usuario.

Personaje del jugador: Esta demo es intencionalmente muy sencilla y básica, por lo que el pequeño monstruo no está haciendo nada aparte de esperar el caramelo. La tarea principal para el jugador es recoger el caramelo.

Candy: El mecánico principal del juego es atrapar el mayor número de caramelos posible. Los dulces se generan en el borde superior de la pantalla, y el jugador debe tocar (o hacer clic) en ellos, ya que están cayendo. Si cualquier caramelo cae de la parte inferior de la pantalla, se elimina y el personaje del jugador recibe daño. No tenemos un sistema de vida implementado, así que después de eso el juego termina instantáneamente y se muestra el mensaje apropiado.

Bueno, veamos la estructura de código de nuestro archivo Game.js ahora:

1
Candy.Game = function(game) {
2
	// ...

3
};
4
Candy.Game.prototype = {
5
	create: function() {
6
		// ...

7
	},
8
	managePause: function() {
9
		// ...

10
	},
11
	update: function() {
12
		// ...

13
	}
14
};
15
Candy.item = {
16
	spawnCandy: function(game) {
17
		// ...

18
	},
19
	clickCandy: function(candy) {
20
		// ...

21
	},
22
	removeCandy: function(candy) {
23
		// ...

24
	}
25
};

Hay tres funciones definidas en el prototipo Candy.Game:

  • create() se ocupa de la inicialización
  • managePause() hace una pausa y reanuda el juego
  • update() gestiona el bucle del juego principal con cada tick

Crearemos un objeto práctico llamado item para representar un único caramelo. Tendrá algunos métodos útiles:

  • spawnCandy() añade nuevos dulces al mundo del juego
  • clickCandy() se dispara cuando un usuario hace clic o hace tapping en el caramelo
  • removeCandy() lo elimina

Vamos a repasarlos:

1
Candy.Game = function(game) {
2
	this._player = null;
3
	this._candyGroup = null;
4
	this._spawnCandyTimer = 0;
5
	this._fontStyle = null;
6
	Candy._scoreText = null;
7
	Candy._score = 0;
8
	Candy._health = 0;
9
};

Aquí, estamos configurando todas las variables que usaremos más adelante en el código.

Al definir this._name, estamos restringiendo el uso de las variables al ámbito Candy.Game, lo que significa que no se pueden usar en otros estados—no las necesitamos allí, así que ¿por qué exponerlas?

Al definir Candy._name, estamos permitiendo el uso de esas variables en otros estados y objetos, por lo que, por ejemplo, Candy._score se puede aumentar desde el Candy.item.clickCandy() .

Los objetos se inicializan en null, y las variables que necesitamos para los cálculos se inicializan con ceros.

Podemos pasar al contenido de Candy.Game.prototype:

1
create: function() {
2
	this.physics.startSystem(Phaser.Physics.ARCADE);
3
	this.physics.arcade.gravity.y = 200;
4
5
	this.add.sprite(0, 0, 'background');
6
	this.add.sprite(-30, Candy.GAME_HEIGHT-160, 'floor');
7
	this.add.sprite(10, 5, 'score-bg');
8
	this.add.button(Candy.GAME_WIDTH-96-10, 5, 'button-pause', this.managePause, this);
9
10
	this._player = this.add.sprite(5, 760, 'monster-idle');
11
	this._player.animations.add('idle', [0,1,2,3,4,5,6,7,8,9,10,11,12], 10, true);
12
	this._player.animations.play('idle');
13
14
	this._spawnCandyTimer = 0;
15
	Candy._health = 10;
16
17
	this._fontStyle = { font: "40px Arial", fill: "#FFCC00", stroke: "#333", strokeThickness: 5, align: "center" };
18
	Candy._scoreText = this.add.text(120, 20, "0", this._fontStyle);
19
20
	this._candyGroup = this.add.group();
21
	Candy.item.spawnCandy(this);
22
},

Al principio de la función create(), instalamos el sistema de física de ARCADE—hay algunos disponibles en Phaser, pero este es el más sencillo. Después de eso, agregamos la gravedad vertical al juego. Luego añadimos tres imágenes: el fondo, el piso en el que se encuentra el monstruo y el fondo de la UI. El cuarto elemento que agregamos es el botón Pause, Tenga en cuenta que estamos utilizando las variables Candy.GAME_WIDTHy Candy.GAME_HEIGHT, que están definidas en Candy.Preloader() pero que están disponibles en todo el código del juego.

Entonces creamos un monstruo, el avatar del jugador. Es un sprite con marcos—una spritesheet. Para que parezca que está de pie y respira con calma, podemos animarlo.

La función animations.add() crea una animación a partir de los marcos disponibles, y la función toma cuatro parámetros:

  • El nombre de la animación (para que podamos hacer referencia más adelante)
  • La tabla con todos los fotogramas que queremos usar (solo podemos usar algunos de ellos si queremos)
  • Una tasa de fotogramas
  • Un indicador para especificar si desea realizar un bucle en la animación y reproducirla indefinidamente.

Si queremos comenzar nuestra animación, tenemos que usar la función animations.play() con el nombre especificado.

A continuación, establecer el spawnCandyTimer a 0 (preparándose para contar) y health del monstruo a 10.

Estilizar el texto

Las dos líneas siguientes nos muestran algún texto en la pantalla. La función this.add.text() toma cuatro parámetros: posiciones absolutas izquierda y superior en la pantalla, la cadena de texto real y el objeto de configuración. Podemos formatear el texto en consecuencia utilizando la sintaxis similar a CSS en ese objeto de configuración. Podemos formatear el texto en consecuencia utilizando la sintaxis similar a CSS en ese objeto de configuración.

La configuración de nuestra fuente tiene este aspecto:

1
this._fontStyle = { 
2
    font: "40px Arial", 
3
    fill: "#FFCC00", 
4
    stroke: "#333", 
5
    strokeThickness: 5, 
6
    align: "center" 
7
};

En este caso, la fuente es Arial, tiene 40 píxeles de alto, el color es amarillo, hay un trazo definido (con color y grosor) y el texto está alineado en el centro.

Después de eso, definimos candyGroup y generamos el primer caramelo.

Detener el juego

La función de pausa se ve así:

1
managePause: function() {
2
	this.game.paused = true;
3
	var pausedText = this.add.text(100, 250, "Game paused.\nTap anywhere to continue.", this._fontStyle);
4
	this.input.onDown.add(function(){
5
		pausedText.destroy();
6
		this.game.paused = false;
7
	}, this);
8
},

Cambiamos el estado de this.game.paused a true cada vez que se hace clic en el botón de pausa, mostramos el mensaje apropiado al reproductor y configuramos un detector de eventos para el clic del reproductor o toque en la pantalla. Cuando se detecta ese clic o toque, eliminamos el texto y establecemos this.game.paused a false.

La variable paused en el objeto game es especial en Phaser, ya que detiene todas las animaciones o cálculos en el juego, por lo que todo se congela hasta que no reparamos el juego estableciéndolo en false.

El bucle de actualización

El nombre de la función update() es una de las palabras reservadas en Phaser. Cuando se escribe una función con ese nombre, se ejecutará en cada fotograma del juego. Puede administrar cálculos dentro de él en función de diversas condiciones.

1
update: function() {
2
	this._spawnCandyTimer += this.time.elapsed;
3
	if(this._spawnCandyTimer > 1000) {
4
		this._spawnCandyTimer = 0;
5
		Candy.item.spawnCandy(this);
6
	}
7
	this._candyGroup.forEach(function(candy){
8
		candy.angle += candy.rotateMe;
9
	});
10
	if(!Candy._health) {
11
		this.add.sprite((Candy.GAME_WIDTH-594)/2, (Candy.GAME_HEIGHT-271)/2, 'game-over');
12
		this.game.paused = true;
13
	}
14
}

Cada marca en el mundo del juego, añadimos el tiempo transcurrido desde la marca anterior a la variable spawnCandyTimer para mantener un registro de ella. La instrucción if comprueba si es o no el momento de restablecer el temporizador y generar nuevos dulces en el mundo del juego. Hacemos esto cada segundo (es decir, cada vez que notamos que spawnCandyTimer ha pasado 1000 milisegundos). Entonces, iteramos a través del grupo de caramelos con todo el objeto de caramelo dentro (podríamos tener más de uno en la pantalla) usando un forEach, y agregamos una cantidad fija (almacenada en el valor rotateMe del objeto candy) a la variable angle del caramelo, de modo que cada uno gira a esta velocidad fija mientras cae. La última cosa que hacemos es comprobar si la salud health ha caído a 0—si es así, entonces mostramos el juego sobre la pantalla y detenemos el juego.

Gestión de los eventos Candy

Para separar la lógica del caramelo del Game principal, utilizamos un objeto llamado item que contiene las funciones que utilizaremos: spawnCandy(), clickCandy() y removeCandy(). Mantenemos algunas de las variables relacionadas con el caramelo en el objeto Game para facilitar su uso, mientras que otros se definen sólo en las funciones item para una mejor mantenibilidad.

1
spawnCandy: function() {
2
	var dropPos = Math.floor(Math.random()*Candy.GAME_WIDTH);
3
	var dropOffset = [-27,-36,-36,-38,-48];
4
	var candyType = Math.floor(Math.random()*5);
5
	var candy = game.add.sprite(dropPos, dropOffset[candyType], 'candy');
6
	candy.animations.add('anim', [candyType], 10, true);
7
    candy.animations.play('anim');
8
9
	game.physics.enable(candy, Phaser.Physics.ARCADE);
10
	candy.inputEnabled = true;
11
	candy.events.onInputDown.add(this.clickCandy, this);
12
13
	candy.checkWorldBounds = true;
14
	candy.events.onOutOfBounds.add(this.removeCandy, this);
15
	candy.anchor.setTo(0.5, 0.5);
16
	candy.rotateMe = (Math.random()*4)-2;
17
	game._candyGroup.add(candy);
18
},

La función comienza definiendo tres valores:

  • Una coordenada x aleatoria para soltar el caramelo de (entre cero y el ancho del Canvas)
  • La coordenada y para dejar caer el caramelo de, basado en su altura (que determinamos más adelante sobre la base del tipo de caramelo)
  • Un tipo de caramelo aleatorio (tenemos cinco imágenes diferentes para usar)

A continuación, agregamos un solo caramelo como sprite, con su posición inicial y la imagen como se definió anteriormente. Lo último que hacemos en este bloque es establecer un nuevo marco de animación que se utilizará cuando el caramelo genera.

A continuación, habilitamos el cuerpo del caramelo para el motor de la física, para que pueda caer naturalmente desde la parte superior de la pantalla cuando se pone la gravedad. A continuación, activamos la entrada en el caramelo para que se haga clic o se toque, y establecer el detector de eventos para esa acción.

Para estar seguro de que el caramelo disparará un evento cuando salga de los límites de la pantalla que establecemos checkWorldBounds a true. Events.onOutOfBounds() es una función que se llamará cuando nuestro caramelo salga de la pantalla; Lo hacemos llamar removeCandy() a su vez. Poner el ancla a nuestro caramelo en el centro exacto nos permite girarlo alrededor de su eje, de modo que girará naturalmente. Establecemos la variable rotateMe aquí para que podamos usarla en el bucle update() para rotar el caramelo; Elegimos un valor entre -2 y +2. La última línea añade nuestro caramelo recién creado al grupo de dulces, de modo que podamos hacer un bucle a través de todos ellos.

Pasemos a la siguiente función, clickCandy():

1
clickCandy: function(candy) {
2
	candy.kill();
3
	Candy._score += 1;
4
	Candy._scoreText.setText(Candy._score);
5
},

Esta toma un caramelo como un parámetro y utiliza el método de Phaser kill() para eliminarlo. También aumentamos la puntuación en 1 y actualizamos el texto de la puntuación.

Restablecer el caramelo también es corto y fácil:

1
removeCandy: function(candy) {
2
	candy.kill();
3
	Candy._health -= 10;
4
},

La función removeCandy() se activa si el caramelo desaparece debajo de la pantalla sin que se haga clic. El objeto de caramelo candy se quita, y el jugador pierde 10 puntos de salud. (Tenía 10 al principio, por lo que falta incluso una pieza de dulces cayendo termina el juego.)

Prototipos y estados de juego

Hemos aprendido acerca de la mecánica del juego, la idea central y cómo se ve la jugabilidad. Ahora es el momento de ver las otras partes del código: escalar la pantalla, cargar los activos, gestionar los botones presionados, etc.

Ya sabemos los estados del juego, así que veamos exactamente cómo se ven, uno tras otro:

Boot.js

Boot.js es el archivo JavaScript donde vamos a definir nuestro objeto de juego principal—llamémosle Candy(pero puedes nombrarlo como quieras). Aquí está el código fuente del archivo Boot.js:

1
var Candy = {};
2
Candy.Boot = function(game) {};
3
Candy.Boot.prototype = {
4
	preload: function() {
5
		this.load.image('preloaderBar', 'img/loading-bar.png');
6
	},
7
	create: function() {
8
		this.input.maxPointers = 1;
9
		this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
10
		this.scale.pageAlignHorizontally = true;
11
		this.scale.pageAlignVertically = true;
12
		this.scale.setScreenSize(true);
13
		this.state.start('Preloader');
14
	}
15
};

Como puedes ver, estamos empezando con var Candy = {} que crea un objeto global para nuestro juego. Todo se almacenará en el interior, por lo que no hinchará el espacio de nombres global.

El código Candy.Boot = function(game){} crea una nueva función llamada Boot() (utilizada en index.html) que recibe el objeto del juego game como parámetro (también creado por el framework en index.html).

El código Candy.Boot.prototype = {} es una forma de definir el contenido de Candy.Boot usando prototipos:

1
Candy.Boot.prototype = {
2
	preload: function() {
3
		// code

4
	},
5
	create: function() {
6
		// code

7
	}
8
};

Hay algunos nombres reservados para funciones en Phaser, como he mencionado antes; preload() y create() son dos de ellos. preload() se utiliza para cargar todos los recursos y create() se llama exactamente una vez (después de preload()), por lo que puede poner el código que se utilizará como configuración para el objeto allí, como para definir variables o agregar sprites .

Nuestro objeto de arranque Boot contiene estas dos funciones, por lo que se puede hacer referencia utilizando Candy.Boot.preload() Y Candy.Boot.create(), respectivamente. Como se puede ver en el código fuente completo del archivo Boot.js, la función preload() carga una imagen de preloader en el marco:

1
preload: function() {
2
	this.load.image('preloaderBar', 'img/loading-bar.png');
3
},

El primer parámetro en this.load.image() es el nombre que damos a la imagen de la barra de carga, y el segundo es la ruta al archivo de imagen en nuestra estructura de proyecto.

Pero ¿por qué estamos cargando una imagen en el archivo Boot.js, cuando Preload.js se supone que lo haga por nosotros de todos modos? Bueno, necesitamos una imagen de una barra de carga para mostrar el estado de todas las otras imágenes que se cargan en el archivo Preload.js, por lo que tiene que cargarse antes, antes de todo lo demás.

Opciones de escalas

La función create() contiene algunos ajustes específicos de Phaser para entrada y escalado:

1
create: function() {
2
	this.input.maxPointers = 1;
3
	this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
4
	this.scale.pageAlignHorizontally = true;
5
	this.scale.pageAlignVertically = true;
6
	this.scale.setScreenSize(true);
7
	this.state.start('Preloader');
8
}

La primera línea, que establece input.maxPointers en 1, define que no usaremos multi-touch, ya que no lo necesitamos en nuestro juego.

El ajuste scale.scaleMode controla la escala de nuestro juego. Las opciones disponibles son: EXACT_FIT, NO_SCALE y SHOW_ALL; Puede enumerar a través de ellos y utilizar los valores de 0, 1 o 2, respectivamente. La primera opción escalará el juego a todo el espacio disponible (100% de ancho y alto, sin relación preservada); El segundo deshabilitará el escalamiento por completo; Y el tercero se cerciorará de que el juego encaje en las dimensiones dadas, pero todo será demostrado en la pantalla sin ocultar ningunos fragmentos (y la proporción será preservada).

Ajustar scale.pageAlignHorizontally y scale.pageAlignVertically a true alineará nuestro juego horizontal y verticalmente, de modo que habrá la misma cantidad de espacio libre en el lado izquierdo y derecho del elemento Canvas; Lo mismo vale para arriba y abajo.

Llamar scale.setScreenSize(true) "activa" nuestra escala.

La última línea, state.start ('Preloader'), ejecuta el siguiente estado—en este caso el estado de Preloader.

Preloader.js

El archivo Boot.js que acabamos de leer tiene una función simple de preload() de una línea y un montón de código en la función create(), pero Preloader.js se ve totalmente diferente: tenemos muchas imágenes para cargar y create() Sólo se utilizará para pasar a otro estado cuando se cargan todos los elementos.

Aquí está el código del archivo Preloader.js:

1
Candy.Preloader = function(game){
2
	Candy.GAME_WIDTH = 640;
3
	Candy.GAME_HEIGHT = 960;
4
};
5
Candy.Preloader.prototype = {
6
	preload: function() {
7
		this.stage.backgroundColor = '#B4D9E7';
8
		this.preloadBar = this.add.sprite((Candy.GAME_WIDTH-311)/2,
9
			(Candy.GAME_HEIGHT-27)/2, 'preloaderBar');
10
		this.load.setPreloadSprite(this.preloadBar);
11
12
		this.load.image('background', 'img/background.png');
13
		this.load.image('floor', 'img/floor.png');
14
		this.load.image('monster-cover', 'img/monster-cover.png');
15
		this.load.image('title', 'img/title.png');
16
		this.load.image('game-over', 'img/gameover.png');
17
		this.load.image('score-bg', 'img/score-bg.png');
18
		this.load.image('button-pause', 'img/button-pause.png');
19
20
		this.load.spritesheet('candy', 'img/candy.png', 82, 98);
21
		this.load.spritesheet('monster-idle',
22
			'img/monster-idle.png', 103, 131);
23
		this.load.spritesheet('button-start',
24
			'img/button-start.png', 401, 143);
25
	},
26
	create: function() {
27
		this.state.start('MainMenu');
28
	}
29
};

Se inicia de forma similar al archivo Boot.js anterior; Definimos el objeto Preloader y añadimos definiciones para dos funciones (preload() y create()) a su prototipo. Dentro del objeto Prototype definimos dos variables: Candy.GAME_WIDTH y Candy.GAME_HEIGHT; Estos establecen el ancho y la altura por defecto de la pantalla del juego, que se utilizará en otras partes del código.

Las primeras tres líneas de la función preload() son responsables de establecer el color de fondo de la etapa (a #B4D9E7, azul claro), mostrando el sprite en el juego y definiéndolo como predeterminado para la función especial llamada setPreloadSprite() Que indicará el progreso de los activos de carga.

Veamos la función add.sprite():

1
this.preloadBar = this.add.sprite((640-311)/2, (960-27)/2, 'preloaderBar');

Como puede ver, pasamos tres valores: la posición izquierda absoluta de la imagen (el centro de la pantalla se logra restando el ancho de la imagen del ancho de la etapa y reduciendo a la mitad el resultado), la posición superior absoluta de la imagen (Calculado de manera similar) y el nombre de la imagen (que ya cargamos en el archivo Boot.js).

Cargando spritesheets

Las siguientes líneas se basan en el uso de load.image() (que ya has visto) para cargar todos los elementos gráficos en el juego.

Los tres últimos son un poco diferentes:

1
this.load.spritesheet('candy', 'img/candy.png', 82, 98);

Esta función, load.spritesheet(), en lugar de cargar una sola imagen, se encarga de una colección completa de imágenes dentro de un archivo—un spritesheet. Se necesitan dos parámetros adicionales para indicar la función el tamaño de una sola imagen en el sprite.

Candy spritesheetCandy spritesheetCandy spritesheet

En este caso, tenemos cinco diferentes tipos de dulces dentro de un archivo candy.png. La imagen completa es 410x98px, pero el único elemento se establece en 82x98px, que se introduce en la función load.spritesheet() . El spritesheet del jugador se carga de una manera similar.

La segunda función, create(), inicia el siguiente estado de nuestro juego, que es MainMenu. Esto significa que el menú principal del juego se mostrará justo después de haber cargado todas las imágenes de la función preload().

MainMenu.js

Este archivo es donde vamos a hacer algunas imágenes relacionadas con el juego, y donde el usuario haga clic en el botón de Inicio Start para iniciar el bucle de juego y jugar el juego.

1
Candy.MainMenu = function(game) {};
2
Candy.MainMenu.prototype = {
3
	create: function() {
4
		this.add.sprite(0, 0, 'background');
5
		this.add.sprite(-130, Candy.GAME_HEIGHT-514, 'monster-cover');
6
		this.add.sprite((Candy.GAME_WIDTH-395)/2, 60, 'title');
7
		this.add.button(Candy.GAME_WIDTH-401-10, Candy.GAME_HEIGHT-143-10,
8
			'button-start', this.startGame, this, 1, 0, 2);
9
	},
10
	startGame: function() {
11
		this.state.start('Game');
12
	}
13
};

La estructura es similar a los archivos JavaScript anteriores. El prototipo del objeto MainMenu no tiene una función preload(), porque no lo necesitamos—todas las imágenes ya se han cargado en el archivo Preload.js.

Hay dos funciones definidas en el prototipo: create() (de nuevo) y startGame(). Como mencioné antes, el nombre de la primera es específico de Phaser, mientras que el segundo es nuestro.

Echemos un vistazo a startGame() primero:

1
startGame: function() {
2
	this.state.start('Game');
3
}

Esta función sólo se ocupa de una cosa—iniciar el bucle del juego—pero no se inicia automáticamente ni después de cargar los elementos. Lo asignaremos a un botón y esperaremos la entrada del usuario.

1
create: function() {
2
	this.add.sprite(0, 0, 'background');
3
	this.add.sprite(-130, Candy.GAME_HEIGHT-514, 'monster-cover');
4
	this.add.sprite((Candy.GAME_WIDTH-395)/2, 60, 'title');
5
	this.add.button(Candy.GAME_WIDTH-401-10, Candy.GAME_HEIGHT-143-10,
6
		'button-start', this.startGame, this, 1, 0, 2);
7
},

El método create() tiene tres funciones de add.sprite () Phaser que ya conocemos: añaden imágenes a la etapa visible colocándolas de forma absoluta. Nuestro menú principal contendrá el fondo, el pequeño monstruo en la esquina, y el título del juego.

Botones

También hay un objeto que ya hemos utilizado en el estado del juego Game , un botón:

1
this.startButton = this.add.button(Candy.GAME_WIDTH-401-10, Candy.GAME_HEIGHT-143-10,
2
	'button-start', this.startGame, this, 1, 0, 2);

Este botón parece más complicado que los métodos que hemos visto hasta ahora. Pasamos ocho argumentos diferentes para crearlo: posición izquierda, posición superior, nombre de la imagen (o sprite), la función a ejecutar después de hacer clic en el botón, el contexto en el que se ejecuta esta función y los índices de las imágenes en el Botón de la hoja de sprites.

Así es como se ve el botón spritesheet, con los estados etiquetados:

Start button spritesheet

Es muy similar a la hoja de sprites de candy.png que usamos antes, excepto que está dispuesta verticalmente.

Es importante recordar que los últimos tres dígitos pasados a la función—1, 0, 2—son los diferentes estados del botón: over (hover), out (normal) y down (toque / clic), respectivamente. Tenemos estados normales, de reposo y de clic en la hoja de sprites de button.png, respectivamente, por lo que cambiamos el orden en la función add.button() de 0, 1, 2 a 1, 0, 2 para reflejarlo.

¡Eso es! Ahora sabes los fundamentos del marco de juegos Phaser; ¡felicitaciones!

El juego terminado

El juego de demostración utilizado en el artículo se ha convertido en un juego completo y acabado que puedes jugar aquí. Como puedes ver, hay vidas, logros, altas puntuaciones y otras características interesantes implementadas, pero la mayoría de ellas se basan en el conocimiento que ya has aprendido siguiendo este tutorial.

Monster Wants Candy full gameMonster Wants Candy full gameMonster Wants Candy full game

También puede leer el corto "making of" post en el blog para aprender sobre los orígenes del juego en sí, la historia detrás de él, y algunos hechos divertidos.

Recursos

La construcción de juegos HTML5 para dispositivos móviles ha explotado en los últimos meses. La tecnología es cada vez mejor y hay herramientas y servicios apareciendo casi todos los días—es el mejor momento para sumergirse en el mercado.

Marcos como Phaser le dan la capacidad de crear juegos que funcionan perfectamente en una variedad de dispositivos diferentes. Gracias a HTML5, puede dirigirse no sólo a los navegadores móviles y de escritorio, sino también a diferentes sistemas operativos y plataformas nativas.

Hay un montón de recursos en este momento que podrían ayudarle a entrar en el desarrollo del juego HTML5, por ejemplo, esta lista de inicio HTML5 o este artículo Introducción al desarrollo de juegos HTML5. Si necesita ayuda, puede encontrar otros desarrolladores en los foros HTML5GameDevs o directamente en el canal #BBG en Freenode IRC. También puede comprobar el estado del próximo libro sobre Firefox OS y juegos HTML5, pero todavía está en las primeras etapas de la escritura. Incluso hay un boletín semanal de Gamedev.js al que puedes suscribirte, para estar al día con las últimas noticias.

Resumen

Este fue un largo viaje a través de cada línea de código de la demostración de Monster Wants Candy, pero espero que te ayude a aprender Phaser, y que en un futuro próximo vas a crear juegos impresionantes usando el marco.

El código fuente utilizado en el artículo también está disponible gratuitamente en GitHub, por lo que puede bifurcar o simplemente descargarlo y hacer lo que quieras. Siéntase libre de modificarlo y crear sus propios juegos encima de él, y asegúrese de visitar los foros de Phaser si necesita algo durante el desarrollo.

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