En este artículo aprenderás a realizar grupos de elementos con el motor de juegos Javascript Phaser 3 y aplicarles comportamientos y configuraciones comunes.
En el Manual de Phaser hemos ido aprendiendo muchas cosas sobre el motor de juegos Javascript. Ahora ha llegado el momento de colocar los bloques que se deben romper con la bola en nuestro juego clon del Arkanoid, lo que nos permitirá llegar a un estado del juego casi definitivo.
Esta parte nos ayudará a entender una de las posibilidades más útiles de Phaser, que es la creación de grupos de elementos. Estos grupos son tan interesantes porque permiten definir de una única vez acciones y comportamientos que compartirán todos los elementos del grupo, lo que ayuda mucho a la hora de codificar.
Por supuesto, en nuestro juego del Arkanoid (también conocido como Breakout), todos los bloques formarán parte de un mismo grupo, ya que su función es exactamente igual.
Cómo crear un grupo
Los grupos en Phaser se crean en el método create() de las escenas. Para ello existen diversos métodos según el tipo de elementos que queremos insertar en los grupos.
En nuestro caso los bloques son elementos estáticos, que no van a moverse, solamente aparecer y desaparecer cuando la bola choque con ellos. Como tienen que responder a las colisiones necesitamos hacelos mediante los métodos de físicas.
Este sería un modo de hacer un grupo.
this.miGrupo = this.physics.add.staticGroup();
Luego podemos insertar elementos dentro del grupo, con el método create, pero asociado al grupo que se acaba de generar.
this.miGrupo.create(54, 44, 'elementocargado');
this.miGrupo.create(75, 32, 'elementocargado');
Como puedes ver, indicamos la posición del elemento que va a colocarse dentro del grupo y luego el identificador del elemento que hemos cargado previamente en el preload().
Crear grupos y sus elementos mediante un mismo método
Sin embargo, en muchas ocasiones no es práctico crear todos los elementos de esta manera, agregando uno a uno al grupo. Simplemente porque habitualmente los elementos se colocan según un patron determinado. En estos casos podemos usar una estrategia distinta, que consiste en crear el grupo y sus elementos en un único paso.
Esto es justamente lo que hemos hecho para colocar de una vez todos los bloques de nuestro juego.
this.bricks = this.physics.add.staticGroup({
key: ['bluebrick', 'orangebrick', 'greenbrick', 'blackbrick'],
frameQuantity: 10,
gridAlign: {
width: 10,
height: 4,
cellWidth: 67,
cellHeight: 34,
x: 112,
y: 100
}
});
Como puedes ver, el método para crear el grupo es el mismo: staticGroup(), sin embargo esta vez lo hemos configurado mediante un objeto. Ese objeto podría tener varios formatos distintos, así que toma este ejemplo como una de las muchas posibilidades que este método nos ofrece.
Explico los distintos elementos de este objeto de configuración del grupo:
- key: hemos colocado un array con todos los identificadores de las imágenes de los bloques previamente cargadas.
- frameQuantity: este es el número de elementos para cada uno de las llaves del grupo. Es decir, colcaremos 10 bloques azules, 10 naranjas, 10 verdes y 10 negros.
- gridAlign: este método permite posicionar los elementos en una rejilla, muy úti para nuestra primera pantalla. Esta rejilla tiene los siguientes valores:
- width: La anchura en columnas de la rejilla
- height: La altura en filas de la rejilla
- cellWidth: Este es el tamaño de la celda de rejilla de anchura, en píxeles. No tiene que ver con el tamaño de lo que metas dentro. Si lo que metes es menor, como es nuestro caso, simplemente te sobrará algo de espacio.
- cellHeight: Los píxeles de altura de la celda de la rejilla
- x: La posición del primer elemento de la rejilla, en la horizontal
- y: La posición del primer elemento de la rejilla, en la vertical.
Hay un tema Importante con respecto a las imágenes declaradas en el array "key": las debemos haber declarado en el método preload(), con un código como este:
this.load.image('bluebrick', 'images/brickBlue.png');
this.load.image('blackbrick', 'images/brickBlack.png');
this.load.image('greenbrick', 'images/brickGreen.png');
this.load.image('orangebrick', 'images/brickOrange.png');
Colisiones con el grupo
Como habíamos comentado, lo bueno de los grupos es que permiten configuraciones comunes para todos los elementos. Es el caso de las colisiones: en vez de definir la colisión para cada bloque a romper, podemos definirla de una única vez para todo el grupo.
Por lo demás, las colisiones se definen con el mismo método collider que ya conocemos, en este caso entre la bola y el grupo de bricks que acabamos de crear.
this.physics.add.collider(this.ball, this.bricks, this.brickImpact, null, this);
El método brickImpact() se encargará de definir qué pasa cuando se produzca una colisión. Nuestro objetivo es simplemente borrar el bloque que acaba de impactar la bola.
brickImpact(ball, brick) {
brick.disableBody(true, true);
}
Gracias a que el método callback que se ejecuta por la colisión nos entrega como parámetro el brick implicado en esta colisión, podemos eliminarlo del juego con el método disableBody().
Agregar puntos a nuestro marcador cuando quitas bloques
Ahora vamos a hacer un poco más complejo el método que implementa la colisión, para asignar nuevos puntos al marcador de puntos del juego.
Aunque para facilitarlo hemos creado un método especial que incrementa los puntos y actualiza el marcador.
brickImpact(ball, brick) {
brick.disableBody(true, true);
this.increasePoints(10);
}
El método increasePoints(), que también vamos a crear dentro de la clase Game, sería el siguiente.
increasePoints(points) {
this.score += points;
this.scoreText.setText('PUNTOS: ' + this.score);
}
No tiene nada nuevo que contar, pues ya vimos cómo gestionar mensajes de texto en el juego.
Mostrar un mensaje cuando se eliminan todos los bloques
Para acabar, mostraremos un mensaje de felicitaciones cuando el jugador haya conseguido terminar con todos los bloques.
Lo haremos por medio de una imagen, de manera similar a como mostrábamos el mensaje de game over. Así que debemos comenzar por ver la precarga de esa imagen en el método preload():
this.load.image('congratulations', 'images/congratulations.png');
Luego la colocación de la imagen en el método create(), teniendo cuidado de hacer que la imagen no sea visible de entrada:
this.congratsImage = this.add.image(400, 90, 'congratulations');
this.congratsImage.visible = false;
Por último mostramos la imagen cuando ya no quedan bloques por destruir en el juego. Para saberlo tenemos un método en el grupo que nos dice cuántos bloques están activos: countActive().
brickImpact(ball, brick) {
brick.disableBody(true, true);
this.increasePoints(10);
if (this.bricks.countActive() === 0) {
this.congratsImage.visible = true;
this.scene.pause();
}
}
Apreciarás que, además de mostrar el mensaje de felicitaciones, pausamos la escena para que la bola no siga moviéndose por la pantalla.
Ocultar todos los elementos de un grupo a la vez
Hay otra modificación del código del juego que merece la pena comentar. Resulta que, cuando perdemos el juego y nos aparece la imagen de game over deseamos ocultar de una vez todos los ladrillos, porque si no la imagen queda un poco extraña al aparecer encima de los ladrillos. Es solo un detalle, pero que nos sirve para aprender algo nuevo.
Básicamente, cuando tenemos un grupo y queremos que todos sus elementos se vuelvan invisibles, no te vale con modificar una propiedad "visible" como pasaba con una imagen simple. Ahora hay un método específico para hacer esta labor.
this.bricks.setVisible(false);
Esta línea de código la hemos colocado cuando se da el juego por perdido, es decir, al irse la bola por la parte inferior de la pantalla.
Conclusión
El juego ha avanzado una barbaridad. Ahora sí que es un auténtico Arkanoid o Breakout. Espero que los resultados estén siendo satisfactorios para ti.
Para tu referencia, con todas las mejoras incorporadas en este artículo, el código nos ha quedado como podemos ver en este enlace de gitHub.
Ahora mismo ya tenemos un juego que podríamos casi terminado, aunque lo cierto es que nos quedan muchas ideas de mejoras que nos darán pie a aprender más sobre Phaser. En el siguiente artículo veremos como crear algunos comportamientos extra que incluyen el trabajo con la escena del juego, lo que nos permitirá reiniciarla cuando la partida acabe.
Videotutorial grupos de sprites en Phaser
En este videotutorial veremos de manera práctica todo lo explicado en este artículo, para los que os gusta aprender de manera visual. Veremos cómo crear grupos de sprites en Phaser y cómo gestionar las colisiones para implementar sus comportamientos.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...