Primeros pasos en Phaser

Nuestro primer juego en Phaser


En este artículo vamos a organizar un pequeño ejemplo simple con Phaser, para entender como funciona y ver los primeros pasos que debemos realizar. Más adelante, profundizaremos más en ellos, pero de momento, nos servirá de primera toma de contacto.

El archivo index.html

Phaser, al fin y al cabo, es un juego web, y como cualquier página web, lo primero que debe cargar es un fichero HTML. En nuestro caso, el fichero index.html se encuentra en la carpeta src.

Lo primero que haría sería modificar su etiqueta <title> y ponerle un título al juego:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Mi primer juego Phaser</title>
  <link rel="stylesheet" href="./index.css">
  <script type="module" src="./index.js"></script>
</head>
<body>

  <div class="container"></div>

</body>
</html>

Luego, añade el <div class="container"></div> en la etiqueta <body>. Aunque no es absolutamente necesario, vamos a hacerlo para que esté todo mejor organizado. En el interior de dicha etiqueta, le vamos a decir a Phaser que cree una etiqueta <canvas>, que es donde se va a cargar y dibujar todo lo referente al juego con Phaser.

Observa que tenemos enlazado un fichero JS y un fichero CSS, que serán nuestros siguientes pasos.

Configuración del juego

Accedemos a nuestro fichero index.js y lo primero que vamos a hacer es crear un objeto de configuración, donde le indicaremos los detalles principales del juego. Observa que estoy creando un objeto config, donde indicaremos varios detalles:

import Phaser from "phaser";

/** @type {Phaser.Types.Core.GameConfig} **/
const config = {
  type: Phaser.AUTO,
  parent: document.querySelector(".container"),
  width: 800,
  height: 600,
  scene: [],
  title: "Primer juego de Phaser",
  version: "1.0"
};

const game = new Phaser.Game(config);

La línea de comentario es un JSDoc. Le indica a Javascript que estamos creando un fichero de configuración de Phaser. De esta forma, al mover el ratón por encima de las propiedades tendremos información en el autocompletado. Además, al añadir nuevas propiedades, si pulsamos CTRL+SPACE nos sugiere las propiedades.

Propiedad Tipo Descripción
width / height / Ancho y alto del juego. Resoluciones comunes: 800x600 o 1024x768.
type Tipo de renderizado: Phaser.AUTO, Phaser.CANVAS o Phaser.WEBGL.
parent / El id o el propio elemento del DOM donde se creará el <canvas>.
canvas / Si lo prefieres, puedes indicar directamente el <canvas>.
title / version Título y versión del juego.
disableContextMenu Si queremos deshabilitar el menú contextual (botón derecho de ratón).
autoCenter Centra el <canvas> en la mitad de la pantalla.
pixelArt Hace algunas optimizaciones para juegos pixel art.
backgroundColor / El color de fondo del canvas del juego. Por ejemplo: 0x000000 o #000000.
scene Define la escena o las escenas del juego.

Aquí he colocado las principales propiedades. En la documentación oficial, tienes más propiedades detalladas: Phaser.Types.Core.GameConfig.

Mi primera escena

Observa que en la configuración anterior, tenemos la propiedad scene con un array vacío. Vamos a reemplazarlo por [Screen] y a crear nuestra primera escena. Seguimos en el fichero index.js y debajo del import vamos a crear una clase con nuestra escena:

import Phaser from "phaser";

class Screen extends Phaser.Scene {
  /* ... */
}

const config = {
  /* ... */
  scene: [Screen],
};

const game = new Phaser.Game(config);

Observa que Screen es una clase que extiende Phaser.Scene, las escenas genéricas de Phaser. Ahora tenemos que implementar el funcionamiento de dicha escena. Vamos a hacer una escena muy sencilla que mostrará la siguiente imagen phaser.png y unos textos debajo:

Phaser Logo

Para ello, vamos a implementar el método preload() para precargar la imagen (necesario) y luego, en el método create() la mostramos y creamos los textos. Estos dos métodos forman parte del ciclo de vida de una escena de Phaser y profundizaremos más en ellos más adelante:

class Screen extends Phaser.Scene {
  preload() {
    this.load.image("logo", "assets/sprites/phaser.png");
  }

  create() {
    this.add.image(400, 300, "logo");

    this.add.text(0, 550, "Mi primer juego de Phaser", {
      font: "16px Courier",
      fill: "#ffffff",
      align: "center",
      fixedWidth: 800
    });
  }
}

Una de las partes fundamentales de Phaser, es que solemos trabajar haciendo referencia a this, que dentro de una clase, se refiere a la propia escena. La escena tiene un objeto load con un método image() que permite precargar una imagen asignándole un nombre o id. A partir de ahora, nos referimos a ella solo por ese id.

Luego, en el método create(), estamos haciendo referencia al objeto add que tiene un método image() para añadir una imagen a la escena que estamos construyendo. Observa que aquí le pasamos las coordenadas x e y, y el id de la imagen a añadir.

Finalmente, observa que hacemos lo propio para los textos. En el objeto add de la escena, llamamos al método text(), que añadirá un texto en las coordenadas x e y indicadas, con las opciones suministradas en el objeto: tipografía, color, alineación y ancho fijo.

Con esto tenemos una escena báica construida. Si has colocado un tamaño de 800x600, habrás comprobado que la imagen no cabe en pantalla. Para solucionarlo, en la línea donde añadimos la imagen, lo reemplazamos por lo siguiente:

  this.add.image(400, 300, "logo").setScale(0.75);

Esto significa que va a añadir la imagen, pero que va a escalarla para que tenga un tamaño del 75% de su tamaño original (los valores van de 0 a 1). También podríamos usar el método .setSize()

Organizar escenas

Ten en cuenta que, por simplicidad, hemos colocado todo en el index.js. Sin embargo, esto no es recomendable, ya que a medida que el juego se haga más grande, se volverá muy difícil de mantener. Lo mejor es organizarlo en carpetas y archivos separados, como explicamos en Estructura de carpetas.

Por lo tanto, lo ideal de cara al futuro sería tener algo así:

import Phaser from "phaser";
import { Screen } from "@/scenes/Screen.js";

const config = {
  /* ... */
  scene: [Screen],
};

const game = new Phaser.Game(config);

Luego, en nuestro fichero /scenes/Screen.js sólo tenemos añadir export antes de class para exportar esa estructura de datos y poder tomarla desde el archivo principal. De esta forma, todo queda mucho mejor organizado:

import Phaser from "phaser";

const SPRITES_PATH = "assets/sprites";

export class Screen extends Phaser.Scene {
  preload() {
    this.load.image("logo", `${SPRITES_PATH}/phaser.png`);
  }

  create() {
    /* ... */
  }
}

Observa que he separado la ruta de la carpeta de los sprites en una constante, para que sea mucho más fácil de modificar si esa ruta cambia en el futuro. Otra buena práctica sería organizar un fichero de constantes externo para trabajar con ellas, pero de eso hablaremos más adelante.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev