Introducción a Phaser 3: Construcción de un Breakout

I

Introducción

El desarrollo de juegos es una rama única del desarrollo de software que puede ser tan gratificante como complejo. Cuando pensamos en crear juegos, solemos pensar en una aplicación para instalar y jugar en nuestros ordenadores o consolas.

La especificación HTML5 introdujo muchas API para permitir el desarrollo de juegos en la web, permitiendo que nuestros juegos lleguen a muchos usuarios en diferentes dispositivos informáticos. Phaser es un marco de juego popular que nos permite crear rápidamente juegos para la web.

La mejor forma de dominar el desarrollo de juegos es crear juegos. Usaremos Phaser para crear un clon de Breakout , una versión del clásico y eterno juego de Atari lanzado en 1976.

Este tutorial contiene HTML y CSS muy básicos. Deberá sentirse cómodo con las funciones y los objetos de JavaScript. También hace un uso ligero de las funciones de ES2015.

El bucle del juego

Todos los juegos se ejecutan dentro de un bucle. Después de configurar nuestro mundo de juego, ingresamos al bucle del juego que realiza las siguientes tareas:

  • Entrada de procesos
  • Actualiza el mundo del juego
  • Renderiza los cambios

Veamos cómo funciona el bucle del juego en un juego como Megaman. Después de examinar el menú para comenzar un nivel, el juego decide dónde colocar las plataformas y carga la música para reproducir. Esa configuración suele ocurrir durante la pantalla de carga.

Cuando comienza el juego, tienes el control de Megaman en un mundo con plataformas, enemigos y una canción particular para ese nivel. Puedes usar tu joystick para mover a Megaman y presionar un botón para saltar o disparar. El bucle del juego está procesando la entrada, actualizando la posición de Megaman y renderizando esos cambios muchas veces en un segundo.

¿Qué es Phaser?

Phaser es un marco de juego HTML5. Utiliza muchas API HTML5 como Canvas, WebGL, Audio, Gamepad, etc. y agrega una lógica útil como administrar el bucle del juego y proporcionarnos motores de física.

Con Phaser, podemos crear juegos 2D con nada más que HTML, CSS y JavaScript.

Reglas de ruptura

Antes de usar Phaser para construir nuestro clon Breakout, primero definamos el alcance del juego:

  • Este juego para un jugador tiene un nivel con 30 ladrillos, una paleta y una pelota.
  • El objetivo es conseguir que la bola destruya todos los ladrillos, asegurándose de que no salga de la parte inferior de la pantalla del juego.
  • El jugador controlará una paleta, que puede moverse hacia la izquierda y hacia la derecha.
  • El juego está diseñado para usuarios web de escritorio, por lo que el teclado se usará para ingresar

Configuración de Phaser

Phaser es una biblioteca de JavaScript, para desarrollar y jugar a nuestro juego necesitaremos algo de HTML básico para cargar el JS. Cree un directorio llamado breakouten uno de sus espacios de trabajo.

Cree los siguientes archivos y carpetas en su directorio:

  • Un index.htmlarchivo
  • Un breakout.jsarchivo
  • Una carpeta llamada assets
  • Dentro de su assetscarpeta, cree una imagescarpeta

Los activos del juego son arte, sonido, video y otros datos utilizados por el juego. Para este simple clon de Breakout, no hay muchos activos que necesiten organizarse con carpetas. Sin embargo, es una buena práctica mantener sus activos separados de su código y separarlos por su tipo.

Agrega el siguiente código a tu index.htmlarchivo:

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  <title>Breakout</title>
  <style>
    html,
    body {
      margin: 0 auto;
      padding: 0;
      width: 100%;
      height: 100%;
    }

    #game {
      margin: 10px auto;
      padding: 0;
      width: 800px;
      height: 640px;
    }
  </style>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="game"></div>
  <script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
  <script src="breakout.js"></script>
</body>

</html>

Este código HTML básico hace lo siguiente:

  • Elimina los márgenes y el relleno del navegador de HTML y la etiqueta del cuerpo
  • Agrega un gameelemento div que contendrá nuestro clon Breakout
  • Carga Phaser v3.17 a través de su CDN
  • Carga nuestro breakout.jsarchivo que actualmente no hace nada pero contendrá nuestra lógica de juego

Para desarrollar juegos de forma eficaz con Phaser, necesitaremos que estos archivos sean servidos por un servidor web. Sin un servidor web, nuestro navegador no permitirá que el script del juego cargue nuestros activos por razones de seguridad.

Afortunadamente, no es necesario configurar Apache o Nginx para obtener un servidor web en ejecución. Si usa VisualStudio Code como yo, puede instalar la extensión Live Server . La mayoría de los IDE y editores de texto tienen un complemento con una funcionalidad similar.

Si tiene instalada la versión 3 de Python, puede ir a su espacio de trabajo a través de la terminal e ingresar python3 -m http.server. Hay otras herramientas CLI que proporcionan servidores web sencillos, elige la que te dé el tiempo más rápido para desarrollar tu juego.

Por último, descargue los recursos de imagen que hemos creado para este juego desde este enlace . Copie y pegue los archivos PNG en la carpeta de imágenes.

Consejo de desarrollo : cuando esté desarrollando un juego, probablemente desee tener la consola JavaScript visible para que pueda ver los errores que aparecen. Si está utilizando Chrome o Firefox, haga clic con el botón derecho en la página y seleccione “Inspeccionar elemento”. Debería aparecer un cuadro en la parte inferior o lateral de la ventana del navegador. Seleccione la pestaña “Consola” para ver los errores de actualización o los registros de nuestro código JavaScript.

Creando nuestro mundo de juegos

Con nuestra configuración HTML y CSS, editemos nuestro breakout.jsarchivo para configurar nuestro mundo de juego.

Inicio de Phaser

Primero, necesitamos configurar Phaser y crear nuestra Gameinstancia. La instancia de Juego es el controlador central para un juego de Phaser, hace toda la configuración e inicia el ciclo del juego por nosotros.

Configuremos y creemos nuestra Gameinstancia:

// This object contains all the Phaser configurations to load our game
const config = {
  type: Phaser.AUTO,
  parent: 'game',
  width: 800,
  heigth: 640,
  scale: {
    mode: Phaser.Scale.RESIZE,
    autoCenter: Phaser.Scale.CENTER_BOTH
  },
  scene: {
    preload,
    create,
    update,
  },
  physics: {
    default: 'arcade',
    arcade: {
      gravity: false
    },
  }
};

// Create the game instance
const game = new Phaser.Game(config);

La typepropiedad le dice a Phaser qué renderizador usar. Phaser puede renderizar nuestro juego usando el elemento WebGL o Canvas de HTML5 . Al establecer el tipo en Phaser.AUTO, le estamos diciendo a Phaser que primero intente renderizar con WebGL y, si eso falla, renderice usando Canvas.

La parentpropiedad indica el ID del elemento HTML en el que se jugará nuestro juego. Definimos las dimensiones de nuestro juego en píxeles con widthy height. El scaleobjeto hace dos cosas por nosotros:

  • mode le dice a Phaser cómo usar el espacio de nuestro elemento padre, en este caso, nos aseguramos de que el juego se ajuste al tamaño del padre div
  • autoCenterle dice a Phaser cómo centrar nuestro juego dentro de nuestro padre divsi queremos. En este caso, centramos nuestro juego vertical y horizontalmente dentro del div padre. Esta propiedad es más útil cuando el juego no ocupa todo el espacio del padre div, se muestra aquí como una pregunta frecuente.

En Phaser, nuestra lógica de juego se define en Scenes. Piense en las escenas como varios estados en nuestro juego: la pantalla de título es una escena, cada nivel de un juego sería su propia escena, una escena de corte sería su propia escena, etc. Phaser proporciona un objeto Escena pero también puede funcionar con un objeto JavaScript normal que contiene las funciones preload(), create()y update()definidas.

La última configuración le dice a Phaser qué motor de física usar. Phaser puede usar 3 motores físicos diferentes: Arcade , Impact y Matter . Arcade es el más simple para comenzar y es suficiente para nuestras necesidades de juego.

Breakout no necesita gravedad para funcionar, así que desactivamos la propiedad en nuestro motor de física. Si estuviéramos construyendo un juego de plataformas, probablemente habilitaríamos la gravedad, de modo que cuando nuestros jugadores salten caigan naturalmente al suelo.

Para asegurarnos de que la configuración de nuestro juego funcione, debemos agregar las funciones preload(), create()y update(). Agregue las siguientes funciones en blanco después de crear nuestra instancia de juego:

function preload() { }

function create() { }

function update() { }

Con su servidor web en ejecución, navegue hasta la página donde se está ejecutando su juego. Debería ver una pantalla en blanco como esta:

Cargando activos

Los activos de este juego consisten en 5 imágenes. En otros juegos que puedas crear, tus activos pueden ser enormes. Los archivos de imágenes, audio y video de alta definición pueden ocupar megabytes de espacio. Cuanto mayor sea el activo, más tiempo llevará la carga. Por esa razón, Phaser tiene una preload()función en la que podemos cargar todos los activos antes de comenzar a ejecutar el juego. Nunca es una experiencia agradable para el usuario jugar un juego y de repente se ralentiza porque está tratando de cargar nuevos activos.

Cambie la preload()función a la siguiente para que podamos cargar nuestras imágenes antes de que comience el ciclo del juego:

function preload() {
  this.load.image('ball', 'assets/images/ball_32_32.png');
  this.load.image('paddle', 'assets/images/paddle_128_32.png');
  this.load.image('brick1', 'assets/images/brick1_64_32.png');
  this.load.image('brick2', 'assets/images/brick2_64_32.png');
  this.load.image('brick3', 'assets/images/brick3_64_32.png');
}

El primer argumento es la clave que usaremos más adelante para hacer referencia a la imagen, el segundo argumento es la ubicación de la imagen.

Nota: – cuando usamos thisen nuestros preload(), create()y update()funciones, nos estamos refiriendo a la escena de ejecución por la instancia de juego que fue creado anteriormente.

Con las imágenes cargadas, queremos colocar sprites en la pantalla. En la parte superior de breakout.js, agregue estas variables que contendrán nuestros datos de sprites:

let player, ball, violetBricks, yellowBricks, redBricks, cursors;

Una vez que se definen globalmente, todas nuestras funciones pueden utilizarlas.

Agregar Sprites

Un objeto es cualquier imagen 2D que forma parte de la escena de un juego. En Phaser, un sprite encapsula una imagen junto con su posición, velocidad, propiedades físicas y otras propiedades. Comencemos creando nuestro sprite de jugador a través de la create()función:

player = this.physics.add.sprite(
  400, // x position
  600, // y position
  'paddle', // key of image for the sprite
);

Ahora debería poder ver una paleta en la pantalla:

El primer argumento del sprite()método es la XCoordenada para colocar el sprite. El segundo argumento es la YCoordenada, y el último argumento es clave para el activo de imagen agregado en la preload()función.

Es importante comprender cómo Phaser y la mayoría de los marcos de juegos 2D usan coordenadas. Los gráficos que aprendimos en la escuela generalmente colocan el origen, es decir, el punto (0, 0) en el centro. En Phaser, el origen está en la parte superior izquierda de la pantalla. A medida que xaumenta, esencialmente nos movemos hacia la derecha. A medida que yaumenta, nos movemos hacia abajo.

Nuestro juego tiene un ancho de 800 píxeles y una altura de 640 píxeles, por lo que las coordenadas de nuestro juego se verían así:

Agreguemos la pelota para que se siente encima del jugador. Agregue el siguiente código a la create()función:

ball = this.physics.add.sprite(
  400, // x position
  565, // y position
  'ball' // key of image for the sprite
);

Como la bola está por encima de nuestro jugador, el valor de la Coordenada Y es menor que la Coordenada Y del jugador.

Agregar grupos de sprites

Si bien Phaser facilita la adición de sprites, rápidamente se volvería tedioso si cada sprite tuviera que definirse individualmente. Los ladrillos de Breakout son prácticamente idénticos. Las posiciones son diferentes, pero sus propiedades, como el color y cómo interactúan con la pelota, son las mismas. En lugar de crear 30 objetos de sprites de ladrillo, podemos crear grupos de sprites para administrarlos mejor.

Agreguemos la primera fila de ladrillos violetas a través de la create()función:

// Add violet bricks
violetBricks = this.physics.add.group({
  key: 'brick1',
  repeat: 9,
  setXY: {
    x: 80,
    y: 140,
    stepX: 70
  }
});

En lugar de this.physics.add.sprite()usar this.physics.add.group()y pasar un objeto JavaScript. La propiedad de clave hace referencia a la clave de imagen que usarán todos los sprites del grupo de sprites. La repeatpropiedad le dice a Phaser cuántas veces más crear un objeto. Cada grupo de sprites crea un sprite. Con el repeatvalor 9, Phaser creará 10 sprites en ese grupo de sprites. El setXYobjeto tiene tres propiedades interesantes:

  • x es la coordenada X del primer sprite
  • y es la coordenada Y del segundo sprite
  • stepX es la longitud en píxeles entre los sprites repetidos en el eje x.

También hay una stepYpropiedad, pero no es necesario que la usemos para este juego. Agreguemos los otros dos grupos de sprites restantes para ladrillos:

// Add yellow bricks
yellowBricks = this.physics.add.group({
  key: 'brick2',
  repeat: 9,
  setXY: {
    x: 80,
    y: 90,
    stepX: 70
  }
});

// Add red bricks
redBricks = this.physics.add.group({
  key: 'brick3',
  repeat: 9,
  setXY: {
    x: 80,
    y: 40,
    stepX: 70
  }
});

Nuestro juego ya se está juntando, tu pantalla debería verse así:

Ganar y perder

Es una buena práctica de desarrollo (y programación) de juegos tener el final a la vista. En Breakout, podemos perder un juego si nuestra bola cae al fondo de la pantalla. En Phaser, para que la pelota esté debajo de la pantalla, la Coordenada Y de la pelota es mayor que la altura del mundo del juego. Creemos una función que verifique esto, agreguemos al final de la breakout.jsadición lo siguiente:

function isGameOver(world) {
  return ball.body.y > world.bounds.height;
}

Nuestra función toma el objeto de mundo de la propiedad física de la escena, que estará disponible en la update()función. Comprueba si la coordenada Y del objeto de la bola es mayor que la altura de los límites del mundo del juego.

Para ganar el juego tenemos que deshacernos de todos los ladrillos. Todos los Sprites en Phaser tienen una propiedad activa. Podemos usar esa propiedad para determinar si ganamos o no. Los grupos de sprites pueden contar la cantidad de sprites activos que contienen. Si no hay sprites activos en cada uno de los grupos de sprites de ladrillo, es decir, hay 0 sprites de ladrillo activos, entonces el jugador ganó el juego.

Actualicemos el breakout.jsarchivo, agregando una marca en la parte inferior:

function isWon() {
  return violetBricks.countActive() + yellowBricks.countActive() + redBricks.countActive() === 0;
}

Aceptamos cada uno de los grupos de sprites como parámetros, sumamos el número de sprites activos dentro de ellos y verificamos si es igual a 0.

Ahora que hemos definido nuestras condiciones para ganar y perder, queremos que Phaser las compruebe al principio del ciclo del juego. Tan pronto como el jugador gane o pierda, el juego debería detenerse.

Actualicemos la update()función:

function update() {
  // Check if the ball left the scene i.e. game over
  if (isGameOver(this.physics.world)) {
    // TODO: Show "Game over" message to the player
  } else if (isWon()) {
    // TODO: Show "You won!" message to the player
  } else {
    // TODO: Logic for regular game time
  }
}

Mover el reproductor con entrada de teclado

El movimiento del jugador depende de la entrada del teclado. Para poder rastrear la entrada del teclado. Es hora de usar la cursorsvariable.

Y al final de nuestra create()función:

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

Las teclas del cursor en Phaser rastrean el uso de 6 teclas del teclado: arriba, derecha, abajo, izquierda, shift y espacio.

Ahora necesitamos reaccionar al estado de nuestro cursorsobjeto para actualizar la posición de nuestro jugador. En la elsecláusula de nuestra update()función agregue lo siguiente:

// Put this in so that the player stays still if no key is being pressed
player.body.setVelocityX(0);

if (cursors.left.isDown) {
  player.body.setVelocityX(-350);
} else if (cursors.right.isDown) {
  player.body.setVelocityX(350);
}

¡Ahora podemos mover nuestro reproductor de izquierda a derecha!

Notarías que el sprite del jugador puede salir de la pantalla del juego, idealmente, no debería hacerlo. Nos ocuparemos de eso más adelante cuando manejemos las colisiones.

Esperando para empezar

Antes de agregar lógica para mover la pelota, sería útil que el juego esperara la entrada del usuario antes de moverse. No es una buena experiencia cargar un juego y verse forzado a comenzar de inmediato. ¡El jugador no tendría tiempo suficiente para reaccionar!

Muevamos la pelota hacia arriba después de que el jugador presione la barra espaciadora. Si el usuario mueve la paleta hacia la izquierda o hacia la derecha, la bola también se moverá, por lo que siempre estará en el centro de la paleta.

Primero, necesitamos nuestra propia variable para rastrear si un juego se inició o no. En la parte superior de breakout.js, después de la declaración de nuestras variables de juego, agreguemos:

let gameStarted = false;

Ahora, en la elsecláusula de nuestra función de actualización:

if (!gameStarted) {
  ball.setX(player.x);

  if (cursors.space.isDown) {
    gameStarted = true;
    ball.setVelocityY(-200);
  }
}

Si el juego no ha comenzado, establece la coordenada X de nuestra bola en el centro del jugador. Las coordenadas de un objeto de juego se basan en su centro, por lo que las propiedades xy yde los sprites relacionan el punto con el centro de nuestros sprites.

Tenga en cuenta que, si bien está bien obtener el valor de una propiedad xhaciendo referencia a ella directamente al configurar las propiedades, siempre intentamos usar la función de establecimiento adecuada. Las funciones del setter pueden incluir lógica para validar nuestra entrada, actualizar otra propiedad o disparar un evento. Hace que nuestro código sea más predecible.

Como antes al mover el reproductor, comprobamos si se presionó la barra espaciadora. Si se presionó, cambiamos la gameStartedbandera a truepara que la pelota ya no siguiera la posición horizontal del jugador, y establecemos la velocidad Y de la pelota en -200. Las velocidades y negativas envían objetos hacia arriba. Para velocidades positivas, los valores más grandes mueven los objetos hacia abajo más rápido. Para velocidades negativas, los valores más pequeños mueven los objetos hacia arriba más rápido.

Ahora cuando movemos al jugador, la pelota sigue y si presionamos la barra espaciadora la pelota se dispara hacia arriba:

Observaría algunas cosas de cómo se comporta nuestro juego hasta ahora:

  • La bola está detrás de los ladrillos.
  • El jugador puede salir de los límites de la pantalla.
  • La pelota puede salir de los límites de la pantalla.

La bola se representa detrás de los ladrillos porque se agregó al juego en nuestra función de creación antes de los grupos de sprites de ladrillos. En Phaser, y generalmente con el elemento de lienzo HTML5, la imagen agregada más recientemente se dibuja sobre las imágenes anteriores.

Los dos últimos problemas se pueden resolver agregando alguna colisión mundial.

Manejo de colisiones

Colisión mundial

Todas nuestras interacciones de sprites se definen en la createfunción. Habilitar la colisión con la escena mundial es muy fácil con Phaser, agregue lo siguiente al final de la createfunción:

player.setCollideWorldBounds(true);
ball.setCollideWorldBounds(true);

Debería darnos un resultado como este:

Si bien el movimiento del jugador está bien, la pelota parece atascada en la parte superior. Para rectificar esto, necesitamos establecer la bouncepropiedad del sprite de la bola. La bouncepropiedad le diría a Phaser cuánta velocidad mantener después de chocar con un objeto.

Agregue esto al final de su create()función:

ball.setBounce(1, 1);

Esto le dice al phaser que la bola debe mantener toda su velocidad X e Y. Si soltamos la pelota con la barra espaciadora, la pelota debería estar rebotando arriba y abajo del mundo del juego. Necesitamos desactivar la detección de colisiones desde la parte inferior del mundo del juego.

Si no lo hacemos, nunca sabremos cuándo termina el juego. Deshabilite la colisión con la parte inferior del mundo del juego agregando esta línea al final de la createfunción:

this.physics.world.checkCollision.down = false;

Ahora deberíamos tener un juego como este:

Colisión de ladrillos

Ahora que nuestros sprites en movimiento chocan correctamente con nuestro mundo de juego, trabajemos en la colisión entre la pelota y los ladrillos y luego la pelota y el jugador.

En nuestra create()función agregue las siguientes líneas de código al final:

this.physics.add.collider(ball, violetBricks, hitBrick, null, this);
this.physics.add.collider(ball, yellowBricks, hitBrick, null, this);
this.physics.add.collider(ball, redBricks, hitBrick, null, this);

El método colisionador le dice al sistema físico de Phaser que ejecute la hitBrick()función cuando ballcolisiona con varios grupos de sprites de ladrillos.

Cada vez que presionamos la barra espaciadora, la bola se dispara hacia arriba. No hay velocidad X, por lo que la pelota volvería directamente a la paleta. Sería un juego aburrido. Por lo tanto, cuando golpeamos un ladrillo por primera vez, estableceremos una velocidad X aleatoria.

En la parte inferior de la breakout.jsdefinición a hitBrickcontinuación:

function hitBrick(ball, brick) {
  brick.disableBody(true, true);

  if (ball.body.velocity.x === 0) {
    randNum = Math.random();
    if (randNum >= 0.5) {
      ball.body.setVelocityX(150);
    } else {
      ball.body.setVelocityX(-150);
    }
  }
}

La hitBrick()función acepta los dos argumentos anteriores que se usaron en el collider()método, por ejemplo, bally violetBricks. La disableBody(true, true)llamada en el ladrillo le dice a Phaser que lo ponga inactivo y lo oculte de la pantalla. Si la velocidad X de la pelota es 0, le damos a la pelota una velocidad que depende del valor de un número aleatorio.

Si una pelota pequeña rueda hacia su pie a un ritmo lento, al chocar se detendría. El motor Arcade Physics modela el impacto que tiene la colisión en la velocidad de forma predeterminada. Para nuestro juego, no queremos que la pelota pierda velocidad cuando golpea un ladrillo. Necesitamos establecer la immovablepropiedad de nuestros grupos de sprites en true.

Actualizar las definiciones de violetBricks, yellowBricksy redBricksa lo siguiente:

// Add violet bricks
violetBricks = this.physics.add.group({
  key: 'brick1',
  repeat: 9,
  immovable: true,
  setXY: {
    x: 80,
    y: 140,
    stepX: 70
  }
});

// Add yellow bricks
yellowBricks = this.physics.add.group({
  key: 'brick2',
  repeat: 9,
  immovable: true,
  setXY: {
    x: 80,
    y: 90,
    stepX: 70
  }
});

// Add red bricks
redBricks = this.physics.add.group({
  key: 'brick3',
  repeat: 9,
  immovable: true,
  setXY: {
    x: 80,
    y: 40,
    stepX: 70
  }
});

Nuestra colisión de ladrillos ahora está completa y nuestro juego debería funcionar así:

Consejo de desarrollo : cuando desarrolle la física de su juego, es posible que desee habilitar el modo de depuración para ver los cuadros de límite de sus sprites y cómo chocan entre sí. En su configobjeto de juego , dentro de la arcadepropiedad donde definimos gravity, puede habilitar la depuración agregando esto al objeto:

debug: true

Colisión de jugador

Manejar las colisiones entre la pelota y el jugador es un esfuerzo similar. Primero, asegurémonos de que el jugador sea immovable. Al final de la create()función agregue lo siguiente:

player.setImmovable(true);

Y luego agregamos un colisionador entre la pelota y el jugador:

this.physics.add.collider(ball, player, hitPlayer, null, this);

Cuando la pelota golpea al jugador, queremos que sucedan dos cosas:

  • La pelota debe moverse un poco más rápido, para aumentar gradualmente la dificultad del juego.
  • La dirección horizontal de la pelota depende de qué lado del jugador golpeó: si la pelota golpea el lado izquierdo del jugador, debe ir hacia la izquierda, si golpea el lado derecho del jugador, debe ir hacia la derecha.

Para adaptarse a estos, actualice breakout.jscon la hitPlayer()función:

function hitPlayer(ball, player) {
  // Increase the velocity of the ball after it bounces
  ball.setVelocityY(ball.body.velocity.y - 5);

  let newXVelocity = Math.abs(ball.body.velocity.x) + 5;
  // If the ball is to the left of the player, ensure the X-velocity is negative
  if (ball.x < player.x) {
    ball.setVelocityX(-newXVelocity);
  } else {
    ball.setVelocityX(newXVelocity);
  }
}

Nota: Un sprite puede chocar con otro sprite, un sprite puede chocar con un grupo de sprite y los grupos de sprite pueden chocar entre sí. Phaser es lo suficientemente inteligente como para usar la función de colisión que definimos como apropiada en el contexto.

Ahora nuestro juego tiene una colisión entre jugadores y ladrillos:

Agregar texto

Si bien nuestro juego es completamente funcional, alguien que juegue a este juego no tendría idea de cómo comenzar o saber cuándo termina el juego.

Agreguemos 3 nuevas variables globales que almacenarán nuestros datos de texto después de la gameStarteddeclaración en la parte superior de breakout.js:

let openingText, gameOverText, playerWonText;

Texto de apertura

Agreguemos algo de texto cuando el juego esté cargado para decirle al jugador que presione la barra espaciadora. En la create()función agregue el siguiente código:

openingText = this.add.text(
  this.physics.world.bounds.width / 2,
  this.physics.world.bounds.height / 2,
  'Press SPACE to Start',
  {
    fontFamily: 'Monaco, Courier, monospace',
    fontSize: '50px',
    fill: '#fff'
  }
);

openingText.setOrigin(0.5);

Los dos primeros argumentos del textmétodo son las coordenadas X e Y del cuadro de texto. Usamos el ancho y la altura de la escena del juego para determinar dónde se coloca: en el centro. El tercer argumento es el texto a mostrar. El cuarto argumento es un objeto JS que contiene datos relacionados con la fuente.

A diferencia de los objetos de texto, las coordenadas X e Y de los objetos de texto se refieren a su punto superior izquierdo del objeto, no a su centro. Por eso usamos el setOrigin()método para hacer que el sistema de coordenadas funcione como sprites, en este caso facilita su posicionamiento en el centro.

Cuando estamos jugando, ya no queremos ver el texto de apertura. En la update()función, cambie la ifinstrucción que comprueba si la barra espaciadora se presionó a lo siguiente:

if (cursors.space.isDown) {
  gameStarted = true;
  ball.setVelocityY(-200);
  openingText.setVisible(false);
}

Los objetos de texto no son sprites, no podemos desactivar sus cuerpos. Podemos hacerlos invisibles cuando no necesitamos verlos. Nuestro juego ahora comienza así:

Juego terminado y juego ganado

Como antes, necesitamos agregar los objetos de texto en la create()función y hacerlos invisibles para que no se vean cuando se inicie el juego:

// Create game over text
gameOverText = this.add.text(
  this.physics.world.bounds.width / 2,
  this.physics.world.bounds.height / 2,
  'Game Over',
  {
    fontFamily: 'Monaco, Courier, monospace',
    fontSize: '50px',
    fill: '#fff'
  }
);

gameOverText.setOrigin(0.5);

// Make it invisible until the player loses
gameOverText.setVisible(false);

// Create the game won text
playerWonText = this.add.text(
  this.physics.world.bounds.width / 2,
  this.physics.world.bounds.height / 2,
  'You won!',
  {
    fontFamily: 'Monaco, Courier, monospace',
    fontSize: '50px',
    fill: '#fff'
  }
);

playerWonText.setOrigin(0.5);

// Make it invisible until the player wins
playerWonText.setVisible(false);

Ahora que están definidos, tenemos que cambiar su visibilidad en la update()función:

// Check if the ball left the scene i.e. game over
if (isGameOver(this.physics.world)) {
  gameOverText.setVisible(true);
  ball.disableBody(true, true);
} else if (isWon()) {
  playerWonText.setVisible(true);
  ball.disableBody(true, true);
} else {
  ...
}

Deshabilitamos el cuerpo de la bola para que deje de actualizarse y mostrarse, ya que ya no es necesaria.

Si perdemos el juego, veremos esto:

Si ganamos el juego, veremos esto:

¡Nuestro clon Breakout está completo!

Conclusión

Phaser es un marco de desarrollo de juegos HTML5 que nos permite crear rápidamente videojuegos en la web. Además de abstraer las API de HTML5, también nos proporciona utilidades útiles como motores de física y gestiona el ciclo del juego: el ciclo de vida de ejecución de todos los juegos.

La mejor manera de mejorar sus habilidades de desarrollo de juegos es seguir creando juegos. Si desea obtener más información sobre el desarrollo de juegos con Phaser, consulte el tutorial introductorio del sitio web oficial .

Puedes ver el código fuente anotado del juego aquí .

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad