1. ¿Qué es Phaser?
  2. Acierta Imagen
    1. Estructura HTML
    2. Estructura Javascript
    3. Cargar fondo y caras
    4. Detecta click/pulsación sobre la cara
    5. Cara random
    6. Evalúa solución
    7. Caras aleatorias
    8. Refrescar escena
    9. Marcador
    10. Temporizador
  3. Haciendo el juego responsive y publicación
  4. Ejercicio mazmorras
  5. Fall down
    1. Gravedad
    2. Un caramelo por segundo
    3. Detecta pulsación sobre caramelo
    4. Caramelo random
    5. El caramelo cae desde diferentes posiciones
    6. Perder el juego
  6. Galería de tiro
    1. Cargar un sprite
    2. Animar todas las instancias del grupo
    3. Desplazar Grupo
    4. Eliminar el objeto sobre el que pulsamos
    5. Si el personaje sale de la pantalla, lo ponemos al principio
  7. Flappy Bird
    1. Detectar teclas.
    2. Colisiones
    3. Crear tuberías
    4. Colisión
  8. Coches
    1. Instalación del Map Editor
    2. Creamos un documento json utilizando el map editor
    3. Objects Layer
    4. Mover coche hacia delante
    5. Rotar coche
  9. Plataformas
    1. Ubicación de prota, plataformas y físicas
    2. Instalación del Map Editor
    3. Creamos un documento json utilizando el map editor
  10. Pet Game
    1. Estructura y carga de imágenes
    2. Draggable
    3. Detectar click sobre los objetos de abajo
    4. Hacer transparente un objeto al pulsar sobre él
    5. Rotando la mascota
    6. Moviendo items
    7. Al comer, cambia el marcador de la mascota
    8. Animación al comer
    9. Añadimos el texto
    10. Reduce stats
    11. Evalúa si ha perdido
    12. Separar el juego en estados
    13. Bootstate
    14. Preload State
    15. Home State
  11. Space Ships
    1. Estructura básica
    2. Añadir el fondo animado
    3. Colocar la nave espacial
    4. Crear enemigos
    5. Movimiento del enemigo
    6. Recibiendo daños
    7. Liberación de partículas al morir el enemigo
    8. Disparos
    9. Detectar colision entre misiles y nave prota
    10. Sobreescribir reset
    11. Temporizar la aparición de enemigos
    12. Multiple levels
    13. Ejercicio JSON
  12. Endless runner
    1. Estructura básica
    2. Añadir una plataforma con un tile
    3. Añadir Prota
    4. Body Bounding box
    5. Jumping
    6. Platform Movement
    7. Suelo
    8. Load platforms
    9. Killing Floors
    10. Random Platform
    11. Coins
    12. Kill Coins
    13. Collecting Coins
    14. Animated Background
    15. Moving Water
    16. Game Over
    17. Counting coins
    18. Game Over Overlay

¿Qué es phaser?

Es una librería de Javascript que permite desarrollar videojuegos en este lenguaje de forma muy rápida.

Algunos enlaces de interés

Acierta imagen

Estructura HTML

Nuestro código Javascript estará dentro de un documento HTML similar al siguiente:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>El título del juego</title>
	<meta name="viewport" content="user-scalable=no,  initial-scale=1,  maximum-scale=1,  width=device-width" />
	<style>
		html, body {
			margin: 0 auto;
			padding: 0;
			background: black;
		}
	</style>

	<script src="phaser.js"></script>
	<script>
		//Aquí va todo nuestro código de Javascript
	</script>
</head>
<body>

</body>
</html>
Estructura básica

Estructura Javascript

const config = {
  type: Phaser.AUTO,
  width: 960,
  height: 640,
  scene: {
    preload,
    create,
    update
  },
};

new Phaser.Game(config);

function preload (){
	console.log("preload")
}

function create (){
	console.log("create")
}

function update(){
	console.log("update")
}
Estructura básica

Cargar fondo y caras

function preload() {
	this.load.image('caraIMG0', '/apuntes/assets/phaser/acierta-imagen/cara0.png');
}

function create() {
	this.add.sprite(100, 400, 'caraIMG0');
}
Carga caras

Detecta click/pulsación sobre la cara

function create() {
	const cara0 = this.add.sprite(100, 400, 'caraIMG0').setInteractive();
	cara0.on('pointerdown', caraPulsada);
}

function caraPulsada(pointer){
	alert(this.texture.key);
}
Cara pulsada

Cara Random

Debemos colocar una imagen superior que será aleatoria; cuando refresquemos la pantalla dicha cara cambiará.

Teoría para generar un número random

Math.floor(Math.random() * (MAX - MIN + 1)) + MIN;

Por ejemplo, si deseamos generar un número aleatorio entre 25 y 75, la sentencia sería

Math.floor(Math.random() * (75-25+1)) + 25;
Cara Random

Evalúa solución

Debemos evaluar si la cara sobre la que hemos pulsado coincide con la cara ubicada en la parte superior de la pantalla.

Evalúa solución

Caras aleatorias

La tres imágenes inferiores deben ser diferentes cada vez que refresquemos la pantalla.

Evalúa solución

Refrescar imagenes

Encapsular el código que hay dentro de la función create en una función llamada cargarImagenes(). Llamaremos a dicha función cuando el juego comience y cuando el usuario pulse sobre una imagen.

Refrescar imagenes

Marcador

Cada vez que el usuario pulse sobre la imagen correcta, debe aumentar un marcador. Si no acierta, se mostrará un mensaje de alerta.

Marcador

Temporizador

Un cronómetro que empieza en los 9 segundos va disminuyendo segundo a segundo. Si llega a los cero segundos, el jugador pierde.

Cada vez que el usuario acierta, el cronómetro vuelve a empezar, pero si la vez anterior teníamos 9 segundos, ahora sólo tendremos 8, y así sucesivamente, descendiendo el tiempo de segundo en segundo.

Evalúa solución

Haciendo el juego responsive y publicación

Para lograr un óptimo resultado, utilizaremos la siguiente metaetiquta y el siguiente código CSS:

<meta name="viewport" content="user-scalable=no,  initial-scale=1,  maximum-scale=1,  width=device-width" />
<style>
        body{
            background: brown

        }
        canvas{
            display:block;
            margin: 0;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
	}
</style>

Tras haber añadido estos códigos, integraremos el siguiente código en nuestra aplización:

window.addEventListener("resize", resize, false);

function preload (){
	resize();
	...
}
function resize (width, height){
	var canvas = document.querySelector("canvas");
	var windowWidth = window.innerWidth;
	var windowHeight = window.innerHeight;

	canvas.style.width = windowWidth + "px";
	canvas.style.height = windowHeight + "px";


	/* var windowRatio = windowWidth / windowHeight;
	var gameRatio = game.config.width / game.config.height;
	if(windowRatio < gameRatio){
		canvas.style.width = windowWidth + "px";
		canvas.style.height = (windowWidth / gameRatio) + "px";
	}else{
		canvas.style.width = (windowHeight * gameRatio) + "px";
		canvas.style.height = windowHeight + "px";
	}*/
}

Apache cordova

A la hora de compilar la aplicación con Apache Cordova o Phonegap Build es altamente recomendable incluir en la raíz de nuestro código (a la misma altura que el index.html), un fichero llamado config.xml con el siguiente código por defecto:

<?xml version="1.0" encoding="UTF-8" ?>
	<widget xmlns   = "http://www.w3.org/ns/widgets"
		xmlns:gap   = "http://phonegap.com/ns/1.0"
		id          = "com.phonegap.example"
		versionCode = "1"
		version     = "1.0.0" >
	<name>PhoneGap Example App</name>
	<description>
		An example for phonegap build app which wont show up in the playstore.
	</description>
	<author href="https://pablomonteserin.com/" email="pablomonteserin@pablomonteserin.com">
		Pablo Monteserín
	</author>
	<preference name="Orientation" value="landscape" />
	<preference name="Fullscreen" value="true" />
</widget>

En este fichero, podemos:

Ejercicio Mazmorras

Partimos de una sala con dos puertas. Una lleva a una muerte segura y la otra a un tesoro.

Cargamos el fondo

Area de la puerta pulsada

var puerta1 = this.add.zone(150, 140, 210, 410)
puerta1.setName('puerta1')
puerta1.setInteractive();
puerta1.on('pointerdown', puertaPulsada);
this.add.graphics().lineStyle(2, 0xffff00).strokeRectShape(puerta1);

Cuando el usuario pulsa sobre una de las puertas debe salir un un mensaje de alerta indicándonos si esta es la puerta 1 o la puerta 0.

Con cambio de escena

Crearemos un número aleatorio entre 0 y 1. Este número será el de la puerta que lleva a una muerte segura. La otra será la que lleva al tesoro. Ahora el mensaje de alerta debe avisarnos de si hemos acertado la puerta correcta o no.

Puerta random

Cargar escena

var sceneConfigA = {
	key: 'sceneA',
	preload: preload,
	create: create
};

var sceneConfigB = {
	key: 'sceneB',
	preload: preloadB,
	create: createB
};

var gameConfig = {
	type: Phaser.CANVAS,
	parent: 'phaser-example',
	width: 800,
	height: 600,
	scene: [ sceneConfigA, sceneConfigB ]
};

var game = new Phaser.Game(gameConfig);
...
juego = this
...
juego.scene.switch('sceneB');
Cambio de escena

Fall down

Cargar imágenes

Ver ejemplo de carga de imágenes

El caramelo cae por gravedad

En phaser Hay varios tipos de físicas:

var config = {
	...
	physics: {
        default: 'arcade',
        arcade: {
            debug: true,
            gravity: { y: 200 }
        }
    }
};
...
function create(){
	...
	var block = this.physics.add.image(50, 100, 'c0');
	block.setVelocity(0, 200);
}
Ver ejercicio de un caramelo que cae por gravedad

Un caramelo por segundo

function create(){
	this.time.delayedCall(1000, onEvent, [], this);
}

function onEvent(){
	var block = this.physics.add.image(50, 100, 'c0');
	block.setVelocity(0, 200);
	this.time.delayedCall(1000, onEvent, [], this);
}
Aplicar físicas

Detecta pulsación sobre caramelo

function clickCandy(){
	this.destroy();
}
Detectar click

Caramelo random

var aleatorio = Math.floor(Math.random()*4);
var candy = this.add.sprite(50, 50, 'c'+aleatorio);
Caramelo aleatorio

El caramelo cae desde diferentes posiciones

Que el caramelo caiga en diferentes posiciones horizontales

Ver ejemplo de los caramelos cayendo en distintas posiciones

Perder el juego

function create(){
	...
	this.physics.world.setBoundsCollision(true, true, true, true);
	this.physics.world.on("worldbounds", function (body) {
		alert("fin del juego")
	})

function onEvent(){
	...
	block.setCollideWorldBounds(true);
	block.body.onWorldBounds = true;
Ver ejercicio de perder la partida

Galería de tiro

Crear un grupo

malos = this.physics.add.group();
malo1 = malos.create(30,30,'gnu');
malo2 = malos.create(0,100,'gnu');
malo3 = malos.create(30,200,'gnu');
malo4 = malos.create(0,300,'gnu');
malo5 = malos.create(30,400,'gnu');

Cargar un spritesheet

function preload(){
	this.load.spritesheet('gnu', 'img/wilber_from_gimp_0.png',  {frameWidth: 56, frameHeight:82});
	....

Animar una instancia

function preload(){
	...
	this.load.spritesheet('gnu', 'img/wilber_from_gimp_0.png', {frameWidth: 56, frameHeight:82});//frameWidth y frameHeight son los tamaños de cada uno de los frames de la animación
}

function create(){
	this.anims.create({
		key: 'right',
		frames: this.anims.generateFrameNumbers('gnu', { start: 0, end: 6 }),
		frameRate: 10,
		repeat: -1
	});
	malo1.anims.play('right', true);
}
Animar grupo

Desplazar grupo

Phaser.Actions.Call(malos.getChildren(), function(go) {
  go.setVelocityX(100)
})
Desplazar grupo

Eliminar el objeto sobre el que pulsamos

Eliminar game object

Si el personaje sale de la pantalla, lo ponemos al principio

if(go.x >config.width){
	go.x=0
}
Sale de la pantalla

Flappy Bird

Pájaro en escena con físicas

Documentación caída por gravedad

Las medidas del sprite son 66x48

Pájaro en pantalla

Saltando al pulsar espacio

function create(){
	...
	this.input.keyboard.on('keydown', function (event) {
		if(event.keyCode== 32){
			pajaro.setVelocityY(-200);
		}
	});
}
El pájaro salta al pulsar espacio

Crear tuberías

function create(){
	...
	timedEvent = this.time.delayedCall(1000, nuevaColumna, [], this);
}

function nuevaColumna() {
	var columna = this.physics.add.group();

	var hueco = Math.floor(Math.random() * 5) + 1;
	for (var i = 0; i < 8; i++) {
		//El agujero son dos casillas, por eso ponemos hole +1
		if (i != hueco && i != hueco + 1) {
			var cubo = columna.create(800, i * 60 + 10, 'pipe');
			cubo.body.allowGravity = false;
		}
	}
	columna.setVelocityX(-200);
	columna.checkWorldBounds = true;
	columna.outOfBoundsKill = true;
	timedEvent = this.time.delayedCall(1000, nuevaColumna, [], this);
}
Ponemos las tuberías en pantalla

Otra aproximación para resolver este ejercicio sería crear un juego con gravedad 0 (configurado en el objeto config), y luego asignarle gravedad al pájaro:

pajaro.setGravity(0,400);

Colisión con tuberías

this.physics.add.overlap(pajaro, columna, hitColumna, null, this);

function hitColumna(){
	alert("game over")
}
Detectamos la colisión del pájaron con las tuberías

Physics Editor

  1. Add Sprites

Coches

Instalación del Map Editor

Descarga

Aquí podremos descargar un editor de tiles multiplataforma para hacer los mapas de nuestros juegos

sudo apt-get install tiled

Creamos un documento json utilizando el map editor

  1. New
    1. Map: orientation:ortogonal, layer format: xml -> le damos a OK
  2. Panel Layers -> seleccionamos una Tile layer.
  3. Panel Tilesets -> New Tileset ->
    1. Le damos un nombre al grupo de tiles
    2. Cargamos los tiles preparados a 32x32 con un margen de 0 px (sin margen)
  4. Iremos dibujando en el mapa usando la herramienta stamp.
  5. Menu file -> export as -> seleccionamos json

Al abrir el fichero json, veremos que en general la propiedad data tiene un valor que se repite muchas veces. Es el gid, o identificador del tile. Un 0 significa que no hay nada. Depende de la ubicación del tile en el tilesheet.

function preload(){
	this.load.image('gameTiles', 'tiles_spritesheet.png');
	this.load.tilemapTiledJSON('level1', 'map.json');
}
function create(){
	this.map = this.add.tilemap('level1');
	var tileset = this.map.addTilesetImage('tiles_spritesheet','gameTiles');
	this.backgroundLayer = this.map.createStaticLayer('backgroundLayer', tileset); backgroundLayer es el nombre que le dimos a la capa en Tiled
}

Cargar coche

Carga coche

Mover coche hacia delante

Mover coche hacia delante

Rotar

Rotar

Controles táctiles player 1

Controles táctiles player 1

Colisiones entre coches

Colisiones entre coches

Fricción

Fricción

Colisión con límites

Colisión con límites

Detectar vueltas

Detectar vueltas

Contador de vueltas

Contador de vueltas

Ubicar controles

Controles varios jugadores

Gestionar controles de varios jugadores

Controles varios jugadores

Animación jugadores

Controles varios jugadores

Plataformas

Carga background, plataformas, prota y físicas

Ver ejemplo

Colisión con plataformas

Ver ejemplo

Movimiento

Ver ejemplo

Con animación

Ver ejemplo

Con salto

Ver ejemplo

Controles táctiles

Ver ejemplo

Enemigo

Ver ejemplo

Enemigo inteligente

Ver ejemplo

Pet Game

Estructura y carga de imágenes

Escribir el código fuente de la estructura de un juego Phaser (con sus métodos preload, create y update). Precargar todas las imágenes y el sprite correspondientes. Ubicarlos en pantalla para que quede como el link de resultado final.

Draggable

this.pet.inputEnabled = true;
this.pet.input.enableDrag();
Ver ejemplo de draggable

Detectar click sobre los objetos de abajo

Documentación User Input
Ver ejemplo de detección de click

Hacer transparente un objeto al pulsar sobre él

this.buttons = [this.apple, this.candy, this.toy, this.rotate];
...
pickItem :function(sprite, event){
	this.clearSelection();
	sprite.alpha = 0.4;
},
clearSelection : function(){
	this.buttons.forEach(function(element, index){
		element.alpha = 1;
	});
}
Ver ejemplo de hacer transparente el objeto al pulsar sobre él

Rotando la mascota al pulsar sobre las flechitas


pickRotate :function(sprite, event){
	this.clearSelection();
	sprite.alpha = 0.4;

	var petRotation = this.game.add.tween(this.pet);
	petRotation.to({angle:'+720'}, 1000);

	petRotation.onComplete.add(function(){
		sprite.alpha = 1;
	});
	petRotation.start();
}
Ver ejemplo de rotación de la mascota

Moviendo items

//Primero se llamará a pickItem y luego a placeItem
create: function() {
	...
	this.background.inputEnabled = true;
	this.background.events.onInputDown.add(this.placeItem, this);
	...
	this.selectedItem = null;
},
pickItem :function(sprite, event){
	...
	this.selectedItem = sprite;
},
clearSelection : function(){
	this.buttons.forEach(function(element, index){
		element.alpha = 1;
	});
	this.selectedItem = null;
},
placeItem: function(sprite, event){
	var x = event.position.x;
	var y = event.position.y;
	var newItem = this.game.add.sprite(x, y, this.selectedItem.key);
	newItem.anchor.setTo(0.5);
}
Ver ejemplo de movimiento de los items

La mascota se mueve tras los objetos

var petMovement = this.game.add.tween(this.pet);
petMovement.to({x:x, y:y}, 700);
petMovement.onComplete.add(function(){
	newItem.destroy();
}, this);
petMovement.start();
Ver ejemplo de movimiento de la mascota tras los objetos

Al comer, cambia el marcador de la mascota

this.pet.customParams = {health: 100, fun: 100};
...
placeItem: function(sprite, event){
	var x = event.position.x;
	var y = event.position.y;
	var newItem = this.game.add.sprite(x, y, this.selectedItem.key);
	newItem.customParams = this.selectedItem.customParams;

	//stat es el nombre de la propiedad cuyo valor voy a modificar (health y fun)
	for(var stat in newItem.customParams){
		console.log("stat: " + newItem.customParams[stat]);
		this.pet.customParams[stat] += newItem.customParams[stat];
	}
Ver ejemplo de como cambia el marcador de la mascota al comer

Animación de comer

...
this.pet.animations.add('eating', [1,2,3,2,1], 7);
...
this.pet.animations.play('eating');
Ver ejemplo de como come la mascota

Añadimos el texto

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

this.refreshStats();

...
refreshStats: function(){
	this.healthText.text = this.pet.customParams.health;
	this.funText.text = this.pet.customParams.fun;
}
Ver ejemplo de juego con texto

>Reduce Stats

Creamos un temporizador para reducir los valores para salud y diversión a lo largo del tiempo

this.statsDecreaser = this.game.time.events.loop(Phaser.Timer.SECOND*5, this.reduceProperties, this);
},
reduceProperties: function(){
	this.pet.customParams.health -=10;
	this.pet.customParams.fun -= 10;
	this.refreshStats();
},
Ver ejemplo de cambio de estadísticas

Evalúa si ha perdido

update: function(){
  	if(this.pet.customParams.health <=0 || this.pet.customParams.fun <= 0){
  		this.pet.frame = 4;
  		this.game.time.events.add(2000, this.gameOver, this);
	}
},
gameOver: function(){
	this.game.state.restart();
}
Ver ejemplo de evaluación de si el jugador a perdido

Separar el juego en estados

<script src="../../phaser.js"></script>
<script src="GameState.js"></script>
<script>
	var game = new Phaser.Game(360, 640, Phaser.AUTO);

	game.state.add('GameState', GameState);
	game.state.start('GameState');
</script>
Ver ejemplo de como separar el juego en estados

BootState

...
var BootState = {
	//initiate some game-level settings
	init: function() {
		//scaling options
		this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
		this.scale.pageAlignHorizontally = true;
		this.scale.pageAlignVertically = true;
	},
	preload: function() {
		this.load.image('preloadBar', '../../bar.png');
		this.load.image('logo', '../../logo.png');
	},
	create: function() {
		this.game.stage.backgroundColor = '#fff';

		this.state.start('PreloadState')
	}
};
Ver ejemplo del bootstate

PreloadState

var PreloadState = {

	preload: function() {
		this.logo = this.add.sprite(this.game.world.centerX, this.game.world.centerY, 'logo');
		this.logo.anchor.setTo(0.5);

		this.preloadBar = this.add.sprite(this.game.world.centerX, this.game.world.centerY + 128, 'preloadBar');
		this.preloadBar.anchor.setTo(0.5);
		this.load.setPreloadSprite(this.preloadBar);

		this.load.image('backyard', '../backyard.png');
		this.load.image('apple', '../apple.png');
		this.load.image('candy', '../candy.png');
		this.load.image('rotate', '../rotate.png');
		this.load.image('toy', '../rubber_duck.png');
		this.load.spritesheet('pet', '../pet.png', 97, 83, 5, 1, 1);
	},
	create: function(){
		this.state.start('GameState')
	}
}
Ver ejemplo del preload state

HomeState

Hacer el HomeState

GameState
gameOver: function(){
	this.state.start('HomeState', true, false, 'GAME OVER!');
}
HomeState.js
var HomeState = {

	init: function(message) {
		this.message = message;
	},

	create: function() {
		var background = this.game.add.sprite(0,0,'backyard');
		background.inputEnabled = true;

		background.events.onInputDown.add(function(){
			this.state.start('GameState');
		}, this);

		var style = {font: '35px Arial', fill: '#fff'};
		this.game.add.text(30, this.game.world.centerY + 200, 'TOUCH TO START', style);

		if(this.message) {
			this.game.add.text(60, this.game.world.centerY - 200, this.message, style);
		}
	}
};

Space ships

Estructura básica

Completar el siguiente código para hacer la estructura básica del juego. Debemos precargar todas las imágenes adjuntas

var SpaceHipster = SpaceHipster || {};

SpaceHipster.GameState = {

	init: function() {
		this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

		this.game.physics.startSystem(Phaser.Physics.ARCADE);

		this.PLAYER_SPEED = 200;
		this.BULLET_SPEED = -1000;

 	},
 	...

Añadir el fondo animado

create: function() {
	this.background = this.add.tileSprite(0, 0, this.game.world.width, this.game.world.height, 'space');
	this.background.autoScroll(0, 30);
Ver ejemplo de fondo animada Ver documentación.
Ver ejemplo con nave espacial

Mover la nave espacial

Si pulsamos en la parte derecha de la pantalla, la nave se moverá para la derecha, y viceversa.

update: function() {
	this.player.body.velocity.x = 0;

	if(this.game.input.activePointer.isDown) {
		var targetX = this.game.input.activePointer.position.x;

		var direction = targetX >= this.game.world.centerX ? 1 : -1;

		this.player.body.velocity.x = direction * this.PLAYER_SPEED;
	}
}
Ver ejemplo de movimiento de la nave espacial

Implementar los disparos

A veces queremos crear un grupo de objetos que tienen su propio comportamiento. Son algo parecido a los sprites, pero con parámetros y comportamientos concretos.

//bullets
this.playerBullets = this.add.group();
this.playerBullets.enableBody = true;
this.shootingTimer = this.game.time.events.loop(Phaser.Timer.SECOND/5, this.createPlayerBullet, this);
...
createPlayerBullet: function(){
	var bullet = this.playerBullets.getFirstExists(false);

	if(!bullet) {
		bullet = new SpaceHipster.PlayerBullet(this.game, this.player.x, this.player.top );
		this.playerBullets.add(bullet);
	}else {
		//reset position
		bullet.reset(this.player.x, this.player.top);
	}
	bullet.body.velocity.y = this.BULLET_SPEED;
}
PlayerBuller.js
var SpaceHipster = SpaceHipster || {};

SpaceHipster.PlayerBullet = function(game, x, y) {
	Phaser.Sprite.call(this, game, x, y, 'bullet');

	this.anchor.setTo(0.5);
	this.checkWorldBounds = true;
	this.outOfBoundsKill = true;
};

//Definimos la herencia de PlayerBullet
SpaceHipster.PlayerBullet.prototype = Object.create(Phaser.Sprite.prototype);
//Definimos el constructor de PlayerBullet
SpaceHipster.PlayerBullet.prototype.constructor = SpaceHipster.PlayerBullet;
Ver ejemplo disparos

Crear enemigos

var enemy = new SpaceHipster.Enemy(this.game, 100, 100, 'greenEnemy', 10, []);
this.game.add.existing(enemy);
Enemy.jsvar SpaceHipster = SpaceHipster || {};

SpaceHipster.Enemy = function(game, x, y, >keyPhaser.Sprite.call(this, game, x, y, key);

	this.animations.add('getHit', [0, 1, 2, 1, 0], 25, false);
	this.anchor.setTo(0.5);
	this.health = health;

	this.enemyBullets = enemyBullets;
};

SpaceHipster.Enemy.prototype = Object.create(Phaser.Sprite.prototype);
SpaceHipster.Enemy.prototype.constructor = SpaceHipster.Enemy;
Ver ejemplo de creación de enemigos

Movimiento del enemigo

Enemy.js
SpaceHipster.Enemy.prototype.update = function(){
	/*Si el enemigo está ubicado poco antes de la coordenada x=0,
	lo desplazo den la dirección opuesta y le cambio la dirección.
	Lo desplazo en la dirección opuesta porque si no, se quedaría embuclado,
	llendo hacia delante y hacia atrás */
	
	if(this.x < 0.05*this.game.world.width){
		this.x = 0.05*this.game.world.width +2;
		this.body.velocity.x *= -1;
	}else if(this.x > 0.95*this.game.world.width){
		this.x = 0.95 * this.game.world.width -2;
		this.body.velocity.x *= -1;
	}
	if(this.position.y > this.game.world.height) {
		this.kill();
  	}
}
enemy.body.velocity.x = 100;
enemy.body.velocity.y = 50;
Ver ejemplo de movimiento del enemigo

Recibiendo daños

Primero crearemos un grupo de enemigos

update: function() {
	this.game.physics.arcade.overlap(this.playerBullets, this.enemies, this.damageEnemy, null, this);
...
initEnemies: function(){
	this.enemies = this.add.group();
	this.enemies.enableBody = true;

	var enemy = new SpaceHipster.Enemy(this.game, 100, 100, 'greenEnemy', 10, []);
	this.game.add.existing(enemy);
	enemy.body.velocity.x = 100;
	enemy.body.velocity.y = 50;
},
Enemy.js
SpaceHipster.Enemy.prototype.damage = function(amount) {
  Phaser.Sprite.prototype.damage.call(this, amount);
  this.play('getHit');
};
Ver ejemplo de grupo de enemigos

Liberación de partículas al morir el enemigo

if(this.health <= 0){
    var emitter = this.game.add.emitter(this.x, this.y, 100);
    emitter.makeParticles('enemyParticle');
    emitter.minParticleSpeed.setTo(-200, -200);
    emitter.maxParticleSpeed.setTo(200, 200);
    emitter.gravity = 0;
    //parametros: las parámetras son liberadas todas a la vez (explosión), tiempo que dura, queremos una frecuencia, porcentaje de particulas liberadas
    emitter.start(true, 500, null, 100);
}
Ver ejemplo de liberación de partículas al morir un enemigo.

Disparos del enemigo

EnemyBullets será muy similar a PlayerBullets

GameState.js
initEnemies: function(){
	...
	this.enemies = this.add.group();
	this.enemies.enableBody = true;

	this.enemyBullets = this.add.group();
	this.enemyBullets.enableBody = true;

	var enemy = new SpaceHipster.Enemy(this.game, 100, 100, 'greenEnemy', 10, this.enemyBullets);
	this.enemies.add(enemy);
Enemy.js
SpaceHipster.Enemy = function(game, x, y, key, health, enemyBullets) {
	...
	//el parámetro false significa que el timer no se destruirá cuando termine
	this.enemyTimer = this.game.time.create(false);
	this.enemyTimer.start();
	this.scheduleShooting();
};
...
SpaceHipster.Enemy.prototype.scheduleShooting = function(){
	this.shoot();
	this.enemyTimer.add(Phaser.Timer.SECOND*2, this.scheduleShooting, this);
}

SpaceHipster.Enemy.prototype.shoot = function(){
	var bullet = this.enemyBullets.getFirstExists(false);
	if(!bullet){
		bullet = new SpaceHipster.EnemyBullet(this.game, this.x, this.bottom);
		this.enemyBullets.add(bullet);
	}else{
		bullet.reset(this.x, this.y);
	}
	bullet.body.velocity.y = 100;
}
EnemyBullet.js
var SpaceHipster = SpaceHipster || {};

SpaceHipster.EnemyBullet = function(game, x, y) {
  Phaser.Sprite.call(this, game, x, y, 'bullet');

  //some default values
  this.anchor.setTo(0.5);
  this.checkWorldBounds = true;
  this.outOfBoundsKill = true;
};

SpaceHipster.EnemyBullet.prototype = Object.create(Phaser.Sprite.prototype);
SpaceHipster.EnemyBullet.prototype.constructor = SpaceHipster.EnemyBullet;
Ver ejemplo de enemigo disparando

Detectar colision entre misiles y nave prota

Documentación colisión.
damageEnemy: function(bullet, enemy){
	enemy.damage(1);
	bullet.kill();
}
Ver ejemplo de colisión entre las balas y el enemigo

Sobreescribir reset method

Permite reiniciar un objeto que ha dejado de usarse.

Enemy.js
SpaceHipster.Enemy.prototype.reset = function(x, y, health, key, scale, speedX, speedY){
	Phaser.Sprite.prototype.reset.call(this, x, y, health);
	this.loadTexture(key);
	this.scale.setTo(scale);
	this.body.velocity.x = speedX;
	this.body.velocity.y = speedY;
}
Ver ejemplo de sobreescritura del método reset

Temporizar la aparición de enemigos

Los tiempos son establecidos a partir del inicio de la aplicación, no guardando relación de unos con otros.

loadLevel: function(){
	this.currentEnemyIndex = 0;

	this.levelData = {
	"duration": 35,
	"enemies": [
	{
		"time": 1,
		"x": 0.05,
		"health": 6,
		"speedX": 20,
		"speedY": 50,
		"key": "greenEnemy",
		"scale": 3
	},
	{
		"time": 2,
		"x": 0.1,
		"health": 3,
		"speedX": 50,
		"speedY": 50,
		"key": "greenEnemy",
		"scale": 1
	},
	{
		"time": 3,
		"x": 0.1,
		"health": 3,
		"speedX": 50,
		"speedY": 50,
		"key": "greenEnemy",
		"scale": 1
	},
	{
		"time": 4,
		"x": 0.1,
		"health": 3,
		"speedX": 50,
		"speedY": 50,
		"key": "greenEnemy",
		"scale": 1
	}]
	};
	this.scheduleNextEnemy();
},
scheduleNextEnemy: function() {
	var nextEnemy = this.levelData.enemies[this.currentEnemyIndex];
	if(nextEnemy){
		var nextTime = 1000 * ( nextEnemy.time - (this.currentEnemyIndex == 0 ? 0 : this.levelData.enemies[this.currentEnemyIndex - 1].time));

		this.nextEnemyTimer = this.game.time.events.add(nextTime, function(){
			this.createEnemy(nextEnemy.x * this.game.world.width, -100, nextEnemy.health, nextEnemy.key, nextEnemy.scale, nextEnemy.speedX, nextEnemy.speedY);

			this.currentEnemyIndex++;
			this.scheduleNextEnemy();
		}, this);
	}
}

Multiple levels

Utilizando el número del nivel podemos cargar diferentes levelData de json.

init: function(currentLevel) {
...
	this.numLevels = 3;
	this.currentLevel = currentLevel ? currentLevel:1;
	console.log('current level: ' + this.currentLevel);
},
...
loadLevel: function(){
	this.currentEnemyIndex = 0;

	this.levelData = {
	"duration": 5, //Ponemos un timer para que el nivel se acabe en 5 segundos
...
this.endOfLevelTimer = this.game.time.events.add(this.levelData.duration * 1000, function(){
	console.log('level ended');

	if(this.currentLevel < this.numLevels){
		this.currentLevel++;
	}else{
		this.currentLevel = 1;
	}
	this.game.state.start('GameState', true, false, this.currentLevel );
}, this);
Ver ejemplo multiple levels

Ejercicio - Carga los JSON de cada nivel

Utilizar los ficheros json adjuntos

Endless runner

Estructura básica

Partiremos de la estructura básica adjunta

Descargar recursos.

Añadir una plataforma con un tile

init: function() {
	this.floorPool = this.add.group();
...

create: function() {
	var platform = new MrHop.Platform(this.game, this.floorPool,1, 100, 200);
	this.add.existing(platform);
},
Platform.js
MrHop.Platform = function(game, floorPool,numTiles, x, y) {
	...
	prepare(numTiles, x, y);
};
MrHop.Platform.prototype.prepare = function(numTiles, x, y){
	var i = 0;

	while(i < numTiles){
		var floorTile = this.floorPool.getFirstExists(false);
		if(!floorTile){
			floorTile = new Phaser.Sprite(this.game, x +i * this.tileSize, y, 'floor');
		}else{
			floorTile.reset(x+i*this.tileSize, y);
		}
		this.add(floorTile);
		i++;
	}
	this.setAll('body.immovable', true);
	this.setAll('body.allowGravity', false);
}
Ver ejemplo de colocar un tile

Añadir Prota

Ver ejemplo añadir prota

Body Bounding box

Es posible que querramos definir un area de colisión que no coincide exáctamente con el área del sprite.

Con el siguiente código podremos ver la bounding box del prota

render: function(){
	this.game.debug.body(this.player);
}

Para redefinir la bounding size

this.player.body.setSize(38, 60, 0, 0);

Otra instrucción para debugar:

this.game.debug.bodyInfo(this.player, 0 ,30);

jumping


update: function() {
	this.game.physics.arcade.collide(this.player, this.platform);
	if(this.cursors.up.isDown || this.game.input.activePointer.isDown){
		this.playerJump();
	}else if(this.cursors.up.isUp || this.game.input.activePointer.isUp){
		this.isJumping = false;
	}
},
playerJump: function(){
	if(this.player.body.touching.down){
		this.startJumpY = this.player.y;
		this.isJumping = true;
		this.jumpPeaked = false;
		this.player.body.velocity.y = -300;
	}else if(this.isJumping && !this.jumpPeaked){
		var distanceJumped = this.startJumpY - this.player.y;
		if(distanceJumped <= this.maxJumpDistance){
			this.player.body.velocity.y = -300;
		}else{
			this.jumpPeaked = true; //jumpPeaked es la variable que determina si hemos llegado al máximo de altura durante el salto. La altura del salo irá en aumento mientras mantengamos la tecla arriba pulsada
		}
	}
},
Ver ejemplo junping

Collision Group

init: function() {
	this.platformPool = this.add.group();
,
create: function() {
	...
	this.add.existing(this.platform);
	this.platformPool.add(this.platform);
,
update: function(){
	//no es posible establecer una colisión con un grupo de grupos, por ello iremos recorriendo los grupos y estableciendo colisiones independientes
	this.platformPool.forEachAlive(function(platform, index){
		this.game.physics.arcade.collide(this.player, platform);
	}, this)

}
MrHop.Platform.prototype.prepare = function(numTiles, x, y){
	this.alive = true;
Ver ejemplo collision group

Platform Movement

En lugar de mover la cámara, lo que haremos será mover las plataformas

MrHop.Platform = function(game, floorPool,numTiles, x, y, speed) {
	...
	this.prepare(numTiles, x, y, speed);
	MrHop.Platform.prototype.prepare = function(numTiles, x, y, speed){
		...
	this.setAll('body.velocity.x', -speed);
}
init: function() {
...
    this.levelSpeed = 200;
},
create: function() {
    ...
    this.platform = new MrHop.Platform(this.game, this.floorPool,12, 0, 200, -this.levelSpeed);

},
update: function(){
	this.player.body.velocity.x = this.levelSpeed;
}
Ver ejemplo Platform movement

Si el prota no está en contacto con el suelo, que se quede en el sitio (horizontalmente)

if(this.player.body.touching.down){
	this.player.body.velocity.x = this.levelSpeed;
}else{
	this.player.body.velocity.x = 0;
}
Ver ejemplo, si el prota no está en en contacto con el suelo, que se quede en el sitio

Load Platforms

create: function() {
...
	this.loadLevel();
},
update: function(){
...
	//Cuando la plataforma sea totalmente visible crear otra nueva
	if(this.currentPlatform.length && this.currentPlatform.children[this.currentPlatform.length-1].right < this.game.world.width) {
		this.createPlatform();
	}
},
loadLevel: function(){

	this.levelData = {
		platforms: [
		{
			separation: 50,
			y: 200,
			numTiles: 4
		},
		{
			separation: 50,
			y: 250,
			numTiles: 6
		},
		{
			separation: 100,
			y: 200,
			numTiles: 3
		},
		{
			separation: 50,
			y: 250,
			numTiles: 8
		},
		{
			separation: 100,
			y: 200,
			numTiles: 10
		},
		{
			separation: 100,
			y: 300,
			numTiles: 4
		},
		{
		separation: 50,
			y: 200,
			numTiles: 4
		}
	]
};

	this.currIndex = 0;

	this.createPlatform();
},
createPlatform: function(){
	var nextPlatformData = this.levelData.platforms[this.currIndex];

	if(nextPlatformData){
		this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, nextPlatformData.numTiles, this.game.world.width + nextPlatformData.separation, nextPlatformData.y, -this.levelSpeed);

		this.platformPool.add(this.currentPlatform);

		this.currIndex++;
	}
}
Cargar plataformas

Killing floors

create: function() {
...
	this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, 11, 0, 200, -this.levelSpeed);
	this.platformPool.add(this.currentPlatform);
},
...
	this.platformPool.forEachAlive(function(platform, index){
		this.game.physics.arcade.collide(this.player, platform);
		//platform hereda de Phaser.group. Si platform tiene algún elemento
		//y la parte derecha del último tile de la plataforma ha salido de la pantalla,
		//elimino la plataforma
		if(platform.length && platform.children[platform.length-1].right < 0){
			platform.kill();
		}
	}, this);
	//Cuando la plataforma actual se vea totalmente en pantalla, crearemos la siguiente
	if(this.currentPlatform.length && this.currentPlatform.children[this.currentPlatform.length-1].right < this.game.world.width) {
		this.createPlatform();
	}
...

createPlatform: function(){
	var nextPlatformData = this.levelData.platforms[this.currIndex];

	if(nextPlatformData){

		this.currentPlatform = this.platformPool.getFirstDead();
		if(!this.currentPlatform){
			this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, nextPlatformData.numTiles, this.game.world.width + nextPlatformData.separation, nextPlatformData.y, -this.levelSpeed);
		}else{
			this.currentPlatform.prepare(nextPlatformData.numTiles, this.game.world.width + nextPlatformData.separation, nextPlatformData.y, -this.levelSpeed);
		}

		this.platformPool.add(this.currentPlatform);
		this.currIndex++;
	}
}
//En el momento de borrar añado todos los tiles de la plataforma a un array provisional (sprites) cuyo contenido luego añadiré a un pool de sprites
MrHop.Platform.prototype.kill = function(){
	this.alive = false;
	this.callAll('kill');
	var sprites = [];
	this.forEach(function(tile){
		sprites.push(tile);
	}, this);
	sprites.forEach(function(tile){
		this.floorPool.add(tile);
	}, this);
}
Ver ejemplo matando plataformas

Random Platform

create: function() {
...
	this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, 11, 0, 200, -this.levelSpeed);
	this.platformPool.add(this.currentPlatform);
},
uplodad: function(){
	if(this.currentPlatform.length && this.currentPlatform.children[this.currentPlatform.length-1].right < this.game.world.width) {
		this.createPlatform();
	}
}
loadLevel: function(){
	this.createPlatform();
},
createPlatform: function(){
    var nextPlatformData = this.generateRandomPlatform();
    ...
generateRandomPlatform: function(){
	var data = {}
	var minSeparation = 60;
	var maxSeparation = 200;
	data.separation = minSeparation + Math.random() * (maxSeparation - minSeparation);
	var minDifY = -120;
	var maxDifY = 120;

	data.y = this.currentPlatform.children[0].y + (minDifY + Math.random() * (maxDifY - minDifY));
	data.y = Math.max(150, data.y);
	data.y = Math.min(this.game.world.height - 50, data.y);

	var minTiles = 1;
	var maxTiles = 5;
	data.numTiles = minTiles + Math.random() * (maxTiles - minTiles);
	return data;
}
Random platform

Coins (cada plataforma tiene su propio pool de monedas)

this.coinsPool = this.add.group();
this.coinsPool.enableBody = true;
...
create: function() {
	...
	this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, 11, 0, 200, -this.levelSpeed, this.coinsPool);
...

createPlatform: function(){
	...
	if(!this.currentPlatform){
		this.currentPlatform = new MrHop.Platform(this.game, this.floorPool, nextPlatformData.numTiles, this.game.world.width + nextPlatformData.separation, nextPlatformData.y, -this.levelSpeed, this.coinsPool);
	}
Platform.js
MrHop.Platform = function(game, floorPool,numTiles, x, y, speed, coinsPool) {
...
	this.coinsPool = coinsPool;
}

MrHop.Platform.prototype.prepare = function(numTiles, x, y,speed){
...
	this.addCoins(speed);
}
//Para cada tile, decidiremos si tiene o no moneda encima
MrHop.Platform.prototype.addCoins = function(){
	var coinsY = 90 + Math.random() * 110;
	var hasCoin;
	this.forEach(function(tile){
		var hasCoin = Math.random() <=0.4;
		if(hasCoin){
			var coin = this.coinsPool.getFirstExists(false);
			if(!coin){
				coin = new Phaser.Sprite(this.game, tile.x, tile.y - coinsY, 'coin');
				this.coinsPool.add(coin);
			}else{
				coin.reset(tile.x, tile.y - coinsY);
			}
			coin.body.velocity.x = speed;
			coin.body.allowGravity = false;
		}

	}, this)
}
Ver ejemplo uso de monedas

Kill Coins

Añadimos el siguiente código al método update

//kill coins that leave the screen
	this.coinsPool.forEachAlive(function(coin){
		if(coin.right <= 0) {
		coin.kill();
	}
}, this);
Ver ejemplo matar monedas

Collecting coins

create : function(){
...
	this.coinSound = this.add.audio('coin');
},
update: function(){
...
	this.game.physics.arcade.overlap(this.player, this.coinsPool, this.collectCoin, null, this);

collectCoin: function(player, coin){
	coin.kill();
	this.myCoins++;
	this.coinSound.play();
}
Collecting coins

Animated Background

create: function() {
	this.background = this.add.tileSprite(0, 0, this.game.world.width, this.game.world.height, 'background');
	this.background.tileScale.y = 2;
	this.background.autoScroll(-this.levelSpeed/6, 0);
	this.game.world.sendToBack(this.background);
Ver ejemplo background animado

Moving Water

this.water = this.add.tileSprite(0, this.game.world.height - 30, this.world.width, 30, 'water');
this.water.autoScroll(-this.levelSpeed/2, 0);
Ver ejemplo agua moviendose

Game Over

update: function() {
...
	if(this.player.alive){
		if(this.player.top >= this.game.world.height || this.player.left <= 0){
			this.gameOver();
		}
	}
...
gameOver: function(){
	this.player.kill();
	this.restart();
}
restart: function(){
	this.game.state.start('Game');
}
Ver ejemplo game over

Counting coins

init: function(){
	this.myCoins = 0;
},
create: function(){
	...
	var style = {font: '30px Arial', fill: '#fff'};
	this.coinsCountLabel = this.add.text(10, 20, '0', style);
},
...
collectCoin: function(player, coin){
	...
	this.coinSound.play();
},
Ver ejemplo conteo de monedas

Game Over Overlay

gameOver: function(){
	this.player.kill();
	this.overlay = this.add.bitmapData(this.game.width, this.game.height);
	this.overlay.ctx.fillStyle = '#000';
	this.overlay.ctx.fillRect(0,0,this.game.width, this.game.height);
	this.panel = this.add.sprite(0, this.game.height,this.overlay);
	this.panel.alpha = 0.5;
	var gameOverPanel = this.add.tween(this.panel);
	gameOverPanel.to({y:0}, 500);

	gameOverPanel.onComplete.add(function(){
		this.water.stopScroll();
		this.background.stopScroll();

		var style = {font:'30px Arial', fill:'#fff'};
		this.add.text(this.game.width/2, this.game.height/2, 'GAME OVER', style).anchor.setTo(0.5);

		style = {font:'20px Arial', fill:'#fff'};
		this.add.text(this.game.width/2, this.game.height/2 + 50, 'High Score'+this.highScore, style).anchor.setTo(0.5);
		this.add.text(this.game.width/2, this.game.height/2 + 80, 'Your Score'+this.myCoins, style).anchor.setTo(0.5);

		style = {font:'10px Arial', fill:'#fff'};
		this.add.text(this.game.width/2, this.game.height/2 + 120, 'Tap to play again', style).anchor.setTo(0.5);

		this.game.input.onDown.addOnce(this.restart, this);
	}, this);
	//this.restart();
	gameOverPanel.start()
},
Ver ejemplo game over overlay
icono de mandar un mail¡Contacta conmigo!
contacta conmigoPablo Monteserín

¡Hola! ¿En qué puedo ayudarte?