Phaser motor de juego tiene muchas características nuevas y útiles para un fácil proceso de desarrollo del juego más rápido y. En este tutorial usted aprenderá cómo crear un sencillo juego de torre de defensa en 3 Phaser estilo. En primer lugar vamos a crear un juego vacío, entonces continuaremos con la adición de los enemigos, los torreones y las balas. Vamos a utilizar la física de arcade para hacer balas golpean a los enemigos. Al final vamos a crear completamente funcional juego de torre de defensa.

Objetivos de aprendizaje


  • Crear Phaser 3 juego
  • Use caminos y enemigos maquillaje se mueven a lo largo de un camino
  • Obtener información acerca de Phaser 3 grupos y la forma de utilizarlos para la gestión de los enemigos, torretas
  • y balas
  • Juego con Phaser.Math para el desarrollo del juego más rápido
  • Entender la lógica básica torre de defensa juego

    requisitos Tutorial


    • Básico a Nivel intermedio de JavaScript
    • Navegador de Internet – navegador web moderno
    • editor de código – cualquier tipo de editor de código, bloc de notas, incluso hará el trabajo (yo prefiero NetBeans para mayor comodidad)
    • servidor web local
    • Activos – puede utilizar los activos que vienen con la descarga archivos de origen para este tutorial .

      Fuente código de descarga
      Obtener el código fuente completo para este tutorial aquí.
      No se pierda! extremos de la oferta en

      • Accede a los más de 200 cursos
      • Nuevos cursos añadió mensual
      • Cancelar en cualquier momento
      • Los certificados de terminación

        ACCESO ahora

        Crear juegos de defensa
        Es hora de empezar con nuestro juego. En primer lugar vamos a crear un proyecto vacío Phaser 3 con la escena predeterminada. Vamos a necesitar para implementar estas funciones para nuestra escena:

        precarga – cargar nuestros recursos aquí, en nuestro caso
        imagen de un atlas y uno
        crear – crear la lógica del juego aquí
        actualización – Actualiza el juego aquí

        Así es como nuestro juego vacío debe ser:
        config = {var Tipo: Phaser.AUTO, padre: ‘contenido’, anchura: 640, altura: 512, escena: {clave: ‘principal’, precarga: precarga, crear: crear, actualizar: update}}; juego var = nueva Phaser.Game (config); var gráficos; ruta var; función de precarga () {// cargar los recursos del juego – enemigas y torreta atlas this.load.atlas ( ‘sprites’, ‘activos / spritesheet.png’, ‘activos /spritesheet.json ‘); this.load.image ( ‘bala’, ‘activos / bullet.png’);} function crear () {} actualización function () {} 123456789101112131415161718192021222324252627282930varconfig = {type: Phaser.AUTO, padre: ‘contenido’, anchura: 640 , altura: 512, escena: {clave: ‘principal’, precarga: precarga, crear: crear, actualizar: update}}; vargame = newPhaser.Game (config); VARGRAPHICs; varpath; functionpreload () {// cargar el juego – activos enemigo y atlasthis.load.atlas torreta ( ‘sprites’, ‘activos / spritesheet.png’, ‘activos / spritesheet.json’); this.load.image ( ‘bala’ ‘activos / bullet.png’) ;} functioncreate () {} functionupdate () {}

        En nuestro juego de torre de defensa a los enemigos se moverá a lo largo de un camino. Por esta razón, vamos a crear un elemento de trazado muy simple. Cambiar el crear a esta función:
        función de crear () {// este elemento gráfico es sólo para visualización, // no es relacionada con nuestros gráficos ruta var = this.add.graphics (); // el camino para nuestros enemigos // parámetros son el comienzo X e Y de nuestro camino path = this.add.path (96, -32); path.lineTo (96, 164); path.lineTo (480, 164); path.lineTo (480, 544); Graphics.lineStyle (3, 0xffffff, 1); // visualizar la ruta path.draw (gráficos);} 12345678910111213141516functioncreate () {// este elemento gráfico es sólo para visualización, // no es relacionada con nuestros pathvargraphics = this.add.graphics (); // el camino para nuestra enemigos // parámetros son el comienzo X e y de nuestra pathpath = this.add.path (96, -32); path.lineTo (96.164); path.lineTo (480.164); path.lineTo (480.544); Graphics.lineStyle (3,0xffffff, 1); // visualizar los pathpath.draw (gráficos);}

        Ahora cuando se ejecuta el juego en el navegador debería ver algo como esto:

        Estoy utilizando la ruta de acceso muy simple para este tutorial, pero se puede experimentar un poco con él y utilizar diferentes curvas.
        Ahora es el momento de crear los enemigos. Para ello vamos a extender la clase Imagen 3 Phaser.
        Añadir este código antes de la crear Función:
        var = Enemigo nueva Phaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: función Enemigo (escena) {Phaser.GameObjects.Image.call (esto, escena, 0, 0, ‘sprites’, ‘enemigo’); }, actualización de funciones (tiempo, delta) {}}); 1234567891011121314151617varEnemy = newPhaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: functionEnemy (escena) {Phaser.GameObjects.Image.call (esto, escena, 0 , 0, ‘sprites’, ‘enemigos’);}, actualización de funciones (tiempo, delta) {}});

        Este es nuestro enemigo de clase vacía. Ahora tenemos que añadir un poco de lógica. He decidido separar la trayectoria seguidor parte por lo que podría tener la idea básica de cómo hacer que un objeto siga una trayectoria. Por esta razón cada enemigo tendrá un objeto seguidor con dos parámetros – t que muestra el progreso en el camino del 0 – 1 comenzará a – extremo y VEC (Phaser.Math.Vector2 ()) para obtener las coordenadas X e Y de lo dado punto t. Añadir esta fila al final del constructor Enemigo:
        this.follower = {t: 0, vec: nueva Phaser.Math.Vector2 ()}; 1this.follower = {t: 0, vec: newPhaser.Math.Vector2 ()};

        Ahora añadir este método en nuestro enemigo:
        startOnPath: function () {// establecer el parámetro t en el inicio de la trayectoria de this.follower.t = 0; // obtener x e y de la path.getPoint dado t punto (this.follower.t, this.follower.vec); // establecer la x e y de nuestro enemigo a la información recibida desde el this.setPosition paso anterior (this.follower.vec.x, this.follower.vec.y); }, 123456789101112startOnPath: function () {// establecer el parámetro t en el inicio de la paththis.follower.t = 0; // obtener x e y de la path.getPoint t punto dado (this.follower.t, esto. follower.vec); // establecer la x e y de nuestro enemigo a la información recibida desde el stepthis.setPosition anterior (this.follower.vec.x, this.follower.vec.y);},

        el método startOnPath coloca el enemigo en el primer punto de nuestra ruta.
        Ahora cambia el método de actualización del Enemigo a éste:
        actualización: function (tiempo, delta) {// mover el punto t a lo largo de la trayectoria, 0 es el inicio y el 0 es el final this.follower.t + = ENEMY_SPEED * delta; // obtener las nuevas coordenadas x e y en path.getPoint vec (this.follower.t, this.follower.vec); // actualización enemigo x e y para las x recién obtenidos y this.setPosition y (this.follower.vec.x, this.follower.vec.y); // si hemos llegado al final de la ruta, retire el enemigo si (this.follower.t> = 1) {this.setActive (false); this.setVisible (false); }} 123456789101112131415161718update: función (tiempo, delta) {// mover el punto t lo largo del camino, 0 representa el inicio y 0 es el endthis.follower.t + = * ENEMY_SPEED delta; // obtener el nuevo coordenadas X e Y en vecpath .getPoint (this.follower.t, this.follower.vec); // actualización enemigo x e y para las x recién obtenidos y ythis.setPosition (this.follower.vec.x, this.follower.vec.y); // si hemos llegado al final de la ruta, retire el enemyif (this.follower.t> = 1) {this.setActive (false); this.setVisible (false);}}

        ENEMY_SPEED se define de esta manera por ahora:
        var ENEMY_SPEED = 1/10000;

        En cierto punto que querría cada tipo de enemigo que tienen diferente velocidad, pero por ahora vamos a utilizar la variable global. Ahora vamos a agregar el grupo de enemigos en el juego, añadir el siguiente código al final de la crear Función:
        enemigos = this.add.group ({ClassType: Enemy, runChildUpdate: true}); this.nextEnemy = 0; 12enemies = this.add.group ({ClassType: Enemigo, runChildUpdate: true}); this.nextEnemy = 0;

        y cambiar la función de escenas actualización a esto:
        actualización de la función (tiempo, delta) {// si es hora para la siguiente enemigo si (tiempo> this.nextEnemy) {var = enemigo enemies.get (); si (enemigo) {enemy.setActive (true); enemy.setVisible (true); // colocar al enemigo en el inicio de la ruta enemy.startOnPath (); this.nextEnemy = tiempo + 2000; }}} 123456789101112131415161718functionupdate (tiempo, delta) {// si es hora para la siguiente enemyif (tiempo> this.nextEnemy) {varenemy = enemies.get (), si (enemigo) {enemy.setActive (true); enemy.setVisible (true); // lugar al enemigo en el inicio de la pathenemy.startOnPath (); this.nextEnemy = tiempo + 2000;}}}

        Si ejecuta su juego ahora, debería ver algo como esto:


        Gran, tenemos los enemigos. Ahora es el momento para trabajar en nuestra defensa. En primer lugar voy a dibujar un una cuadrícula en nuestra gráfica por lo que estará claro en las torretas se pueden colocar. Aquí está el DibujarRejilla Función:
        función DibujarRejilla (gráficos) {Graphics.lineStyle (1, 0x0000FF, 0,8); for (var i = 0; i <8; i ++) {Graphics.moveTo (0, i * 64); Graphics.lineTo (640, i * 64); } For (var j = 0; j <10; j ++) {Graphics.moveTo (j * 64, 0); Graphics.lineTo (j * 64, 512); } Graphics.strokePath ();} 123456789101112functiondrawGrid (gráficos) {Graphics.lineStyle (1,0x0000ff, 0,8); for (vari = 0; i <8; i ++) {Graphics.moveTo (0, i * 64); gráficos. lineTo (640, i * 64);} for (varj = 0; j <10; j ++) {Graphics.moveTo (j * 64,0); Graphics.lineTo (j * 64512);} graphics.strokePath (); }

        llamada DibujarRejilla al comienzo de la función de crear de esta manera:
        gráficos var = this.add.graphics (); DibujarRejilla (gráficos); 12vargraphics = this.add.graphics (); DibujarRejilla (gráficos);

        La clase Torreta extenderán la imagen de la misma manera. La diferencia será que las torretas no se moverán pero van a ejecutar cierta acción (disparar) cada X segundos. Esta es la clase de la torreta, se puede colocar inmediatamente después del Enemigo:
        var Torreta = nuevo Phaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: función torreta (escena) {Phaser.GameObjects.Image.call (esto, escena, 0, 0, ‘sprites’, ‘torreta’); this.nextTic = 0;}, // vamos a colocar la torreta de acuerdo con el lugar de rejilla: la función (i, j) {this.y = I * 64 + 64/2; this.x = j * 64 + 64 / 2; map [i] [j] = 1;}, actualizar: function (tiempo, delta) {// tiempo para disparar si (tiempo> this.nextTic) {this.nextTic = tiempo + 1000;}}}); 12345678910111213141516171819202122232425varTurret = newPhaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: functionTurret (escena) {Phaser.GameObjects.Image.call (esto, escena, 0,0, ‘sprites’, ‘torreta’); this.nextTic = 0;}, // vamos a colocar la torreta de acuerdo con la gridplace: function (i, j) {this.y = i * 64 + 64/2; this.x = j * 64 + 64/2; map [ i] [j] = 1;}, actualizar: function (tiempo, delta) {// tiempo para shootif (tiempo> this.nextTic) {this.nextTic = tiempo + 1000;}}});

        Ahora creará el grupo torretas. Añadir este código al final de la función de crear:
        torretas = this.add.group ({ClassType: Torreta, runChildUpdate: true}); 1turrets = this.add.group ({ClassType: Torreta, runChildUpdate: true});

        Para añadir una torreta para el juego que se añadir una función de la entrada del usuario pointerdown. Añadir esto al final de la función de crear:
        this.input.on ( ‘pointerdown’, placeTurret); 1this.input.on ( ‘pointerdown’, placeTurret);

        Y aquí está la placeTurret Función:
        función placeTurret (puntero) {var i = Math.floor (pointer.y / 64); var j = Math.floor (pointer.x / 64); si (canPlaceTurret (i, j)) {var torreta = turrets.get (); si (torreta) {turret.setActive (true); turret.setVisible (true); turret.place (i, j); }}} 12345678910111213functionplaceTurret (puntero) {vari = Math.floor (pointer.y / 64); varj = Math.floor (pointer.x / 64), si (canPlaceTurret (i, j)) {varturret = turrets.get ( ); si (torreta) {turret.setActive (true); turret.setVisible (true); turret.place (i, j);}}}

        Y aquí está la canPlaceTurret : < / p>
        canPlaceTurret función (i, j) {mapa retorno [i] [j] === 0;} 123functioncanPlaceTurret (i, j) {returnmap [i] [j] === 0;}

        Aquí se utiliza una matriz para comprobar si el lugar donde queremos colocar una torreta está libre. Cuando colocamos una torreta establecemos el valor a 1. Los elementos de trayecto están predefinidos con -1.

        Añadir esto a la parte superior del juego:
        mapa var = [[0, -1, 0, 0, 0, 0, 0, 0, 0, 0], [0, -1, 0, 0, 0, 0, 0, 0, 0, 0], [0, -1, -1, -1, -1, -1, -1, -1, 0, 0], [0, 0, 0, 0, 0, 0, 0, -1, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, -1, 0, 0], [0, 0, 0, 0, 0, 0, 0, -1, 0, 0], [0 , 0, 0, 0, 0, 0, 0, -1, 0, 0], [0, 0, 0, 0, 0, 0, 0, -1, 0, 0]]; 12345678varmap = [[0 , -1,0,0,0,0,0,0,0,0], [0, -1,0,0,0,0,0,0,0,0], [0, -1, -1, -1, -1, -1, -1, -1,0,0], [0,0,0,0,0,0,0, -1,0,0], [0,0 , 0,0,0,0,0, -1,0,0], [0,0,0,0,0,0,0, -1,0,0], [0,0,0,0 , 0,0,0, -1,0,0], [0,0,0,0,0,0,0, -1,0,0]];

        Ahora bien, si se ejecuta el juego en el navegador que debe tener algo como esto:


        Al hacer clic en el juego y el lugar está vacío debe ser capaz de colocar una torreta. Pero nuestras torretas son bastante inactivo – no hacen nada para los enemigos. Su tiempo para hacerlas disparar y matar. Vamos a crear las balas de la misma manera como los enemigos y las torres.
        Bala var = nuevo Phaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: función de la bala (escena) {Phaser.GameObjects.Image.call (esto, escena, 0, 0, ‘bala’); this.dx = 0; this.dy = 0; this.lifespan = 0; this.speed = Phaser.Math.GetSpeed ​​(600, 1);}, fuego: function (x, y, ángulo) {this.setActive (true); esta .setVisible (true); // las balas de fuego desde el centro de la pantalla a la dada this.setPosition x / y (x, y); // no es necesario para hacer girar las balas, ya que son redondos // esto. setRotation (ángulo); this.dx = Math.cos (ángulo); this.dy = Math.sin (ángulo); this.lifespan = 300;}, actualizar: function (tiempo, delta) {this.lifespan – = delta ; this.x + = this.dx * (this.speed delta *); this.y + = this.dy * (this.speed delta *), si (this.lifespan <= 0) {this.setActive (false ); this.setVisible (false);}}}); 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849varBullet = newPhaser.Class ({Extiende: Phaser.GameObjects.Image, initialize: functionBullet (escena) {Phaser.GameObjec ts.Image.call (esto, escena, 0,0, 'bala'); this.dx = 0; this.dy = 0; this.lifespan = 0; this.speed = Phaser.Math.GetSpeed ​​(600,1 );}, fuego: function (x, y, ángulo) {this.setActive (true); this.setVisible (true); // balas fuego desde el centro de la pantalla a la dada x / ythis.setPosition (x, y); // no necesitamos para girar las balas, ya que son ronda // this.setRotation (ángulo); this.dx = Math.cos (ángulo); this.dy = Math.sin (ángulo); esta .lifespan = 300;}, actualización de funciones (tiempo, delta) {this.lifespan- = delta; this.x + = this.dx * (* this.speed delta); this.y + = this.dy * (esto. velocidad * Delta), si (this.lifespan <= 0) {this.setActive (falso); this.setVisible (falso);}}});

        y añadir fila al final de la función de crear: < / p>
        balas = this.add.group ({ClassType: bala, runChildUpdate: true}); 1bullets = this.add.group ({ClassType: bala, runChildUpdate: true});

        Ahora vamos a crear una función para las torretas a su uso, cuando necesitan balas disparar. La función obtendrá tres parámetros – X, Y y el ángulo entre la torreta y el enemigo. Esta función le llamará bullet.fire con los mismos parámetros.
        función addBullet (x, y, el ángulo) {bullet var = bullets.get (); si (bala) {bullet.fire (x, y, el ángulo); }} 1234567functionaddBullet (x, y, ángulo) {varbullet = bullets.get (), si (bala) {bullet.fire (x, y, ángulo);}}

        Y necesitamos una función más para hacer que nuestras torretas trabajo. Será recoger y devolver un enemigo apropiado. En nuestro caso, el primer enemigo de la serie de enemigos que está cerca de la torreta. Puede jugar un poco y hacer la función de llegar al enemigo más cercano. Si no hay un enemigo cerca de la torreta se volverá falsa. Esta función utilizará Phaser.Math.Distance.Between para calcular la distancia entre dos puntos.
        función getEnemy (x, y, distancia) {enemyUnits var = enemies.getChildren (); for (var i = 0; i en getEnemy iteramos en los niños del grupo de enemigos y prueba si el niño está activo y luego, si la distancia es menor que el tercer parámetro.

        Ahora tenemos que cambiar la torreta de utilizar estas dos funciones para disparar balas. Cambiar su método de actualización a esto:
        cambio: la función (tiempo, delta) {if (tiempo> this.nextTic) {this.fire (); this.nextTic = tiempo + 1000; }} 1234567update: función (tiempo, delta) {if (tiempo> this.nextTic) {this.fire (); this.nextTic = tiempo + 1000;}}

        Y ahora tenemos que crear el método de fuego. Que recibiría un enemigo a través de la función getEnemy. A continuación, vamos a calcular el ángulo entre la torreta y el enemigo y nosotros le llamaremos addBullet con este ángulo. A continuación, vamos a girar nuestra torreta hacia este enemigo. Debido a que la imagen de la torreta está apuntando hacia arriba tenemos que ajustar el ángulo un poco. Y debido a que el ángulo de la clase La imagen es en grados necesitamos multiplicar el valor actual con Phaser.Math.RAD_TO_DEG.

        Añadir este código a la clase Torreta:
        fuego: function () {var enemigo = getEnemy (this.x, this.y, 100); si (enemigo) {ángulo var = Phaser.Math.Angle.Between (this.x, this.y, enemy.x, enemy.y); addBullet (this.x, this.y, ángulo); this.angle = (ángulo + Math.PI / 2) * Phaser.Math.RAD_TO_DEG; }}, 12345678fire: function () {varenemy = getEnemy (this.x, this.y, 100); if (enemigos) {varangle = Phaser.Math.Angle.Between (this.x, this.y, enemy.x , enemy.y); addBullet (this.x, this.y, ángulo); = this.angle (ángulo + Math.PI / 2) * Phaser.Math.RAD_TO_DEG;}},

        Ahora debería ver la torretas disparando a los enemigos. Pero las balas y los enemigos no interactúan. Para hacerlos interactuar vamos a utilizar la física de juegos electrónicos. Cambiar el juego de objetos de configuración para esto:
        var config = {type: Phaser.AUTO, padre: ‘contenido’, anchura: 640, altura: 512, la física: {default: ‘Arcade’}, escena: {clave: ‘principal’, precarga: precarga, crear: crear , fecha: update}}; 123456789101112131415varconfig = {type: Phaser.AUTO, padre: ‘contenido’, anchura: 640, altura: 512, la física: {default: ‘Arcade’}, escena: tecla {: ‘principal’, precarga : precarga, crear: crear, actualizar: update}};

        En crear un cambio método
        enemigos = this.add.group ({ClassType: Enemy, runChildUpdate: true}); 1enemies = this.add.group ({ClassType: Enemy, runChildUpdate: true});

        a
        enemigos = this.physics.add.group ({ClassType: Enemy, runChildUpdate: true}); 1enemies = this.physics.add.group ({ClassType: Enemy, runChildUpdate: true});

        y
        balas = this.add.group ({ClassType: bala, runChildUpdate: true}); 1bullets = this.add.group ({ClassType: bala, runChildUpdate: true});

        a
        balas = this.physics.add.group ({ClassType: bala, runChildUpdate: true}); 1bullets = this.physics.add.group ({ClassType: bala, runChildUpdate: true});

        Ahora vamos a hacer la enemigos con 100 puntos de golpe y cada bala hará 50 de daño.
        Añadir esto al final del Enemigo método startOnPath:
        this.hp = 100;

        Y tenemos que añadir este método por lo que nuestro enemigo recibirá el daño:
        receiveDamage: function (daño) {this.hp – = daño; // Si HP cae por debajo de 0 desactivamos este enemigo si (this.hp <= 0) {this.setActive (false); this.setVisible (false); }}, 123456789receiveDamage: function (daño) {this.hp- ​​= daño; // si hp cae por debajo de 0 desactivamos esta enemyif (this.hp <= 0) {this.setActive (false); this.setVisible (false) ;}},

        Estamos casi listos con la interacción entre las balas y los enemigos. Vamos a utilizar el método de superposición para hacerlo.
        Añadir este código al final de la función de crear:
        this.physics.add.overlap (enemigos, balas, damageEnemy); 1this.physics.add.overlap (enemigos, balas, damageEnemy);

        Y ahora tenemos que crear la función damageEnemy:
        función damageEnemy (enemigo, bala) {// sólo si tanto enemigo y balas están vivos si (enemy.active === === cierto && bullet.active true) {// eliminamos la bala de inmediato bullet.setActive (falso ); bullet.setVisible (false); // disminuir la hp enemigo con BULLET_DAMAGE enemy.receiveDamage (BULLET_DAMAGE); }} 1234567891011functiondamageEnemy (enemigo, bala) {// sólo si tanto enemigo y bala son aliveif (enemy.active === === cierto && bullet.active true) {// eliminamos la bala de inmediato bullet.setActive (falso) ; bullet.setVisible (false); // disminuir el CV enemigo con BULLET_DAMAGEenemy.receiveDamage (BULLET_DAMAGE);}}

        Y ahora usted debe ser capaz de ver los enemigos desaparecen cuando es golpeado dos veces. Que la variable de dañar algo más pequeño como el 20 o el enemigo CV mucho más para probar el rendimiento. Se puede jugar con los valores y otras propiedades para obtener una mejor comprensión de la mecánica del juego.

        Conclusión
        Después de terminar este tutorial usted debe ser capaz de crear un juego de torre de defensa básico con Phaser 3. Hay mucho más por hacer en un juego de torre de defensa; por ejemplo, se puede añadir una interfaz de usuario, una variedad de torres y enemigos, animaciones y efectos. Usted puede tratar de hacer el juego en varias escenas, la escena principal del menú, más de juego y escenas de victoria o cuadros de diálogo, las puntuaciones de los usuarios y mucho más. Phaser 3 con su estructura modular y la funcionalidad rica es una gran manera de desarrollar juegos HTML5 profesionales en un corto período de tiempo.

        Mensajes relacionados
        Phaser 3 juego con los barcos azules y rojas duelo Phaser 3, scroll infinito, avión juego de vuelo

Deja una respuesta

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