Phaser 3 y Tiled: construyendo un juego de plataformas

    Introducci贸n

    Phaser 3 nos permite crear juegos r谩pidamente en nuestro navegador con JavaScript. Algunos de nuestros juegos 2D favoritos son plataformas: piensa en juegos como Mario, Sonic, Super Meat Boy o Cuphead.

    Azulejos es un editor de mapas 2D que se utiliza para crear mundos de juegos. Exploraremos c贸mo crear un nivel de plataformas con Tiled, integrarlo con Phaser y animar sprites para crear una rica experiencia de plataformas 2D.

    En este art铆culo crearemos un juego de plataformas b谩sico, donde nuestro jugador puede moverse y saltar en nuestro mundo. Si el jugador golpea un pico, entonces reiniciamos la posici贸n del jugador. Se puede encontrar una demostraci贸n jugable de este juego. Aqu铆.

    Este tutorial est谩 escrito para aquellos familiarizados con Phaser 3. Si no lo est谩, familiar铆cese con el marco con uno de nuestros art铆culos anteriores sobre Phaser.

    Empezando

    Para seguir mejor este tutorial, descargue y descomprima el proyecto Pharos.sh-platformer.zip en su espacio de trabajo. La carpeta debe incluir los siguientes activos:

    • index.html: Carga Phaser 3.17 y nuestro game.js archivo
    • game.js: Contiene la l贸gica de nuestro juego
    • activos / im谩genes:
      • background.png
      • kenney_player.png
      • kenney_player_atlas.json
      • spike.png
    • assets / tilemaps: Carpeta vac铆a, se utilizar谩 para guardar archivos en mosaico
    • activos / conjuntos de mosaicos:
      • platformPack_tilesheet.png

    Nota: Si lo prefiere, tambi茅n puede seguir leyendo el c贸digo del proyecto en nuestra Repositorio de GitHub.

    No olvide ejecutar un servidor en la carpeta de su proyecto, con su IDE o incluso con Python: python3 -m http.server. Esto es necesario para que Phaser pueda cargar estos activos a trav茅s de HTTP. Nuevamente, para obtener m谩s informaci贸n, consulte nuestro art铆culo anterior sobre el tema (vinculado arriba).

    Todos los activos del juego fueron creados y compartidos por Kenney. El archivo atlas fue creado con Empaquetadora Atlas Phaser.

    Editor de mapas en mosaico

    Tiled es un software gratuito y de c贸digo abierto para crear niveles de juego. Est谩 disponible en todos los principales sistemas operativos de escritorio, as铆 que visite el sitio web y desc谩rgalo para continuar.

    Crear un mapa de mosaicos

    Abra Tiled y haga clic en “Nuevo mapa”. En el mensaje, cambie el formato de la capa de mosaico a “Base64 (sin comprimir)”, el ancho a 14 mosaicos y la altura a 7, y el tama帽o de mosaico a 64 px cada uno.

    Guarde el archivo como “level1.tmx” en “assets / tilemaps”.

    Crear un conjunto de mosaicos

    En el panel derecho, haga clic en “Nuevo conjunto de mosaicos …”. En la ventana emergente, nombre el conjunto de mosaicos “kenny_simple_platformer”. Aseg煤rese de que la opci贸n “Insertar en mapa” est茅 seleccionada. Sin esa opci贸n, Phaser puede experimentar problemas al cargar su mapa correctamente. En la propiedad “Fuente”, seleccione “platformPack_tilesheet.png” del directorio “assets / tilesets”.

    El ancho de la imagen de la hoja de mosaico es 896 px y la altura es 448 px. Contiene 98 im谩genes en total de igual tama帽o, todas caben en 7 filas y 14 columnas. Con matem谩ticas b谩sicas podemos deducir que cada mosaico tiene 64 p铆xeles de ancho y alto. Aseg煤rese de que el ancho y la altura del conjunto de mosaicos sea de 64 px:

    Dise帽ando nuestro nivel

    Los mapas en Tiled se componen de capas. Cada capa almacena alg煤n dise帽o del mundo del juego. Las capas que est谩n en la parte superior tienen sus mosaicos que se muestran sobre las capas que est谩n debajo. Obtenemos profundidad us谩ndolos. Este juego b谩sico tendr谩 solo dos capas:

    • Plataforma: contiene el mundo con el que interact煤a el jugador.
    • Picos: contiene los picos peligrosos que pueden da帽ar al jugador.

    La capa de plataforma

    Antes de agregar nuestros mosaicos al mapa, primero cambiemos el nombre de la capa. Los nombres de las capas ser谩n referenciados en nuestro c贸digo de Phaser, as铆 que cambiemos “Tiled Layer 1” a “Platforms”:

    Para crear un nivel, simplemente seleccione un mosaico de su conjunto de mosaicos y haga clic en el lugar donde desea colocarlo en el mapa. Creemos / agreguemos todas nuestras plataformas:

    Picos en la capa de objetos

    En el panel Capas a la derecha de la pantalla, haga clic en el bot贸n “Nueva capa” y seleccione “Capa de objeto”. Nombra la capa “Spikes”.

    En la barra de herramientas superior, seleccione la opci贸n “Insertar objeto”:

    Ahora podemos agregar las fichas de picos del conjunto de fichas:

    隆Hemos creado nuestro nivel de juego! Ahora necesitamos integrarlo con Phaser.

    Carga de un mapa en mosaico

    Phaser no puede leer el .tmx archivo que cre贸 Tiled. Primero, exportemos nuestro mapa a JSON. Haga clic en “Archivo -> Exportar como”, seleccione JSON como formato y as铆gnele el nombre “level1.json” en el tilemaps carpeta. Al igual que con todos los proyectos de Phaser, nuestros activos deben cargarse en nuestro preload funci贸n:

    function preload() {
      this.load.image('background', 'assets/images/background.png');
      this.load.image('spike', 'assets/images/spike.png');
      // At last image must be loaded with its JSON
      this.load.atlas('player', 'assets/images/kenney_player.png','assets/images/kenney_player_atlas.json');
      this.load.image('tiles', 'assets/tilesets/platformPack_tilesheet.png');
      // Load the export Tiled JSON
      this.load.tilemapTiledJSON('map', 'assets/tilemaps/level1.json');
    }
    

    Nota: Puede que se pregunte por qu茅 tenemos que cargar la imagen de picos por separado si est谩 incluida en el mapa de mosaicos. Desafortunadamente, esta peque帽a duplicaci贸n es necesaria para que los objetos se muestren correctamente.

    En nuestro create funci贸n, primero agreguemos el fondo y escalemos para nuestra resoluci贸n:

    const backgroundImage = this.add.image(0, 0,'background').setOrigin(0, 0);
    backgroundImage.setScale(2, 0.8);
    

    Entonces agreguemos nuestro mapa:

    const map = this.make.tilemap({ key: 'map' });
    

    La clave coincide con el nombre dado en el preload funci贸n cuando cargamos el Tiled JSON. Tambi茅n tenemos que agregar la imagen del conjunto de mosaicos a nuestro Phaser map objeto:

    const tileset = map.addTilesetImage('kenney_simple_platformer', 'tiles');
    

    El primer argumento de addTilesetImage es el nombre del conjunto de mosaicos que usamos en Tiled. El segundo argumento es la clave de la imagen que cargamos en el preload funci贸n.

    Ahora podemos agregar nuestra capa de plataforma:

    const platforms = map.createStaticLayer('Platforms', tileset, 0, 200);
    

    Y deber铆a ver esto:

    De forma predeterminada, Phaser no gestiona las colisiones de nuestras capas en mosaico. Si agregamos nuestro jugador ahora, caer铆a completamente a trav茅s de los mosaicos de la plataforma. Digamos a Phaser que la capa puede colisionar con otros objetos:

    platforms.setCollisionByExclusion(-1, true);
    

    Cada mosaico en nuestro mapa recibi贸 un 铆ndice de Tiled para hacer referencia a lo que deber铆a mostrarse all铆. Un 铆ndice de nuestra plataforma solo puede ser mayor que 0. setCollisionByExclusion le dice a Phaser que habilite las colisiones para cada mosaico cuyo 铆ndice no sea -1, por lo tanto, todos los mosaicos.

    Atlas de texturas

    La animaci贸n de nuestro reproductor se almacena en un atlas de texturas, una imagen que contiene im谩genes m谩s peque帽as. Al igual que las hojas de sprites, reducen la actividad de la red al cargar un archivo. La mayor铆a de los atlas de texturas contienen mucho m谩s que informaci贸n de sprites.

    Echemos un vistazo a nuestro archivo de imagen: “kenney_player.png”:

    Nuestro atlas contiene 8 cuadros: los cuadros 0 a 3 est谩n en la parte superior y los cuadros 4 a 7 est谩n debajo. Por s铆 solo, esto no es tan 煤til para Phaser, por eso vino con un archivo JSON: “kenney_player_atlas.json”.

    El archivo tiene un frames matriz que contiene informaci贸n sobre cada imagen individual que forma el atlas.

    Para utilizar el atlas, necesitar谩 conocer el filename propiedad de los marcos que est谩 utilizando.

    Agregar un jugador

    Con nuestro mundo configurado, podemos agregar el jugador e interactuar con nuestras plataformas. En nuestro create funci贸n agreguemos lo siguiente:

    this.player = this.physics.add.sprite(50, 300, 'player');
    this.player.setBounce(0.1);
    this.player.setCollideWorldBounds(true);
    this.physics.add.collider(this.player, platforms);
    

    Por defecto, Phaser usa el primer fotograma del atlas, si quisi茅ramos comenzar en un fotograma diferente, podr铆amos haber agregado un next argumento a la sprite m茅todo con el filename propiedad de la imagen del atlas, por ejemplo robo_player_3.

    La propiedad de rebote solo agrega un poco de vivacidad cuando nuestro jugador salta y aterriza. Y configuramos al jugador para que choque con nuestro mundo de juego y las plataformas. Ahora deber铆amos ver a nuestro jugador parado en nuestras plataformas:

    El cuadro morado existe alrededor de nuestro reproductor porque debug El modo est谩 habilitado para nuestros motores de f铆sica. El modo de depuraci贸n muestra los l铆mites que determinan c贸mo chocan nuestros sprites.

    Agregar animaciones

    Recuerde que nuestro atlas de texturas ten铆a 8 cuadros para el movimiento del jugador. Phaser nos permite crear animaciones basadas en los fotogramas de una imagen de atlas. Creemos una animaci贸n para caminar usando los dos 煤ltimos fotogramas de la primera fila del atlas a trav茅s de nuestro create funci贸n:

    this.anims.create({
      key: 'walk',
      frames: this.anims.generateFrameNames('player', {
        prefix: 'robo_player_',
        start: 2,
        end: 3,
      }),
      frameRate: 10,
      repeat: -1
    });
    

    los key property es la cadena que usamos para reproducir la animaci贸n m谩s tarde. los frames La propiedad es una matriz de fotogramas en el archivo JSON de nuestro atlas que contiene la animaci贸n. La animaci贸n comienza en el primer fotograma de la matriz y termina en el 煤ltimo. Usamos la funci贸n de ayuda generateFrameNames para crearnos la lista de nombres de fotogramas, una funci贸n muy 煤til para archivos de atlas grandes.

    los frameRate el valor predeterminado es 24 fotogramas por segundo, lo que puede ser demasiado r谩pido para nuestro reproductor, por lo que lo configuramos en 10. Cuando configuramos repeat a -1 le estamos diciendo a Phaser que ejecute esta animaci贸n infinitamente.

    Agreguemos las animaciones para nuestro sprite inactivo, el primer fotograma del atlas:

    this.anims.create({
      key: 'idle',
      frames: [{ key: 'player', frame: 'robo_player_0' }],
      frameRate: 10,
    });
    

    Nuestra animaci贸n inactiva es simplemente un cuadro. Agreguemos una animaci贸n para cuando nuestro jugador salte, que tambi茅n es solo un cuadro:

    this.anims.create({
      key: 'jump',
      frames: [{ key: 'player', frame: 'robo_player_1' }],
      frameRate: 10,
    });
    

    Con nuestras animaciones agregadas, necesitamos habilitar las teclas del cursor para que podamos mover nuestro jugador:

    this.cursors = this.input.keyboard.createCursorKeys();
    

    Animando a nuestro jugador

    Si nuestro jugador se mueve hacia la izquierda o hacia la derecha, entonces queremos caminar. Si pulsamos la barra espaciadora o hacia arriba, queremos saltar. De lo contrario, permaneceremos en nuestra posici贸n inactiva. Implementemos esto en nuestro update funci贸n:

    // Control the player with left or right keys
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-200);
      if (this.player.body.onFloor()) {
        this.player.play('walk', true);
      }
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(200);
      if (this.player.body.onFloor()) {
        this.player.play('walk', true);
      }
    } else {
      // If no keys are pressed, the player keeps still
      this.player.setVelocityX(0);
      // Only show the idle animation if the player is footed
      // If this is not included, the player would look idle while jumping
      if (this.player.body.onFloor()) {
        this.player.play('idle', true);
      }
    }
    
    // Player can jump while walking any direction by pressing the space bar
    // or the 'UP' arrow
    if ((this.cursors.space.isDown || this.cursors.up.isDown) && this.player.body.onFloor()) {
      this.player.setVelocityY(-350);
      this.player.play('jump', true);
    }
    

    Animar un objeto es tan f谩cil como configurar la animaci贸n para true. Si fue un observador, notar谩 que nuestro atlas solo tiene movimientos hacia la derecha. Si nos movemos hacia la izquierda, ya sea caminando o saltando, queremos voltear el sprite en el eje x. Si nos movemos hacia la derecha, queremos darle la vuelta.

    Podemos lograr este objetivo con el siguiente c贸digo:

    if (this.player.body.velocity.x > 0) {
      this.player.setFlipX(false);
    } else if (this.player.body.velocity.x < 0) {
      // otherwise, make them face the other side
      this.player.setFlipX(true);
    }
    

    隆Ahora nuestro jugador se mueve por el juego con un estilo bien animado!

    Agregar picos

    Phaser nos proporciona muchas formas de obtener sprites de nuestra capa de objetos. Los picos se almacenan dentro de una matriz en nuestro objeto de mapa en mosaico. Cada pico obligar铆a a nuestro jugador a empezar de nuevo si los golpea. Tiene sentido para nosotros poner todos los picos en un grupo de sprites y configurar colisiones entre el jugador y el grupo. Cuando se configura una colisi贸n con un grupo de sprites, se aplica a todos los sprites.

    En el create funci贸n agregue lo siguiente:

    // Create a sprite group for all spikes, set common properties to ensure that
    // sprites in the group don't move via gravity or by player collisions
    this.spikes = this.physics.add.group({
      allowGravity: false,
      immovable: true
    });
    
    // Let's get the spike objects, these are NOT sprites
    const spikeObjects = map.getObjectLayer('Spikes')['objects'];
    
    // Now we create spikes in our sprite group for each object in our map
    spikeObjects.forEach(spikeObject => {
      // Add new spikes to our sprite group, change the start y position to meet the platform
      const spike = this.spikes.create(spikeObject.x, spikeObject.y + 200 - spikeObject.height, 'spike').setOrigin(0, 0);
    });
    

    Deber铆amos conseguir esto:

    El l铆mite de colisi贸n del sprite de picos es mucho m谩s alto que los picos mismos. Si no se modifica, puede crear una mala experiencia de juego. 隆Los jugadores restablecer铆an su posici贸n sin tocar el objeto! Ajustemos los cuerpos de las p煤as para que sean m谩s peque帽os, particularmente de altura. Reemplace la forEach con este:

    spikeObjects.forEach(spikeObject => {
      const spike = this.spikes.create(spikeObject.x, spikeObject.y + 200 - spikeObject.height, 'spike').setOrigin(0, 0);
      spike.body.setSize(spike.width, spike.height - 20).setOffset(0, 20);
    });
    

    Para mantener el cuadro delimitador abarcando correctamente los picos, agregamos un desplazamiento que coincide con la reducci贸n de altura. Ahora tenemos sprites de picos m谩s apropiados:

    Colisi贸n con el jugador

    Si nuestro jugador choca con un pico, su posici贸n se reinicia. Es com煤n en los juegos de plataformas que los jugadores tengan una animaci贸n de ‘perder’. Agreguemos una animaci贸n parpadeante cuando nuestro reproductor se reinicie. Primero, en el create agreguemos la colisi贸n:

    this.physics.add.collider(this.player, this.spikes, playerHit, null, this);
    

    La l贸gica para el reinicio del jugador estar谩 en el playerHit funci贸n. Cada vez que el jugador choca con un sprite del grupo de sprites spike, se llamar谩 a esta funci贸n. Al final del archivo, agregue lo siguiente:

    function playerHit(player, spike) {
      player.setVelocity(0, 0);
      player.setX(50);
      player.setY(300);
      player.play('idle', true);
      player.setAlpha(0);
      let tw = this.tweens.add({
        targets: player,
        alpha: 1,
        duration: 100,
        ease: 'Linear',
        repeat: 5,
      });
    }
    

    Aqu铆 est谩n sucediendo bastantes cosas. Tomemos cada instrucci贸n l铆nea por l铆nea:

    • Establece la velocidad del jugador en 0. Es mucho m谩s predecible (y m谩s seguro) detener el movimiento del jugador al reiniciar
    • Establece las coordenadas X e Y en la primera posici贸n del jugador.
    • Use la animaci贸n inactiva, tal como estaba cuando comenz贸 el reproductor
    • los alpha La propiedad controla la opacidad de un objeto. Es un valor entre 0 y 1 donde 0 es completamente transparente y 1 es completamente opaco
    • Crea una interpolaci贸n: una ‘animaci贸n’ de una propiedad de un objeto de juego. La interpolaci贸n se aplica al objeto del jugador que choc贸 con el pico. Establece la propiedad alpha en 1 (es decir, hace que nuestro reproductor sea completamente visible). Esta interpolaci贸n dura 100 ms y la opacidad aumenta linealmente como lo indica el ease propiedad. Tambi茅n se repite 5 veces, de ah铆 que parezca que parpadea.

    Ahora nuestro juego se ve as铆:

    Nota: Aseg煤rese de quitar el debug: true propiedad de la configuraci贸n del juego antes de compartirla con amigos, 隆nunca deje el modo de depuraci贸n en producci贸n!

    Conclusi贸n

    Con Tiled podemos dise帽ar mundos de juegos 2D tanto peque帽os como expansivos. Es una buena pr谩ctica crear capas para profundizar en nuestro mundo de juego. Luego tomamos el mundo que construimos en Tiled y lo agregamos a nuestro juego Phaser.

    Agregamos la capa de plataforma como una capa est谩tica, haci茅ndola inamovible cuando el jugador choca. Luego creamos un grupo de sprites para los picos y creamos una funci贸n para manejar las colisiones entre cada pico y el jugador.

    Adem谩s de crear un mundo de juego vibrante, aprendimos c贸mo animar a nuestro personaje usando un atlas, una imagen grande que contiene varias im谩genes m谩s peque帽as, acompa帽ada de un archivo JSON que detalla qu茅 imagen se encuentra en cada cuadro. Tambi茅n usamos una interpolaci贸n para cambiar una propiedad de nuestro objeto durante un per铆odo de tiempo determinado.

    Con estas t茅cnicas, 隆depende de ti crear el pr贸ximo mejor juego de plataformas con Phaser!

    Puedes ver el c贸digo fuente Aqu铆.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *