Phaser es un gran Framework Open Source basado en Pixi.js para el desarrollo de Juegos que posee una gran cantidad de características como soportar WebGL & Canvas (Dependiendo del navegador, si este no soporta WebGL utilizará el Canvas), la precarga de archivos (Imágenes, JSON), manejo de Sprites, Grupos, Animación, Partículas, Cámara, Entradas (Teclado, Mouse, Multi-touch y Gamepad), Sonido (Codecs como ogg, wav, mp3 según el navegador), Tilemaps, Escalamiento (Device Scaling), un Sistema de Plugins, Navegador móvil y Soporte para los desarrolladores!
Antes de continuar podemos observar unos enlaces interesantes:
Bueno, manos a la obra! Podemos iniciar con Phaser de una manera muy fácil como podemos ver a continuación
Index.html
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="apple-mobile-web-app-capable" content="yes">
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
- <meta name="viewport" content="user-scalable=0, initial-scale=1, minimum-scale=1, maximum-scale=1, width=device-width, minimal-ui=1">
- <title>Phaser Game</title>
- </head>
- <body>
- <div id="game"></div>
- <script src="/phaser.min.js"></script>
- <script type="text/javascript">
- var playState = {
- init: function () {
- //Se llama cuando se inicia el estado
- },
- preload: function () {
- //Se cargan Imagenes y archivos de recurso
- },
- create: function () {
- //Se crea el personaje, los enemigos, los sonidos, el fondo del juego, etc
- },
- update: function () {
- //Logica del Juego como los movimientos, las colisiones, el movimiento del personaje, etc
- },
- render: function () {
- //Depurar lo que se renderiza
- //this.game.debug.body(this.player);
- },
- pause: function () {
- //Cuando el juego es pausado
- },
- shutdown: function () {
- //Cuando se sale del estado
- }
- };
- //(Ancho, Alto, Renderer WebGL/Canvas/Auto, id elemento donde se creara el lienzo del juego)
- var game = new Phaser.Game(320, 480, Phaser.AUTO, 'game');
- //Se pueden crear tantos estados del juego como tu quieras
- game.state.add('play', playState);
- //Se inicia un estado del juego
- game.state.start('play');
- </script>
- </body>
- </html>
Como podemos ver Phaser esta basado en estados, los cuales básicamente podrían ser para cargar archivos, un Menú del juego, otro estado cuando empezamos a jugar en diferentes niveles, un estado para ver información del juego, etc.
En el ejemplo anterior logramos ver como es de sencillo crear un estado e inicializarlo con Phaser. Un estado del juego posee varias funciones (callbacks) que nos ayudarán a
- preload: Cargar las imágenes y otros recursos del juego.
- create: Crear nuestros personajes, crear el fondo del mundo, los enemigos, crear los sonidos y las animaciones.
- update: Configurar los movimientos de nuestros personajes, las colisiones entre el personaje y los enemigos, entre el personaje y el mundo, etc. Este estado constantemente se esta ejecutando para actualizar el juego, según los fotogramas por segundo.
Carga de archivos (Imágenes, Sonidos, etc)
La carga de archivos generalmente se realiza en un estado del juego, en donde mostramos una barra de progreso mientras se cargan todos los recursos que vayamos a necesitar. Para realizar esto podemos definir 2 estados.
- Cargar las imágenes que se mostrarán mientras se cargan los demás archivos que utilizaremos en el juego (Por ejemplo la barra de progreso) y configurar el escalamiento
- var bootState = {
- preload: function () {
- //Color de fondo, por defecto es negro
- //this.game.stage.backgroundColor = 'black';
- this.game.load.image('loading', 'assets/loading.png');//http://www.nicholls.co/ninjadev/assets/loading.png
- this.game.load.image('loadingborder', 'assets/loadingborder.png');//http://www.nicholls.co/ninjadev/assets/loadingborder.png
- },
- create: function () {
- this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
- this.scale.minWidth = 240;//Ancho minimo
- this.scale.minHeight = 170;//Altura minimo
- this.scale.maxWidth = 2880;//Ancho maximo
- this.scale.maxHeight = 1920;//Altura maxima
- //Alinear el juego horizontalmente
- this.scale.pageAlignHorizontally = true;
- //Se escala de manera automatica
- this.scale.setScreenSize(true);
- //Se inicializa el estado de carga de archivos
- this.game.state.start('load');
- }
- };
- Estado de carga de los archivos utilizados en el juego (Mostramos una barra de progreso mientras finaliza)
- var loadState = {
- preload: function () {
- //Agregar texto al juego
- this.labelloading = this.game.add.text(this.game.world.centerX + 0.5, //Posicion en X
- this.game.world.centerY - 15 + 0.5, //Posicion en Y
- 'cargando...', //Texto
- { font: '30px Arial', fill: '#fff' }); //Estilo del texto
- //Establecer el punto de anclaje en el centro
- this.labelloading.anchor.setTo(0.5, 0.5);
-
- this.preloadingborder = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY + 15, 'loadingborder');
- this.preloadingborder.x -= this.preloadingborder.width / 2;
- this.preloading = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY + 19, 'loading');
- this.preloading.x -= this.preloading.width / 2;
- //Crear la barra de carga del juego
- this.game.load.setPreloadSprite(this.preloading, 1);//Sprite, direccion(0==horizontal, 1==vertical)
-
- this.game.load.image('title', 'assets/title.png');
- //Cargamos la hoja de imagenes con el ancho, el alto de cada sprite
- this.game.load.spritesheet('player', 'assets/player.png', 64, 64);
- //Segun el soporte del navegador se carga nuestro audio
- this.game.load.audio('explosion', ['audio/explosion.wav', 'audio/explosion.mp3']);
- },
- create: function () {
- //Generalmente llegamos a un menu
- this.game.state.start('menu');
- }
- };
Y para que funcione tan solo tenemos que agregar estos 2 nuevos estados a nuestro Juego
- game.state.add('boot', bootState);
- game.state.add('load', loadState);
- game.state.start('boot');
Para saber más sobre como escalar el juego para cualquier tipo de pantalla leer la siguiente guía (Click en la imagen)
Como podemos ver en Phaser podemos cargar spritesheets los cuales nos ayudan a acelerar el tiempo de carga del juego porque cargamos en una vez varias imágenes individuales, para crear un spritesheet podemos hacerlo muy fácilmente con la herramienta TexturePacker.
Estado actual del Juego
Tenemos una función muy útil para obtener el estado actual de nuestro juego y depurarlo desde la Consola del Navegador F12
- //Devuelve el estado actual, permitiendo modificarlo desde la Consola del Navegador
- game.state.getCurrentState()
Texto
- //X, Y, Estilo, Grupo
- var label = this.game.add.text(x, y, 'Texto', { font: 'bold 10px "Press Start 2P"', fill: '#000000', fontSize: 10, align: 'center' });
Imágenes
Una imagen es perfecta cuando queremos añadir una imagen de fondo por ejemplo, de resto será mejor un sprite.
- //X, Y, nombre de la imagen cargada
- this.game.add.image(10, 0, 'background');
Sprites
Una imagen o animación en 2 dimensiones que esta integrada en una escena, las imágenes se obtienen por coordenadas y tamaños. Las animaciones por tanto se realizan con dibujos secuenciales.
- //X, Y y nombre de la imagen
- this.player = this.game.add.sprite(20, 20, 'player');
- //Escalar objeto en X y Y
- this.player.scale.set(0.5);
- //Escalar individualmente
- title.scale.x = 0.3;
- title.scale.y = 0.8;
- //Anclaje, punto de agarre del objeto en X y Y, 0 == Izquierda o Arriba y 1 == Derecha o Abajo
- this.player.anchor.setTo(0.5, 0.5);
- //Mover el objeto
- this.player.x += 10;
- this.player.y = 20;
- //Rotar el objeto
- this.player.rotation = 4;
- this.player.angle = 180;
- //... y muchas mas!
TileSprite
Es un sprite que tiene una textura repetida.
- //X, Y, Ancho, Alto, nombre de la imagen cargada
- var tilesprite = this.game.add.tileSprite(0, 0, 32, 64, 'imagen');
- //La textura puede ser desplazada (scrolled)
- tilesprite.tilePosition.setTo(10, 20);
- //La textura puede ser escalada
- tilesprite.tileScale.setTo(1.5, 1.5);
Grupos
Un grupo nos sirve para agrupar varios sprites y poder realizar transformaciones a todos los sprites del grupo muy fácilmente.
- //Se define un grupo de objetos que se pueden crear y reciclar
- //Se pueden aplicar transformaciones y llamar funciones a todos los sprites del grupo
- this.clouds = game.add.group();
- //Generar rapidamente un grupo de sprites iguales
- this.clouds.createMultiple(100, 'cloud');
- //SetAll => Cambiarle el valor de una propiedad a todos los hijos del grupo
- //Chequear si esta en el mundo
- this.clouds.setAll('checkWorldBounds', true);
- //Elimina el Sprite si se sale del juego
- this.clouds.setAll('outOfBoundsKill', true);
- //Mover todos los sprites de posicion
- this.clouds.setAll('x', 500);
- //Habilitar fisica a todos los sprites del grupo
- this.clouds.enableBody = true;
- //Agregar un objeto existente
- this.clouds.add(cloud);
- //Crear un nuevo objeto en el grupo
- this.clouds.create(x, y, 'cloud2');
- //Eliminar a todos los del grupo
- this.clouds.callAll('kill');
- //Obtener un objeto eliminado
- var cloud = this.clouds.getFirstDead();
- var cloud = this.clouds.getFirstExists(false);
- //Revivir
- cloud.revive();
Animar un Sprite
Como vimos anteriormente un sprite puede tener una secuencia de imágenes por medio de un spritesheet como por ejemplo para que el personaje corra, salte, se agache, entre otras acciones y aprovechando esto podemos realizar muy fácilmente una animación
- //Nombre animacion, Frames, Velocidad por frame y si es ciclico
- this.player.animations.add('walk', [0, 1, 2, 3, 4, 0], 15, true);
- //Nombre Animacion, Cambiar velocidad por frame, si es ciclica y si eliminar el sprite cuando termine
- sprite.animations.play('walk');
Tweening
Phaser nos provee muy fácilmente la capacidad para realizar cambios de valores en un periodo de tiempo definido, como por ejemplo escalar, trasladar o cambiarle la opacidad a un sprite en cierto tiempo, entre otras muchas más opciones.
- this.game.add.tween(title).delay(100)//Tiempo de retardo para que comience la animacion
- .to({ angle: 4 }, 500)//Rotar el angulo a 4 en 500 milisengudos
- .to({ angle: -4 }, 500)//Rotar el angulo a -4 en 500 milisengudos
- .loop()//Definimos un ciclo
- .start();//Iniciamos la animacion
-
- this.game.add.tween(logo).to({ alpha: 0 }, 2000, Phaser.Easing.Linear.None) //Cambiar la opacidad a 0 en 2 seg de forma lineal
- .start()//Iniciar la animacion
- .onComplete.add(function () {//Cuando termine la animacion
- //Podemos ejecutar otra funcion
- });
Reproducir la música
Después de haber cargado nuestra música previamente podemos reproducirla muy fácilmente
- this.music = this.game.add.audio('music'); //Nombre del archivo de sonido
- this.music.volume = 0.3; //Cambiar el volumen
- this.music.loop = true; //Si se repite cuando finalice de reproducir
- this.music.play();//Reproducir
Física
Phaser cuenta con varios sistemas de física que podemos manejar a la hora de crear nuestros juegos; Arcade (Sistema para colisiones rectangulares AABB), Ninja (Sistema para Tilemaps y pendientes) y P2 (Sistema de física para colisiones complejas). Veamos un breve ejemplo
- //Crea una instancia de simulacion de la fisica requerida
- this.game.physics.startSystem(Phaser.Physics.ARCADE);
- //Varias formas de habilitar la fisica
- this.game.physics.enable(this.player, Phaser.Physics.ARCADE);
- this.game.physics.arcade.enable(this.coin);
Si por alguna razón nuestro jugador se llega a caer por problemas de colisiones con las baldosas debido a que no fue detectado por culpa de la CPU, podemos cambiar los valores de colisión
- //Cambiar los valores delta durante la colision entre las imagenes, solo ajustar este si el personaje se cae por culpa de la CPU
- self.game.physics.arcade.TILE_BIAS = 50;
Una opción muy útil que tenemos es la posibilidad de poder configurar que el sprite colisione contra los límites del juego y no se salga del lienzo
- this.player.body.collideWorldBounds = true;
- //Modificado para que no colisione con el suelo del mundo y si se caiga
- this.game.physics.arcade.checkCollision.down = false;
Colisiones
Las Colisiones son muy necesarias en todos los juegos y Phaser nos permite configurarlas de una manera muy sencilla (Las colisiones pueden ser entre sprites, grupos y capas)
- update : function() {
- //Colision de objetos en un grupo
- this.game.physics.arcade.collide(this.clouds);
- //Colision entre una capa y un sprite o un grupo
- this.game.physics.arcade.collide(this.layer, this.enemies);
- //Colision entre un sprite y un grupo
- this.game.physics.arcade.collide(this.player, this.enemies, function (player, enemy) {
- //Cuando colisiona el jugador y un enemigo
- });
- //Se superponen 2 objetos pero evita el impacto manteniendo las velocidades y propiedades
- this.game.physics.arcade.overlap(this.player, this.coin, function () {
- //Eliminar la moneda
- });
- }
Una opción muy útil que tenemos es poder modificar el área de contacto del sprite respecto a las colisiones, para que esta no tenga que ser necesariamente la imagen del sprite
- //Ancho, Alto, offset X, offset Y
- this.player.body.setSize(this.player.width -10, this.player.height - 5, 0, 0);
Cámara
Nosotros en Phaser podemos muy fácilmente resetear la cámara, configurarla para que esta siga un sprite por ejemplo o hasta hacer que los elementos se posicionen respecto a esta.
- //Resetear la camara
- this.game.camera.reset();
- //La camara siga a nuestro personaje
- this.game.camera.follow(this.player, Phaser.Camera.FOLLOW_PLATFORMER);
- //Posicionar una nube respecto a la camara
- cloud.fixedToCamera = true;
- cloud.cameraOffset.setTo(this.game.width, this.game.rnd.integerInRange(40, 150));
- //Mover la nube respecto a la camara
- this.game.add.tween(cloud.cameraOffset).to({ x: -500 }, 10000, Phaser.Easing.Linear.None).start();
Partículas
Phaser nos provee la utilidad de poder emitir partículas para efectos continuos como la lluvia, estrellas si estamos en el espacio, etc mediante un objeto llamado emisor.
- var emitter = self.game.add.emitter(self.game.world.centerX, 0, 200);//X, Y, numero de particulas
- emitter.alpha = 0.6;//Controlar la transparencia de las particulas
- emitter.width = self.game.world.width;//Tamao de longitud del emisor
- emitter.makeParticles('star'); //Seleccionamos la imagen cargada para cada particula
- emitter.minParticleScale = 0.2;//Escala minima de las particulas
- emitter.maxParticleScale = 0.7;//Escala maxima de las particulas
- emitter.setYSpeed(100, 300);//Valor minimo y maximo de este rango
- emitter.setXSpeed(0, 0);//Valor minimo y maximo de este rango
- emitter.gravity = 0;//Podemos configurarle la gravedad
- emitter.minRotation = 0;//Rotacion minima
- emitter.maxRotation = 0;//Rotacion maxima
Y para comenzar a emitir las partículas es tan fácil como llamar la siguiente función
- //Emitir todas de una vez o con una frecuencia, tiempo de vida de una particula, frecuencia, cantidad (0 == todas)
- emitter.start(false, 7000, 100, 0);
Entradas
Phaser soporta diferentes tipos de entradas debido al soporte con que cuenta para móviles, como el Mouse, el Toque, el Teclado y el Gamepad. Por lo tanto Phaser nos permite configurar las entradas que se vayan a utilizar en nuestro juego, lo cual nos permite que si vamos a utilizar la barra espaciadora o la tecla abajo nuestro juego por tanto capture estos eventos y no se desplace por ejemplo la página web por culpa del scroll.
- create: function () {
- this.cursor = this.game.input.keyboard.createCursorKeys();
- //Phaser captura las teclas y no se envia el evento al navegador
- this.game.input.keyboard.addKeyCapture([
- Phaser.Keyboard.LEFT,
- Phaser.Keyboard.RIGHT,
- Phaser.Keyboard.UP,
- Phaser.Keyboard.DOWN,
- Phaser.Keyboard.SPACEBAR
- ]);
- }
Y para saber si se ha presionado alguna tecla es tan fácil como
- update: function () {
- if (this.cursor.left.isDown) {
- //Movemos nuestro personaje hacia la izquierda
- this.player.body.velocity.x = -350;
- }
- }
También podríamos capturar otras teclas como el habitual W, A, S, D de esta manera
- create: function(){
- this.wasd = {
- up: self.game.input.keyboard.addKey(Phaser.Keyboard.W),
- left: self.game.input.keyboard.addKey(Phaser.Keyboard.A),
- down: self.game.input.keyboard.addKey(Phaser.Keyboard.S),
- right: self.game.input.keyboard.addKey(Phaser.Keyboard.D)
- };
- },
- update: function () {
- if (this.wasd.left.isDown) {
- //Movemos nuestro personaje hacia la izquierda
- this.player.body.velocity.x = -350;
- }
- }
Otra opción útil con que contamos es la posibilidad de poder saber si una tecla fue presionada por ejemplo en los últimos 10 segundos
- update: function () {
- if (this.game.input.keyboard.justReleased(Phaser.Keyboard.SPACEBAR, 10000)) {
- console.log("La barra espaciadora ha sido presionada en los ultimos 10 segundos.");
- }
- }
Con Phaser se puede saber muy fácilmente si se esta presionando el Mouse
- update: function() {
- //Saber si el puntero del mouse esta siendo presionado
- if (this.game.input.mousePointer.isDown) {
- var halfWidth = this.game.width / 2;
- var halfHeight = this.game.height / 2;
-
- if (this.game.input.mousePointer.x < halfWidth) {
- //Presionando en la parte izquierda
- } else if (this.game.input.mousePointer.x > halfWidth) {
- //Presionando en la parte derecha
- }
- if (this.game.input.mousePointer.y < halfHeight) {
- //Presionando en la parte superior
- } else if (this.game.input.mousePointer.y > halfHeight) {
- //Presionando en la parte inferior
- }
- }
- }
Animaciones con Phaser y GSAP
Si quisiéramos realizar animaciones en Phaser las cuales sean controladas con una línea de tiempo, podemos hacer esto integrando otras librerías como GSAP
Drag and Drop en Phaser
Caminos mediante emisores de partículas en Phaser
Movimientos parabólicos en Phaser
Transformaciones en Phaser (Escalar, Rotar y Reflejar)
Barras de vida en Phaser
Simulando la luz en Phaser
Jugando con Hexágonos en Phaser
Vidas para nuestros personajes en Phaser
Desplazamiento horizontal en el Canvas con Phaser
Slide Box en Phaser
Raspa y Gana en Phaser
Pistas de carreras en Phaser usando física con Box2D
En un artículo anterior doy una breve introducción a lo que es la Nube y como podemos aprovechar todo lo que esta nos ofrece para llevar nuestras aplicaciones a nuestros clientes:
Cuando obtienes una suscripción Pago por Uso en Azure, cuentas con algunos servicios necesarios para poder probar de una manera muy fácil tus aplicaciones y sin pagar aún un solo peso, como por ejemplo 10 Sitios Web gratis de 1gb cada uno y una base de datos de 20mb gratis.
Por lo tanto procedemos a crear nuestro Sitio Web en donde vamos a tener alojado nuestro juego entrando al portal https://manage.windowsazure.com/.
Esperamos a que termine la operación asíncrona de la creación de nuestro Sitio Web y le damos Click al enlace
Nos dirigimos a la sección de Configuración en el menú y activamos la opción que dice “Editar en Visual Studio Online”, finalmente guardamos.
Después de guardar nos dirigimos a la sección del Panel y en la parte de la Vista rápida esperamos unos segundos hasta que aparezca un enlace que dice “Editar en Visual Studio Online”, le damos Click.
Al entrar en este enlace seremos dirigidos hacia Monaco, nuestro editor de Código de Visual Studio Online, aquí podremos observar la estructura de archivos que compone a nuestro Sitio Web en Azure. En esta parte ya podemos empezar a crear archivos, eliminarlos, subirlos desde nuestro computador, y a medida que vayamos escribiendo código Monaco automáticamente irá guardando, permitiéndonos ir probando o que nuestros clientes prueben sin tener que estar subiendo por FTP o realizando cambios en el repositorio de código fuente mediante nuestro control de versiones.