Tips de Desarrollo Web

Escrito por Juan David Nicholls

NAVEGACIÓN

Creando Juegos HTML5 con Phaser Framework en Monaco, el Editor de Código de Visual Studio Online

Hola a todos! Sin importar que lenguaje de Backend  utilices para desarrollar Sitios Web (PHP, Java, C# con ASP.NET, etc), tu puedes crear Juegos 2D  para Desktop y móviles muy fácilmente con HTML5 mediante Canvas y WebGL. Es por esto que en este artículo vamos a ver como puedes reutilizar tu conocimiento existente de Javascript para desarrollar juegos que se puedan abrir desde cualquier navegador moderno o hasta poder llevarlos a la tiendas de Aplicaciones de Windows, como iOS y Android utilizando servicios para estos 2 últimos como CrossWalk o CocoonJS, es hora de empezar a hacer tus propios Indie Games! :D 

También se ha vuelto una tendencia trabajar con Editores de código Web, es decir que desde cualquier parte que te conectes puedas editar tus Sitios Web y no tengas que tener instalado un IDE en tu máquina, esto es muy útil cuando quieres ir trabajando y que otros puedan ir viendo tu trabajo sin muchas complicaciones, es por esto que vamos a hablar un poco de Monaco, el editor de código de Visual Studio Online (Un Servicio accesible desde cualquier Sitio Web en Azure).

Visual Studio Online

Phaser Framework

Phaser Framework

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
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8" />
  5.     <meta name="apple-mobile-web-app-capable" content="yes">
  6.     <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  7.     <meta name="viewport" content="user-scalable=0, initial-scale=1, minimum-scale=1, maximum-scale=1, width=device-width, minimal-ui=1">
  8.     <title>Phaser Game</title>
  9. </head>
  10. <body>
  11.     <div id="game"></div>
  12.     <script src="/phaser.min.js"></script>
  13.     <script type="text/javascript">
  14.         var playState = {
  15.             init: function () {
  16.                 //Se llama cuando se inicia el estado
  17.             },
  18.             preload: function () {
  19.                 //Se cargan Imagenes y archivos de recurso
  20.             },
  21.             create: function () {
  22.                 //Se crea el personaje, los enemigos, los sonidos, el fondo del juego, etc
  23.             },
  24.             update: function () {
  25.                 //Logica del Juego como los movimientos, las colisiones, el movimiento del personaje, etc
  26.             },
  27.             render: function () {
  28.                 //Depurar lo que se renderiza
  29.                 //this.game.debug.body(this.player);
  30.             },
  31.             pause: function () {
  32.                 //Cuando el juego es pausado
  33.             },
  34.             shutdown: function () {
  35.                 //Cuando se sale del estado
  36.             }
  37.         };
  38.         //(Ancho, Alto, Renderer WebGL/Canvas/Auto, id elemento donde se creara el lienzo del juego)
  39.         var game = new Phaser.Game(320, 480, Phaser.AUTO, 'game');
  40.         //Se pueden crear tantos estados del juego como tu quieras
  41.         game.state.add('play', playState);
  42.         //Se inicia un estado del juego
  43.         game.state.start('play');
  44.     </script>
  45. </body>
  46. </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)

Estado de carga

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

  1. var bootState = {
  2.     preload: function () {
  3.         //Color de fondo, por defecto es negro
  4.         //this.game.stage.backgroundColor = 'black';
  5.         this.game.load.image('loading', 'assets/loading.png');//http://www.nicholls.co/ninjadev/assets/loading.png
  6.         this.game.load.image('loadingborder', 'assets/loadingborder.png');//http://www.nicholls.co/ninjadev/assets/loadingborder.png
  7.     },
  8.     create: function () {
  9.         this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
  10.         this.scale.minWidth = 240;//Ancho minimo
  11.         this.scale.minHeight = 170;//Altura minimo
  12.         this.scale.maxWidth = 2880;//Ancho maximo
  13.         this.scale.maxHeight = 1920;//Altura maxima
  14.         //Alinear el juego horizontalmente
  15.         this.scale.pageAlignHorizontally = true;
  16.         //Se escala de manera automatica
  17.         this.scale.setScreenSize(true);
  18.         //Se inicializa el estado de carga de archivos
  19.         this.game.state.start('load');
  20.     }
  21. };

  • Estado de carga de los archivos utilizados en el juego (Mostramos una barra de progreso mientras finaliza)
  1. var loadState = {
  2.     preload: function () {
  3.         //Agregar texto al juego
  4.         this.labelloading = this.game.add.text(this.game.world.centerX + 0.5, //Posicion en X
  5.                                      this.game.world.centerY - 15 + 0.5, //Posicion en Y
  6.                                      'cargando...', //Texto
  7.                                      { font: '30px Arial', fill: '#fff' }); //Estilo del texto
  8.         //Establecer el punto de anclaje en el centro
  9.         this.labelloading.anchor.setTo(0.5, 0.5);
  10.  
  11.         this.preloadingborder = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY + 15, 'loadingborder');
  12.         this.preloadingborder.x -= this.preloadingborder.width / 2;
  13.         this.preloading = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY + 19, 'loading');
  14.         this.preloading.x -= this.preloading.width / 2;
  15.         //Crear la barra de carga del juego
  16.         this.game.load.setPreloadSprite(this.preloading, 1);//Sprite, direccion(0==horizontal, 1==vertical)
  17.  
  18.         this.game.load.image('title', 'assets/title.png');
  19.         //Cargamos la hoja de imagenes con el ancho, el alto de cada sprite
  20.         this.game.load.spritesheet('player', 'assets/player.png', 64, 64);
  21.         //Segun el soporte del navegador se carga nuestro audio
  22.         this.game.load.audio('explosion', ['audio/explosion.wav', 'audio/explosion.mp3']);
  23.     },
  24.     create: function () {
  25.         //Generalmente llegamos a un menu
  26.         this.game.state.start('menu');
  27.     }
  28. };

Y para que funcione tan solo tenemos que agregar estos 2 nuevos estados a nuestro Juego

  1. game.state.add('boot', bootState);
  2. game.state.add('load', loadState);
  3. 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)

A Guide to the Phaser Scale Manager cover page

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 

  1. //Devuelve el estado actual, permitiendo modificarlo desde la Consola del Navegador
  2. game.state.getCurrentState()

Texto

  1. //X, Y, Estilo, Grupo
  2. 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.

  1. //X, Y, nombre de la imagen cargada
  2. 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.

  1. //X, Y y nombre de la imagen
  2. this.player = this.game.add.sprite(20, 20, 'player');
  3. //Escalar objeto en X y Y
  4. this.player.scale.set(0.5);
  5. //Escalar individualmente
  6. title.scale.x = 0.3;
  7. title.scale.y = 0.8;
  8. //Anclaje, punto de agarre del objeto en X y Y, 0 == Izquierda o Arriba y 1 == Derecha o Abajo
  9. this.player.anchor.setTo(0.5, 0.5);
  10. //Mover el objeto
  11. this.player.x += 10;
  12. this.player.y = 20;
  13. //Rotar el objeto
  14. this.player.rotation = 4;
  15. this.player.angle = 180;
  16. //... y muchas mas!

TileSprite

Es un sprite que tiene una textura repetida.

  1. //X, Y, Ancho, Alto, nombre de la imagen cargada
  2. var tilesprite = this.game.add.tileSprite(0, 0, 32, 64, 'imagen');
  3. //La textura puede ser desplazada (scrolled)
  4. tilesprite.tilePosition.setTo(10, 20);
  5. //La textura puede ser escalada
  6. 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.

  1. //Se define un grupo de objetos que se pueden crear y reciclar
  2. //Se pueden aplicar transformaciones y llamar funciones a todos los sprites del grupo
  3. this.clouds = game.add.group();
  4. //Generar rapidamente un grupo de sprites iguales
  5. this.clouds.createMultiple(100, 'cloud');
  6. //SetAll => Cambiarle el valor de una propiedad a todos los hijos del grupo
  7. //Chequear si esta en el mundo
  8. this.clouds.setAll('checkWorldBounds', true);
  9. //Elimina el Sprite si se sale del juego
  10. this.clouds.setAll('outOfBoundsKill', true);
  11. //Mover todos los sprites de posicion
  12. this.clouds.setAll('x', 500);
  13. //Habilitar fisica a todos los sprites del grupo
  14. this.clouds.enableBody = true;
  15. //Agregar un objeto existente
  16. this.clouds.add(cloud);
  17. //Crear un nuevo objeto en el grupo
  18. this.clouds.create(x, y, 'cloud2');
  19. //Eliminar a todos los del grupo
  20. this.clouds.callAll('kill');
  21. //Obtener un objeto eliminado
  22. var cloud = this.clouds.getFirstDead();
  23. var cloud = this.clouds.getFirstExists(false);
  24. //Revivir
  25. 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

  1. //Nombre animacion, Frames, Velocidad por frame y si es ciclico
  2. this.player.animations.add('walk', [0, 1, 2, 3, 4, 0], 15, true);
  3. //Nombre Animacion, Cambiar velocidad por frame, si es ciclica y si eliminar el sprite cuando termine
  4. 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.

  1. this.game.add.tween(title).delay(100)//Tiempo de retardo para que comience la animacion
  2.                           .to({ angle: 4 }, 500)//Rotar el angulo a 4 en 500 milisengudos
  3.                           .to({ angle: -4 }, 500)//Rotar el angulo a -4 en 500 milisengudos
  4.                           .loop()//Definimos un ciclo
  5.                           .start();//Iniciamos la animacion
  6.  
  7. this.game.add.tween(logo).to({ alpha: 0 }, 2000, Phaser.Easing.Linear.None) //Cambiar la opacidad a 0 en 2 seg de forma lineal
  8.                          .start()//Iniciar la animacion
  9.                          .onComplete.add(function () {//Cuando termine la animacion
  10.     //Podemos ejecutar otra funcion
  11. });

Reproducir la música

Después de haber cargado nuestra música previamente podemos reproducirla muy fácilmente

  1. this.music = this.game.add.audio('music'); //Nombre del archivo de sonido
  2. this.music.volume = 0.3; //Cambiar el volumen
  3. this.music.loop = true; //Si se repite cuando finalice de reproducir
  4. 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

  1. //Crea una instancia de simulacion de la fisica requerida
  2. this.game.physics.startSystem(Phaser.Physics.ARCADE);
  3. //Varias formas de habilitar la fisica
  4. this.game.physics.enable(this.player, Phaser.Physics.ARCADE);
  5. 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

  1. //Cambiar los valores delta durante la colision entre las imagenes, solo ajustar este si el personaje se cae por culpa de la CPU
  2. 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

  1. this.player.body.collideWorldBounds = true;
  2. //Modificado para que no colisione con el suelo del mundo y si se caiga
  3. 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)

  1. update : function() {
  2.     //Colision de objetos en un grupo
  3.     this.game.physics.arcade.collide(this.clouds);
  4.     //Colision entre una capa y un sprite o un grupo
  5.     this.game.physics.arcade.collide(this.layer, this.enemies);
  6.     //Colision entre un sprite y un grupo
  7.     this.game.physics.arcade.collide(this.player, this.enemies, function (player, enemy) {
  8.         //Cuando colisiona el jugador y un enemigo
  9.     });
  10.     //Se superponen 2 objetos pero evita el impacto manteniendo las velocidades y propiedades
  11.     this.game.physics.arcade.overlap(this.player, this.coin, function () {
  12.         //Eliminar la moneda
  13.     });
  14. }

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

  1. //Ancho, Alto, offset X, offset Y
  2. 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.

  1. //Resetear la camara
  2. this.game.camera.reset();
  3. //La camara siga a nuestro personaje
  4. this.game.camera.follow(this.player, Phaser.Camera.FOLLOW_PLATFORMER);
  5. //Posicionar una nube respecto a la camara
  6. cloud.fixedToCamera = true;
  7. cloud.cameraOffset.setTo(this.game.width, this.game.rnd.integerInRange(40, 150));
  8. //Mover la nube respecto a la camara
  9. 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.

  1. var emitter = self.game.add.emitter(self.game.world.centerX, 0, 200);//X, Y, numero de particulas
  2. emitter.alpha = 0.6;//Controlar la transparencia de las particulas
  3. emitter.width = self.game.world.width;//Tamao de longitud del emisor
  4. emitter.makeParticles('star'); //Seleccionamos la imagen cargada para cada particula
  5. emitter.minParticleScale = 0.2;//Escala minima de las particulas
  6. emitter.maxParticleScale = 0.7;//Escala maxima de las particulas
  7. emitter.setYSpeed(100, 300);//Valor minimo y maximo de este rango
  8. emitter.setXSpeed(0, 0);//Valor minimo y maximo de este rango
  9. emitter.gravity = 0;//Podemos configurarle la gravedad
  10. emitter.minRotation = 0;//Rotacion minima
  11. emitter.maxRotation = 0;//Rotacion maxima

Y para comenzar a emitir las partículas es tan fácil como llamar la siguiente función

  1. //Emitir todas de una vez o con una frecuencia, tiempo de vida de una particula, frecuencia, cantidad (0 == todas)
  2. 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.

  • Keyboard

  1. create: function () {
  2.     this.cursor = this.game.input.keyboard.createCursorKeys();
  3.     //Phaser captura las teclas y no se envia el evento al navegador
  4.     this.game.input.keyboard.addKeyCapture([
  5.         Phaser.Keyboard.LEFT,
  6.         Phaser.Keyboard.RIGHT,
  7.         Phaser.Keyboard.UP,
  8.         Phaser.Keyboard.DOWN,
  9.         Phaser.Keyboard.SPACEBAR
  10.     ]);
  11. }

Y para saber si se ha presionado alguna tecla es tan fácil como

  1. update: function () {
  2.     if (this.cursor.left.isDown) {
  3.         //Movemos nuestro personaje hacia la izquierda
  4.         this.player.body.velocity.x = -350;
  5.     }
  6. }

También podríamos capturar otras teclas como el habitual W, A, S, D de esta manera

  1. create: function(){
  2.     this.wasd = {
  3.         up: self.game.input.keyboard.addKey(Phaser.Keyboard.W),
  4.         left: self.game.input.keyboard.addKey(Phaser.Keyboard.A),
  5.         down: self.game.input.keyboard.addKey(Phaser.Keyboard.S),
  6.         right: self.game.input.keyboard.addKey(Phaser.Keyboard.D)
  7.     };
  8. },
  9. update: function () {
  10.     if (this.wasd.left.isDown) {
  11.         //Movemos nuestro personaje hacia la izquierda
  12.         this.player.body.velocity.x = -350;
  13.     }
  14. }

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

  1. update: function () {
  2.     if (this.game.input.keyboard.justReleased(Phaser.Keyboard.SPACEBAR, 10000)) {
  3.         console.log("La barra espaciadora ha sido presionada en los ultimos 10 segundos.");
  4.     }
  5. }
  • Mouse

Con Phaser se puede saber muy fácilmente si se esta presionando el Mouse 

  1. update: function() {
  2.     //Saber si el puntero del mouse esta siendo presionado
  3.     if (this.game.input.mousePointer.isDown) {
  4.         var halfWidth = this.game.width / 2;
  5.         var halfHeight = this.game.height / 2;
  6.  
  7.         if (this.game.input.mousePointer.x < halfWidth) {
  8.             //Presionando en la parte izquierda
  9.         } else if (this.game.input.mousePointer.x > halfWidth) {
  10.             //Presionando en la parte derecha
  11.         }
  12.         if (this.game.input.mousePointer.y < halfHeight) {
  13.             //Presionando en la parte superior
  14.         } else if (this.game.input.mousePointer.y > halfHeight) {
  15.             //Presionando en la parte inferior
  16.         }
  17.     }
  18. }

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

Mi primer plugin: Kinetic Scrolling Plugin

Slide Box en Phaser

Raspa y Gana en Phaser

Pistas de carreras en Phaser usando física con Box2D


Monaco, el Editor de Código de Visual Studio Online (Azure)

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:

http://www.nicholls.co/blog/post/Iniciando-en-la-Nube-con-Azure

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/.

Creando un Sitio Web en Azure

Esperamos a que termine la operación asíncrona de la creación de nuestro Sitio Web y le damos Click al enlace

Sitio Nuevo en Azure

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.

Activar Edición en Visual Studio Online

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.

Editar en Visual Studio Online

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.

Monaco, el editor de código de Visual Studio Online

Recursos para Indie Games

blog comments powered by Disqus