Flappy Bird con Phaser

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

Cargar fondo

Cargamos la imagen de fondo de la escena principal.

Héroe con físicas y animación

class FlappyBird extends Phaser.Scene {
	preload() {
		...
		this.load.spritesheet('hero', hero, {frameWidth: 50, frameHeight: 50});
	}

	create() {
		this.player = this.physics.add.sprite(50, 100, 'hero');

		this.anims.create({
			key: 'fly',
			frames: this.anims.generateFrameNumbers('hero', {start: 0, end: 1}),
			frameRate: 10,
			repeat: -1,
		});
		this.player.play('fly');
	}
}
main.js
physics: {
	default: 'arcade',
	arcade: {
		debug: true,
		gravity: {
			y: 300,
		},
	},
},

Saltar con space

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

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

Saltar con click en la pantalla

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

Animación al saltar

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

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

Seguir volando

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

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

Las tuberías

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

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

hitPipe() {
	alert('game over');
}

Poner tapas a las tuberías

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

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

Creamos la escena a la que iremos cuando perdamos.

Fondo animado

create() {
	this.bg = this.add.tileSprite(480, 320, 960, 640, 'back').setScrollFactor(0);
	...
}

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

Detectar cuando el player sale de la pantalla

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

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