Pong con Phaser

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

El videojuego original del Pong fue creado en 1972 por la empresa Atari, pionera en la industria del videojuego. Su creador fue el ingeniero Al Alcorn.

¿Cómo se juega?

En este videojuego dos jugadores se enfrentan en una cancha virtual. Cada jugador controla una pala ubicada en los extremos de la pantalla. En el videojuego que vamos a desarrollar estas palas han sido sustituidas por manos. Una pelota rebotará al colisionar contra las palas y los bordes de la cancha. Tenemos que intentar que el rival no llegue a tiempo a golpear la pelota y esta se pierda por su lado de la pantalla.

Pasos en la realización del juego Pong con PhaserJS

Primeros pasos

Lo primero que haremos será cargar la imagen de fondo y cargar el spritesheet de la pelota brillando. Ambas cosas (cargar una imagen y cargar un spritesheet) las hemos realizado en ejercicios anteriores.

La pelota gira

Vamos a hacer que la pelota, ademas de brillar utilizando el spritesheet del paso anterior, de vueltas. Para ello, utilizaremos el siguiente código (teniendo en cuenta que this.ball sería el objeto correspondiente a la pelota que añadimos en el centro de la pantalla en el paso anterior.)

update(){
	this.ball.rotation += 0.1;
}

La pelota se mueve

Para que la pelota se mueva, debe tener físicas. Para lograrlo, las debemos activar en el main.js.

Vamos a definir una función llamada placeBall que colocará la pelota en pantalla, le dará un ángulo inicial en el que comenzará a moverse y una velocidad en la coordenada x e y que estará en función de dicho ángulo y de una velocidad inicial. Para hacer esto, necesitaremos un poco de trigonometría. Conociendo el ángulo en el que se moverá la pelota y la velocidad en la que lo hará podemos obtener los valores de la velocidad que aplicaremos en los ejes x e y.

create(){
	this.speed = 500;
	this.placeBall();
}

placeBall() {
	this.ball.x = this.gameWidth / 2;
        this.ball.y = this.gameHeight / 2;
	let initialAngle = Math.random() * (Math.PI/4 *3 - Math.PI/4)+ Math.PI/4;
	const leftOrRight = Math.floor(Math.random()*2);
	if(leftOrRight === 1)initialAngle = initialAngle + Math.PI;
	const vx = Math.sin(initialAngle ) * this.speed;
	const vy = Math.cos(initialAngle ) * this.speed;
	this.ball.body.velocity.x = vx;
	this.ball.body.velocity.y = vy;
}

Rebote contra las paredes

Para que la pelota pueda rebotar sobre objetos llamaremos al método setBounce. Cuanto mayor sea el parámetro de entrada de este método, mayor será la capacidad de rebote. Además, para que la pelota pueda rebotar contra los límites de la pantalla estableceremos el colliderWorldBound a true.

this.ball.setBounce(1);
this.ball.setCollideWorldBounds(true);

Poner un jugador

El Pongo tendrá dos jugadores representados por el sprite de una mano. Precargaremos la imagen de la mano en la escena Boot.js y la colocaremos en pantalla utilizando físicas, para que la pelota pueda rebotar al colisionar contra ella.

this.hand1 = this.physics.add.sprite(70, 320, 'hand1');

Mover al jugador con el teclado

El objeto this.cursors nos pemitirá evaluar que teclas del teclado va pulsando el jugador. Si pulsa la tecla de ir hacia arriba, desplazaremos la mano 5 unidades hacia arriba y viceversa.

create(){
	...
	this.cursors = this.input.keyboard.createCursorKeys();
}

update() {
	...
	if (this.cursors.up.isDown) {
		this.hand1.y = this.hand1.y - 5;
	} else if (this.cursors.down.isDown) {
		this.hand1.y = this.hand1.y + 5;
	}
}

Controles visuales

Vamos a definir una función llamada visualControls que vincularemos al jugador y que colocará dos botones en pantalla que el jugador podrá pulsar para mover al jugador. Esto permiriá que el jugador pueda jugar utilizando una pantalla tactil como la del móvil, además de utilizando el teclado del ordenador.

this.visualControls({ x: 50, y :50 },{ x :50, y :590 }, this.hand1);
visualControls(btn1, btn2, player) {
	const upbtn = this.add.sprite(btn1.x,btn1.y, 'arrow').setInteractive();
	const downbtn = this.add.sprite(btn2.x, btn2.y, 'arrow').setInteractive();
	downbtn.flipY = true;
}

Funcionalidad de los controles visuales

Los botones que colocamos en el paso anterior no hacen nada. En este paso vamos a añadirles la capacidad de desplazar al jugador. Para ello, vincularemos al objeto player una propiedad llamada verticalDirection utilizando el método setData.

Luego, completaremos el método update para comprobar tanto si el jugador ha pulsado los botones de dirección como el valor de la propiedad Data que hemos seteado, para decidir si debemos desplazar al jugador hacia arriba o hacia abajo.

visualControls(btn1, btn2, player) {
	...
	player.setData('verticalDirection', 0);
	downbtn.on('pointerdown', () => {
		player.setData('verticalDirection', -1);
	});
	upbtn.on('pointerdown', () => {
		player.setData('verticalDirection', 1);
	});
	downbtn.on('pointerup', () => {
		player.setData('verticalDirection', 0);
	});
	upbtn.on('pointerup', () => {
		player.setData('verticalDirection', 0);
	});
}

update(){
	...
	if (this.cursors.up.isDown || this.hand1.getData('verticalDirection') === 1) {
		this.hand1.y = this.hand1.y - 5;
	} else if (this.cursors.down.isDown || this.hand1.getData('verticalDirection') === -1) {
		this.hand1.y = this.hand1.y + 5;
	}
}	

Colisión con el player

Para que pueda existir colisión entre la pelota y el jugador debemos especificar el siguiente collider.

this.physics.add.collider(this.ball, this.hand1);

Ladrillo inamovible

El el paso anterior establecimos la colisión entre la mano y la pelota. Sin embargo, cuando esta se produce, la mano sale desplazada. Estableciendo el cuerpo de la mano como immovable podemos hacer que en caso de colisión la mano no salga despedida.

this.hand1.body.immovable = true;

Dos jugadores

Utilizando un código similar al que utilizamos para el jugador 1, vamos a añadir un segundo jugador, y los controles correspondientes para gestionarlo. Este segundo jugador será controlado con las teclas ASDW.

create(){
	...
	this.hand2 = this.physics.add.sprite(882, 320, 'hand2');
	this.visualControls({x: 910, y: 50}, {x: 910, y: 590}, this.hand2);
	this.physics.add.collider(this.ball, this.hand2);
	this.hand2.body.immovable = true;
	this.cursors2 = this.input.keyboard.addKeys({ up: 'W', left: 'A', down: 'S', right: 'D' });
}

update(){
	...
	if (this.cursors2.up.isDown || this.hand2.getData('verticalDirection') === 1) {
		this.hand2.y = this.hand2.y - 5;
	}else if (this.cursors2.down.isDown || 		
		this.hand2.getData('verticalDirection') === -1) {
		this.hand2.y = this.hand2.y + 5;
	}
}

Problema controles táctiles para varios jugadores

Por defecto Phaser solo detecta la pulsación sobre la pantalla de un único dedo. Esto quiere decir que si el usuario pulsa sobre la pantalla con varios dedos a la vez, solo la primera pulsación será detectada. Afortunadamente, añadir puntos de detección sobre la pantalla es muy fácil.

Debemos añadir al principio del método create la siguiente línea.

this.input.addPointer();

La añadiremos tantas veces como puntos de detección queramos. Como ya tenemos un punto de detección sobre la pantalla, si la añadimos una vez, realmente ahora tendríamos dos puntos de detección. En este juego he decidido que quería cuatro puntos de detección, así que he añadido la línea tres veces:

create() {
	...
	this.input.addPointer();
	this.input.addPointer();
	this.input.addPointer();
}

Perder

Vamos a desactivar la colisión de la escena por la parte izquierda y derecha. De esta forma, si la pelota colisiona contra los laterales, en lugar de rebotar, seguirá avanzando.

También vamos a evaluar si la pelota se pierde por el lado izquierdo (this.ball.x < 0) o derecho (this.ball.x > gameWidth) de la pantalla. Si esto ocurriese, mostraríamos un mensaje de alerta informando que el jugador ha perdido.

create(){
	...
	this.physics.world.setBoundsCollision(false, false, true, true);
}

update(){
	...
	if (this.ball.x < 0) {
		alert('player1 has perdido');
	} else if (this.ball.x > gameWidth) {
		alert('player2 has perdido');
	}
}

Escena de fin de juego

En lugar de mostrar un mensaje de alerta cuando un jugador pierda, mostraremos la escena de fin de juego.

Poner marcador

En lugar de que los jugadores pierdan cuando la pelota se pierda por su lado de la pantalla, vamos a añadir un marcador de puntos que contabilice cuantas veces ha ocurrido eso.

this.showScore();
showScore() {
    this.hand1Score = this.add.text(395, 75, '0', {fontFamily: 'font1', fontSize: 80, color: '#ffffff', align: 'right'}).setOrigin(1, 0);
    this.hand2Score = this.add.text(520, 75, '0', {fontFamily: 'font1', fontSize: 80, color: '#ffffff'});
}

Aumentar el marcador

Ahora que tenemos un marcador, vamos a modificarlo para incrementarlo.

this.hand1Score.setText(parseInt(this.hand1Score.text) + 1);

Ajustar areas de colisión

Queremos que la pelota se introduzca ligeramente en la mano en el momento de la colisión. Para ello, modificaremos su caja de colisión.

this.hand1.setSize(60, 250);

Limitar el rango de movimiento de las manos

Con esta acción haremos que la mano que representa a cada jugador no pueda salir de los límites de la pantalla.

this.hand1.setCollideWorldBounds(true);

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