Galería de tiro con PhaserJS

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

En este videojuego, los enemigos irán apareciendo desde los extremos laterales de la pantalla y entrando en el area del juego. El jugador debe pulsar sobre ellos. Si acierta, el enemigo explotará.

Aquí tenemos una versión sencilla del juego, aunque podríamos añadirle más complicación fácilmente. Podríamos hacer que la velocidad de los enemigos aumente en función del tiempo, o de cuantos hemos matado, podríamos tener un marcador de puntos, podríamos tener vidas, etc.

Pasos en la resolución del juego de galería de tiro con PhaserJS

Cargar fondo

Cargamos la imagen de fondo del juego. Para ello, utilizamos el código correspondiente para precargar la imagen en Boot.js y la ubicamos en pantalla en el método create de la escena Game.

createSprite();

Vamos a añadir el sprite de uno de los enemigos, la oruga o Caterpillar. Este enemigo no es una imagen estática, sino que es una imagen compuesta de varias imágenes que iremos mostrando de manera secuencial para lograr un efecto de animación.

Para ello, en el fichero Boot.js cargaremos un spritesheet en lugar de una imagen estática. El spritesheet es una imagen compuesta por varios frames. Las propiedades frameWidth y frameHeight definirán el tamaño de cada uno de estos frames.

Después de precargar el spritesheet, en el método create vamos a definir la animación que lo utilizará. En el código en la función anims.create hemos definido los siguientes parámetros:

  • Con la propiedad key hemos identificado esta animación como caterpilllarWalk.
  • Con la propiedad frames hemos especificado los frames de la spritesheet que componen la animación. En este caso, todos los frames de la spritehseet sirven para hacer la animación de andar, pero podría ocurrir que en la misma spritesheet hubiese frames de diferentes animaciones.
  • Con la propiedad frameRate indicamos los frames por segundo que tendrá la animación.
  • Con la propiedad repeat indicamos cuantas veces se va a repetir la animación. Como hemos utilizado el valor -1, la animación se repetirá indefinidamente.

/src/Boot.js

preload() {
	...
	this.load.spritesheet('caterpillar', "/assets/images/caterpillar.png", {
		frameWidth: 96,
		frameHeight: 192
	});
};

create() {
	this.anims.create({
		key: 'caterpillarWalk',
		frames: this.anims.generateFrameNumbers('caterpillar', {start: 0, end: 3}),
		frameRate: 7,
		repeat: -1,
	});
}

En la escena Game vamos a utilizar la animación recien cargada. La función createSprite() coloca en pantalla un sprite que tendrá físicas. Es decir que podrá colisionar contra otros objetos, tener velocidad, etc. A este sprite con físicas que he llamado obj en el código le he añadido una velocidad (setVelocity) y he indicado que se reproduzca la animación caterpillarWalk.

/src/game.js

create() {
	...
	this.createSprite();
}

createSprite(){
	const obj = this.physics.add.sprite(0,100, 'caterpillar');
	obj.setVelocity(200,0)
	obj.play('caterpillarWalk');
}

Para que el sprite con físicas pueda añadirse correctamente a la pantalla, debemos indicar en el fichero main.js que vamos a usar físicas de tipo Arcade. Las físicas de tipo arcade son las que menos recursos consumen. En ellas, las cajas de colisión son siempre rectangulares.

/src/main.js

const config = {
	type: Phaser.AUTO,
	width: 960,
	height: 640,
	scene: Game,
	physics: {
		default: 'arcade'
	},
};

Bichos aleatorios

Hasta ahora, estabamos instanciando siempre una oruga. En este paso vamos a instanciar una de las tres variadades de enemigos disponibles. Para ello, tendremos un array con los nombres de cada uno de los tipos de enemigo y generaremos un número aleatorio en función del cual utilizaremos un sprite u otro. Luego, utilizando ese nombre de sprite más la palabra Walk, activaremos la animación correspondiente.

createSprite(){
	...
	const bugs = ['caterpillar', 'ant', 'bee'];
	const random = Math.floor(Math.random()*bugs.length);
	const spriteName = bugs[random];
	...
 	obj.play(spriteName + 'Walk');
}

Varios sprites

Queremos crear un enemigo nuevo cada segundo. Para ello utilizaremos la siguiente función al final de la función createSprite(), que llamará a la propia función al cabo de 1000 milisegundos.

this.time.delayedCall(1000, () => {
      this.createSprite();
}, [], this);

Carga en posiciones aleatorias

Ahora mismo los enemigos estan saliendo siempre del mismo sitio. Queremos que a veces salgan del lado derecho de la pantalla y a veces del lado izquierdo. También queremos que los enemigos salgan desde diferentes alturas (diferentes valores de y).

Para conseguir que los enemigos salgan de ambos lados de la pantalla, crearemos un número aleatorio. Si es es mayor que 0.5, el enemigo saldrá de un extremo de la pantalla. Si no, saldrá del otro.

createSprite(){
        const gameWidth = this.sys.game.config.width;
        const gameHeight = this.sys.game.config.height;

	const y = Math.floor(Math.random() * gameHeight);
        const x = Math.random() > 0.5 ? gameWidth + 100 : -100;
        const direction = x == -100 ? 1 : -1;

	const obj = this.physics.add.sprite(x,y, spriteName);
	...
	obj.setVelocity(direction *200,0);
}

Los bichos deben mirar en la dirección del desplazamiento

Ahora mismo los bichos siempre miran hacia el mismo lado. El objetivo es que miren en la dirección en la que estan andando. Para ello, en función de la variable direction, haremos un flip que volteará la imagen para que mire en la dirección deseada.

createSprite(){
	...
	if(direction == -1){
		obj.flipX = true;
	}
}

Detectar pulsación

Vamos a detectar cuando pulsamos sobre un sprite. Para ello, le añadiremos el evento pointerdown, de manera similar a como hacíamos con las caras que el juego de acierta imagen.

createSprite(){
	...
	const obj = this.physics.add.sprite(x, y, spriteName).setInteractive().on('pointerdown', this.clickedBug);
	...
}

clickedBug(){
	alert('bicho pulsado');
}

Explosión

La animación de explosión se activará cuando pulsemos sobre alguno de los enemigos. Como todas las animaciones, la precargaremos en el preload de Boot.js y luego en el create definiremos las características de la animación.

/src/scenes/Boot.js

preload(){
	...
	this.load.spritesheet('crash', crash, {
		frameWidth: 199,
		frameHeight: 200
	});
}

create(){
	...
	this.anims.create({
		key: 'crashAnim',
		frames: this.anims.generateFrameNumbers('crash', {start: 0, end: 4}),
		frameRate: 7
	});
}

Debemos añadir, en el método que realizó la creación del sprite del enemigo el código que detectará cuando pulsamos sobre él y llamará a la función clickedBug, que deshabilitará las físicas del enemigo para que deje de moverse y activará la animación de explosión.

createSprite(){
	...
	...on('pointerdown', () => this.clickedBug(obj));
	...
}

clickedBug(bug){
	// Sólo queremos deshabilitar y activar la animación si el cuerpo esta deshabilitado, ya que sino, la animación se ejecutaría varias veces
        if (bug.body.enable) {
            bug.disableBody();
            bug.play('crashAnim');
        }
}

La explosión desaparece cuando ha concluído

Finalmente, para que la explosión no deje en pantalla ningún sprite tras haber concluído, añadiremos un listener que escucha cuando termina cualquier animación y en el caso de que la animación que ha concluído sea crashAnim, eliminaremos totalmente el sprite.

obj.on('animationcomplete', this.animationComplete, this);
...
animationComplete(animation, frame, sprite) {
	if (animation.key === 'crashAnim') {
		sprite.destroy();
	}
}

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