Flappy Bird con Phaser

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

Pasos en la realización del juego de Flappy Bird con PhaserJS

Cargar fondo

Cargamos la imagen de fondo de la escena principal. Como siempre, la precargamos en el preload de Boot.js y la ponemos en pantalla en el create de Game.js.

Héroe con físicas y animación

Comenzamos colocando al jugador en escena. El jugador es un muñequito que tendrá la animación de volar, así que en el fichero Boot.js precargamos un spritesheet con dicha animación y definimos la correspondiente animación.

Luego, en la escena Game, ubicamos al personaje en pantalla y activamos su animación. Debemos tener en cuenta que el jugador tiene físicas, que habrá que activar en el fichero main.js.

/src/scenes/Boot.js

preload() {
	...
	this.load.spritesheet('hero', hero, {frameWidth: 50, frameHeight: 50});
}

create(){
	this.anims.create({
		key: 'fly',
		frames: this.anims.generateFrameNumbers('hero', {start: 0, end: 1}),
		frameRate: 10,
		repeat: -1,
	});
}

/src/scenes/Game.js

create() {
	this.player = this.physics.add.sprite(50, 100, 'hero');
	this.player.play('fly');
}
main.js
physics: {
	default: 'arcade',
	arcade: {
		debug: true,
		gravity: {
			y: 300,
		},
	},
},

Saltar con space

Cada vez que el jugador pulse la tecla espacio (representada con el código 32), llamaremos a la función jump, que aplicará al jugador una velocidad vertical que hará que ascienda ligeramente.

create(){
	...
	this.input.keyboard.on('keydown', (event) => {
		if (event.keyCode === 32) {
			this.jump();
		}
	});
}

jump() {
	this.player.setVelocityY(-200);
}

Saltar con click en la pantalla

La idea es que este juego se pueda jugar también en el móvil. Para ello, otra forma de saltar será pulsar sobre la pantalla. Por tanto, añadimos el evento pointerdown a la escena, para que cuando la pulsemos el jugador salte.

this.input.on('pointerdown', () => this.jump());

Animación al saltar

Vamos a congelar la animación de volar cuando el jugador salte. Para ello, definiremos una nueva animación llamada jump que sólo tendrá un frame (start:2, end:2). De esta forma, dará la sensación de que el jugador queda suspendido en el aire.

/src/scenes/Boot.js

create(){
	...
	this.anims.create({
		key: 'jump',
		frames: this.anims.generateFrameNumbers('hero', {start: 2, end: 2}),
		frameRate: 7,
		repeat: 1,
	});
}

Dentro del método jump, activamos la animación que acabamos de definir.

/src/scenes/Game.js

jump() {
	this.player.setVelocityY(-200);
	this.player.play('jump');
}

Seguir volando

En el paso anterior indicamos que la animación se ejecutase a una velocidad de 7 frames por segundo (frameRate). En este paso vamos a añadir un listener que detectará cuando una animación termina y vamos a evaluar que la animación que ha terminado es jump. En ese caso, volvemos a ejecutar la animación de volar para que el jugador continue volando.

create(){
	...
	this.player.on('animationcomplete', this.animationComplete, this);
}

animationComplete(animation, frame, sprite) {
	if (animation.key === 'jump') {
		this.player.play('fly');
	}
}

Las tuberías

El jugador debe ir colándose por entre los huecos de unas tuberías que avanzan horizontalmente de derecha a izquierda.

Para lograr esto, el método newPipe recorrerá las 8 posiciones que compondría una tubería que ocupase todo el alto de la pantalla. Pero para que la tubería tenga un hueco por el que pasar, generaremos una variable gap que tendrá un número aleatorio con la posición del hueco que vamos a dejar. Para que el jueco sea más grande, por encima y por debajo de la posición del mismo también dejaremos un espacio sin tubería.

Tras ubicar todos los trozos de tubería, le aplicaremos ciertas características como la velocidad, la detección de los límites de la pantalla y la destrucción de la tubería cuando los exceda.

Además, queremos crear una nueva tubería a cada segundo, así que iremos llamando a la propia función que crea las tuberías mediante la función delayedCall.

newPipe() {
	//Una tubería un grupo de cubos
	const pipe = this.physics.add.group();
	//Cada tubería tendrá un hueco (zona en la que no hay cubos) por dónde pasará el super héroe
	const gap = Math.floor(Math.random() * 5) + 1;
	for (let i = 0; i < 8; i++) {
    	//El hueco estará compuesto por dos posiciones en las que no hay cubos, por eso ponemos hueco +1
		if (i !== gap && i !== gap + 1 && i !== gap - 1) {
			const cube = pipe.create(960, i * 100, 'pipe0');
			cube.body.allowGravity = false;
		}
	}
	pipe.setVelocityX(-200);
	//Detectaremos cuando las columnas salen de la pantalla...
	pipe.checkWorldBounds = true;
	//... y con la siguiente línea las eliminaremos
	pipe.outOfBoundsKill = true;
	//Cada 1000 milisegundos llamaremos de nuevo a esta función para que genere una nueva columna
	this.time.delayedCall(1000, this.newPipe, [], this);
}

Colisión con tuberías

En el caso de que el jugador colisiones contra una tubería, mostraremos un mensaje de alerta con el texto Game Over. Para esto, el método overlap recibe como parámetros de entrada el objeto player, la tubería y la función que llamaremos cuando ambos colisionen.

newPipe(){
	...
	this.physics.add.overlap(this.player, pipe, this.hitPipe, null, this);
}

hitPipe() {
	alert('Game Over');
}

Poner tapas a las tuberías

Entre las imágenes suministradas, se encuentran unas imágenes de tapitas que podemos colocar en los extremos de las tuberías, al borde de los huecos que estamos dejando. Si el hueco ocupa las posiciones gap, gap+1 y gap-1, la tapita estará al borde de los huecos, es decir en la posiciones gap-2 y gap+2.

newPipe(){
	...
	let cube;
	if (i !== gap && i !== gap + 1 && i !== gap - 1) {
		if (i == gap - 2) {
			cube = pipe.create(960, i * 100, 'pipeUp0');
		} else if (i == gap + 2) {
			cube = pipe.create(960, i * 100, 'pipeDown0');
		} else {
			cube = pipe.create(960, i * 100, 'pipe0');
		}
		cube.body.allowGravity = false;
	}
}

Imagen de tuberías aleatoria

En el juego tenemos dos tipos de tuberías que vamos a colocar. Una es una tubería sencilla y la otra tiene forma de estrella. Iremos colocando una tubería u otra aleatoriamente. Para ello, generaremos un número aleatorio y lo concatenaremos en el nombre del sprite que estamos colocando. De esta forma, podremos colocar sprites diferentes dependiendo de la tubería que queremos visualizar.

newPipe(){
	const random = Math.floor(Math.random() * 2);
	for (let i = 0; i < 8; i++) {
		//El agujero son cuatro casillas
		if (i !== gap && i !== gap + 1 && i !== gap - 1) {
			let cube;
			if (i == gap - 2) {
				cube = pipe.create(960, i * 100, `pipeUp${random}`);
			} else if (i == gap + 2) {
				cube = pipe.create(960, i * 100, `pipeDown${random}`);
			} else {
				cube = pipe.create(960, i * 100, `pipe${random}`);
			}
			cube.body.allowGravity = false;
		}
	}
}

Escena de fin de juego

Hasta ahora, cuando el jugador pierde la partida estabamos mostrando un mensaje de alerta con el texto Game Over. En este paso, lo que haremos en sustitución de mostrar un mensaje de alerta será cargar la escena de partida perdida.

Fondo animado

El videojuego quedará mejor si la imagen de fondo del espacio se va desplazando horizontalmente. La imagen suministrada ya esta preparada para que esto ocurra de manera fluida, ya que en esta imagen, la parte de la derecha coincide con la de la izquierda, y puede verse una transición animada sin saltos.

create() {
       this.bg = this.add
       .tileSprite(0, 0, gameWidth, gameHeight, "back")
       .setOrigin(0)
       .setScrollFactor(0);
	...
}

update(time){
	this.bg.tilePositionX = time * 0.1;
}

Detectar cuando el player sale de la pantalla

Si el jugador sale de la pantalla, ya sea por arriba o por abajo de la misma, cargaremos la escena de Game Over. Para detectar cuando el jugador sale de la pantalla, debemos añadirle la capacidad de colisionar contra los límites de la pantalla y establecer que cuando algo colisione contra ellos, se cargue la escena de perder.

create(){
	...
	this.physics.world.on('worldbounds', (body) => {
		this.scene.start('Game Over');
	});

	this.player.setCollideWorldBounds(true);
	this.player.body.onWorldBounds = true;
}

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.