Introducción a Phaser: Creación de "Monster Wants Candy"
() 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.



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.



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:



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.



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.



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:

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.



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.