Introducción a Phaser 3: Building Breakout

Introducción

El desarrollo de juegos es una rama única del desarrollo de software que puede ser tan gratificante como complejo. Al pensar en crear juegos, generalmente pensamos en una aplicación para instalar y jugar en nuestras computadoras o consolas.
La especificación HTML5 introdujo muchas API para permitir el desarrollo de juegos en la web, lo que permite 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 manera de dominar el desarrollo de juegos es hacer juegos. Usaremos Phaser para crear un clon de Breakout , una versión del clásico y eterno juego Atari lanzado en 1976.
Este tutorial contiene algunos 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 características de ES2015.

The Game Loop

Todos los juegos se ejecutan dentro de un bucle. Después de configurar nuestro mundo de juego, entramos en el bucle del juego que realiza las siguientes tareas:
  1. Entrada de procesos
  2. Actualiza el mundo del juego.
  3. Representa los cambios.
Veamos cómo funciona el bucle de juego en un juego como Megaman . Después de seleccionar el menú para comenzar un nivel, el juego decide dónde colocar las plataformas y carga la música que se reproducirá. Esa configuración usualmente ocurre durante la pantalla de carga.
Cuando el juego comienza, tienes el control de Megaman en un mundo con plataformas, enemigos y una canción en particular para ese nivel. Puedes usar el 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 representando esos cambios muchas veces en un segundo.

¿Qué es Phaser?

Phaser es un framework de juegos HTML5. Utiliza muchas API de HTML5 como Canvas, WebGL, Audio, Gamepad, etc. y agrega alguna lógica útil como administrar el ciclo del juego y proporcionarnos motores físicos.
Con Phaser, podemos crear juegos en 2D sin nada más que HTML, CSS y JavaScript.

Reglas de Breakout

Antes de usar Phaser para construir nuestro clon Breakout, primero definamos el alcance del juego:
  • Este juego para un solo jugador tiene un nivel con 30 ladrillos, una paleta y una bola.
  • El objetivo es conseguir que la bola destruya cada ladrillo, al tiempo que garantiza que no abandone la parte inferior de la pantalla del juego.
  • El jugador controlará una paleta, que puede moverse a izquierda y derecha.
  • El juego está diseñado para usuarios de la Web de escritorio, por lo que el teclado se utilizará para la entrada

Configuración de Phaser

Phaser es una biblioteca de JavaScript, para desarrollar y jugar nuestro juego necesitaremos un poco de HTML básico para cargar el JS. Cree un directorio llamado breakouten uno de sus espacios de trabajo.
Crea los siguientes archivos y carpetas en tu 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 recursos 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.
Agregue el siguiente código a su 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/phaser@3.17.0/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 manera efectiva con Phaser, necesitaremos que estos archivos sean servidos por un servidor web. Sin un servidor web, nuestro navegador no permitirá que nuestro script de juego cargue nuestros activos por razones de seguridad.
Afortunadamente, no hay necesidad de 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 área de trabajo a través del terminal e ingresar python3 -m http.serverExisten otras herramientas CLI que proporcionan servidores web simples, elija la que le dé el tiempo más rápido para desarrollar su juego.
Por último, descargue los recursos de imagen que hemos creado para este juego desde este enlace . Copia y pega los archivos PNG en la carpeta de imágenes.
Sugerencia de desarrollo : cuando estés desarrollando un juego, es probable que desees que la consola de JavaScript esté visible para que puedas ver los errores que aparecen. Si está utilizando Chrome o Firefox, haga clic derecho en la página y seleccione "Inspeccionar elemento". Aparecerá un cuadro desde la parte inferior o lateral de la ventana de su 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 juego

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

Phaser de arranque

Primero, necesitamos configurar Phaser y crear nuestra Gameinstancia. La instancia del juego es el controlador central para un juego de Phaser, hace toda la configuración y el kick inicia el bucle del juego para nosotros.
Vamos a configurar y crear 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é procesador usar. Phaser puede representar nuestro juego utilizando el elemento WebGL o Canvas de HTML5 Al configurar el tipo a Phaser.AUTO, le estamos diciendo a Phaser que primero intente renderizar con WebGL y, si eso falla, renderé con 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 widthheightEl 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 principal. Esta propiedad es más útil cuando el juego no ocupa todo el espacio del padre div, se muestra aquí ya que es una pregunta frecuente.
En Phaser, nuestra lógica de juego se define en ScenesPiense en las escenas como varios estados de 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 de escena pero también puede funcionar con un objeto JavaScript normal que contiene las funciones preload()create()update()definido.
La última configuración le dice a Phaser qué motor de física usar. Phaser puede usar 3 motores físicos diferentes: Arcade , Impacto y Materia . Arcade es el más sencillo para comenzar y es suficiente para nuestras necesidades de juego.
La ruptura no necesita la gravedad para funcionar, por lo que deshabilitamos la propiedad en nuestro motor de física. Si estuviésemos construyendo un juego de plataformas, probablemente habilitaríamos la gravedad, de modo que cuando nuestros jugadores salten, caigan naturalmente al suelo.
Para asegurar nuestro juego creado obras tenemos que añadir los preload()create()update()funciones. Agregue las siguientes funciones en blanco para después de crear nuestra instancia de juego:
function preload() { }

function create() { }

function update() { }  
Con su servidor web en ejecución, navegue a la página donde se ejecuta su juego. Deberías ver una pantalla en blanco como esta:
Configuración del juego

Cargando activos

Los activos en este juego consisten en 5 imágenes. En otros juegos que puedes crear, tus activos pueden ser enormes. Las imágenes de alta definición, el audio y los archivos de video pueden ocupar megabytes de espacio. Cuanto mayor sea el activo, más tiempo lleva la carga. Por esa razón, Phaser tiene una preload()función donde podemos cargar todos los activos antes de comenzar a ejecutar el juego. Nunca es una buena experiencia para el usuario jugar un juego y de repente se ralentiza porque trata 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 de 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()update()funciones, nos estamos refiriendo a la escena a cargo de la instancia juego que fue creado anteriormente.
Con las imágenes cargadas, queremos colocar sprites en la pantalla. En la parte superior de la breakout.js, agregue estas variables que contendrán nuestros datos de sprite:
let player, ball, violetBricks, yellowBricks, redBricks, cursors;  
Una vez que están definidas globalmente, todas nuestras funciones pueden usarlas.

Añadiendo Sprites

Un sprite es cualquier imagen 2D que forma parte de una escena de juego. En Phaser, un sprite encapsula una imagen junto con su posición, velocidad, propiedades físicas y otras propiedades. Comencemos creando nuestro sprite 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:
Jugador en 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 entender cómo Phaser y la mayoría de los marcos de juego en 2D usan las coordenadas. Los gráficos que aprendimos en la escuela usualmente ubican 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, nos estamos moviendo esencialmente hacia la derecha. A medida que yaumenta, nos estamos moviendo hacia abajo.
Nuestro juego tiene un ancho de 800 píxeles y una altura de 640 píxeles, por lo que nuestras coordenadas del juego se verían así:
Coordenadas del juego
Vamos a añadir la bola para sentarse 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 pelota está sobre nuestro jugador, el valor de la coordenada Y es menor que la coordenada Y del jugador.

Agregando Grupos de Sprite

Si bien Phaser facilita la adición de sprites, rápidamente se volvería tedioso si cada sprite tuviera que definirse individualmente. Los ladrillos en 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 sprite de ladrillo, podemos crear grupos de sprite para gestionarlos mejor.
Añadamos 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 clave hace referencia a la clave de imagen que usarán todos los sprites en el grupo de sprites. La repeatpropiedad le dice a Phaser cuántas veces más para crear un sprite. Cada grupo de sprites crea un sprite. Con el repeatconjunto en 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 sprites repetidos en el eje x.
También hay una stepYpropiedad, pero no necesitamos usarla para este juego. Agreguemos los otros dos grupos restantes de sprites 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í:
Todos los Sprites Añadidos

Ganando y perdiendo

Es una buena práctica de desarrollo (y programación) de juegos para mantener 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 bola esté debajo de la pantalla, la coordenada Y de la bola es mayor que la altura del mundo del juego. Vamos a crear una función que verifique esto, agregue la parte inferior de breakout.jsla siguiente:
function isGameOver(world) {  
  return ball.body.y > world.bounds.height;
}
Nuestra función toma el objeto mundial de la propiedad física de la escena, que estará disponible en la update()función. Comprueba si la coordenada Y del sprite de 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. Sprites en Phaser todos tienen una propiedad activa. Podemos usar esa propiedad para determinar si ganamos o no. Los grupos de Sprite pueden contar el número de sprites activos contenidos dentro de ellos. Si no hay sprites activos en cada uno de los grupos de sprites de ladrillo, es decir, hay 0 sprites de ladrillos activos, entonces el jugador ganó el juego.
Vamos a actualizar el breakout.jsarchivo, agregando un cheque 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, agregamos el número de sprites activos dentro de ellos y verificamos si es igual a 0.
Ahora que hemos definido nuestras condiciones de ganar y perder, queremos que Phaser las compruebe al principio del ciclo de juego. Tan pronto como el jugador gane o pierda, el juego debería detenerse.
Vamos a actualizar 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 jugador con entrada de teclado

El movimiento del jugador depende de la entrada del teclado. Para poder realizar un seguimiento de la entrada del teclado. Es hora de usar la cursorsvariable.
Y en el fondo 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 debemos 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 a nuestro jugador de izquierda a derecha!
Jugador en movimiento
Notarías que el jugador sprite puede abandonar la pantalla del juego, idealmente, no debería. Nos ocuparemos de eso más adelante cuando manejemos las colisiones.

Esperando para empezar

Antes de agregar lógica para mover la bola, sería útil que el juego esperara la entrada del usuario antes de moverse. No es una buena experiencia cargar un juego y ser forzado de inmediato. ¡El jugador no tendría un tiempo justo para reaccionar!
Vamos a mover la bola 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á, de modo 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 la 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 xyde los sprites se relacionan con el centro de nuestros sprites.
Tenga en cuenta que aunque está bien obtener el valor de una propiedad xhaciendo referencia directamente al configurar las propiedades, siempre intentamos utilizar la función de establecimiento apropiada. Las funciones de establecimiento pueden incluir lógica para validar nuestra entrada, actualizar otra propiedad o desencadenar un evento. Hace que nuestro código sea más predecible.
Al igual que antes de mover el reproductor, verificamos si se presionó la barra espaciadora. Si se presionó, cambiamos la gameStartedbandera a truepara que la bola ya no siguiera la posición horizontal del jugador, y establecemos la velocidad Y de la bola 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:
Presione para iniciar
Podrías observar algunas cosas de cómo se comporta nuestro juego hasta ahora:
  1. La pelota se rinde detrás de los ladrillos.
  2. El jugador puede salir de los límites de la pantalla.
  3. La pelota puede salir de los límites de la pantalla.
La bola se renderiza detrás de los ladrillos porque se agregó al juego en nuestra función de crear antes de los grupos de sprite 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 una colisión mundial.

Manejo de colisiones

Colisión mundial

Todas nuestras interacciones de sprites están definidas 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 una salida como esta:
Colisión mundial
Mientras que el movimiento del jugador está bien, la pelota parece estar atascada en la parte superior. Para rectificar esto, necesitamos establecer la bouncepropiedad del sprite ball. La bouncepropiedad le diría a Phaser cuánta velocidad debe 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 lanzamos la bola con la barra espaciadora, la bola debería rebotar arriba y abajo del mundo del juego. Necesitamos deshabilitar la detección de colisiones desde la parte inferior del mundo del juego.
Si no lo hacemos, nunca sabremos cuándo se acabó el juego. Deshabilita 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:
La bola cae al fondo del mundo del juego.

Colisión de ladrillos

Ahora que nuestros sprites en movimiento chocan correctamente con nuestro mundo de juego, trabajemos en la colisión entre la bola y los ladrillos y luego la bola 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 de colisionador le dice al sistema de física de Phaser que ejecute la hitBrick()función cuando ballcolisiona con varios grupos de sprite de ladrillo.
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. Ese 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, ballvioletBricksLa disableBody(true, true)llamada al ladrillo le dice a Phaser que la desactive y la oculte de la pantalla. Si la velocidad X de la bola es 0, entonces le damos una velocidad a la bola dependiendo del valor de un número aleatorio.
Si una pequeña bola rueda hacia su pie a un ritmo lento, en caso de colisión se detendría. El motor de Arcade Physics modela la colisión de impacto en la velocidad de forma predeterminada. Para nuestro juego, no queremos que la pelota pierda velocidad cuando golpea un ladrillo. Necesitamos establecer la immovablepropiedad en nuestros grupos de sprites para true.
Actualice las definiciones de violetBricksyellowBricksredBricksa 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í:
Colisión de ladrillos
Sugerencia de desarrollo : al desarrollar la física de tu juego, es posible que desees habilitar el modo de depuración para ver los cuadros de límites de tus sprites y cómo se unen entre sí. En su configobjeto de juego , dentro de la arcadepropiedad donde lo definimos gravity, puede habilitar la depuración agregando esto al objeto:
debug: true  

Colisión del jugador

Gestionar las colisiones entre el balón y el jugador es un esfuerzo similar. Primero, asegurémonos de que el jugador es immovableAl final de la create()función agregue lo siguiente:
player.setImmovable(true);  
Y luego agregamos un colisionador entre la bola 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 debería moverse un poco más rápido, para aumentar gradualmente la dificultad del juego.
  • La dirección horizontal de la pelota depende del lado del jugador al que golpee: si la pelota golpea el lado izquierdo del jugador, entonces debe ir a la izquierda, si golpea el lado derecho del jugador, entonces debe ir hacia la derecha.
Para dar cabida a estos, vamos a actualizar 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 tanto colisión jugador como ladrillo:
Colisión jugador

Añadiendo texto

Si bien nuestro juego es completamente funcional, alguien que juegue a este juego no tendrá idea de cómo comenzar o sabrá cuándo finaliza 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 espacio. 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 primeros dos 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 sprites, los objetos de texto con coordenadas X e Y se refieren a su punto superior izquierdo del objeto, no a su centro. Es por eso que utilizamos el setOrigin()método para hacer que el sistema de coordenadas funcione como sprites, en este caso, facilita la posición 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 deshabilitar sus cuerpos. Podemos hacerlos invisibles cuando no necesitamos verlos. Nuestro juego ahora comienza así:
El texto de apertura comienza el juego

Game Over y Game Won

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 {
  ...
}
Inhabilitamos el cuerpo del balón para que deje de actualizarse y mostrarse ya que ya no es necesario.
Si perdemos el juego, veremos esto:
Juego terminado
Si ganamos el juego, veremos esto:
Juego ganado
¡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 abstraerse de las API de HTML5, también nos proporciona utilidades útiles como motores físicos y administra el ciclo de juego, el ciclo de vida de la ejecución de todos los juegos.
La mejor manera de mejorar tus habilidades de desarrollo de juegos es seguir construyendo juegos. Si desea obtener más información sobre el desarrollo de juegos con Phaser, consulte el tutorialintroductorio del sitio web oficial .
Puedes ver el código fuente anotado para el juego aquí .

Acerca de: Programator

Somos Instinto Programador

0 comentarios:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Con tecnología de Blogger.