SpaceShooter en Phaser 3

¡Hola! Esta es una traducción del curso (enlace al original al final del artículo), que cubre la creación de un shooter espacial usando Phaser 3. El curso constará de catorce pasos, en cada uno de los cuales se resolverá una tarea específica. al crear un proyecto. Antes de comenzar el curso, es recomendable conocer los conceptos básicos de JavaScript.





Paso uno. Configurar un servidor web

Lo primero que debe hacer es configurar su servidor web. A pesar de que los juegos de phaser se ejecutan en el navegador, desafortunadamente no puede ejecutar el archivo html localmente directamente desde el sistema de archivos. Al solicitar archivos a través del protocolo http, la seguridad del servidor le permite acceder solo a los archivos que tiene permitidos. Al descargar un archivo del sistema de archivos local (file: //), su navegador lo restringe fuertemente por razones obvias de seguridad. Debido a esto, necesitaremos alojar nuestro juego en un servidor web local. Puede utilizar cualquier servidor web que desee, ya sea OpenServer o cualquier otro.





Segundo paso. Crear archivos y carpetas requeridos

Encuentre dónde su servidor web aloja los archivos del sitio y cree una carpeta con su proyecto. Nómbrelo como sea conveniente para usted. Dentro del proyecto, cree un archivo index.html. Nuestro archivo de índice es donde declararemos la ubicación del script phaser y otros scripts del juego.





A continuación, necesitamos crear dos nuevas carpetas: contenido (sprites, audio, etc.) y js (phaser y scripts del juego). Ahora, dentro de la carpeta js, debe crear 4 archivos: SceneMainMenu.js, SceneMain.js, SceneGameOver.js y game.js.





Por el momento, la estructura de nuestro proyecto debería verse así:





estructura del proyecto al inicio
estructura del proyecto al inicio

content. , ().





:





Sprites (images)





  • sprBtnPlay.png ( "Play")





  • sprBtnPlayHover.png ( "Play" )





  • sprBtnPlayDown.png ( "Play" )





  • sprBtnRestart.png ( "Restart")





  • sprBtnRestartHover.png ( "Restart" )





  • sprBtnRestartDown ( "Restart" )





  • sprBg0.png ( )





  • sprBg1.png ( )





  • sprEnemy0.png ( )





  • sprEnemy1.png ( )





  • sprEnemy2.png ( )





  • sprLaserEnemy.png ( )





  • sprLaserPlayer.png ( )





  • sprExplosion.png ( )





  • sprPlayer.png ( )





Audio (.wav files)





  • sndExplode0.wav ( )





  • sndExplode1.wav ( )





  • sndLaser.wav ( )





  • sndBtnOver.wav ( )





  • sndBtnDown.wav ( )





.

Phaser. . phaser.js phaser.min.js . , phaser.js , . , - , phaser.min.js. . js .





. Index.html

, , index.html, . IDE .





index.html :





<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta lang="en-us">
    <title>Space Shooter</title>
    <script src="js/phaser.js"></script> <!--     ,    . -->
  </head>
  <body>
    <script src="js/Entities.js"></script>
    <script src="js/SceneMainMenu.js"></script>
    <script src="js/SceneMain.js"></script>
    <script src="js/SceneGameOver.js"></script>
    <script src="js/game.js"></script>
  </body>
</html>
      
      



. , JavaScript . ( Scene) game.js.





.

game.js :





var config = {}
      
      



, phaser. :





type: Phaser.WEBGL,
width: 480,
height: 640,
backgroundColor: "black",
physics: {
  default: "arcade",
  arcade: {
    gravity: { x: 0, y: 0 }
  }
},
scene: [],
pixelArt: true,
roundPixels: true
      
      



, , , WebGL, Canvas. , width height, , . backgroundColor . , physics, , , arcade. , - . physics, (gravity) . scene, , . , , Phaser (pixelArt roundPixels) , , .





scene: [
  SceneMainMenu,
  SceneMain,
  SceneGameOver
],
      
      



. Phaser, :





var game = new Phaser.Game(config);
      
      



game.js ! :





var config = {
    type: Phaser.WEBGL,
    width: 480,
    height: 640,
    backgroundColor: "black",
    physics: {
      default: "arcade",
      arcade: {
        gravity: { x: 0, y: 0 }
      }
    },
    scene: [
        SceneMainMenu,
        SceneMain,
        SceneGameOver
    ],
    pixelArt: true,
    roundPixels: true
}

var game = new Phaser.Game(config);
      
      



.

SceneMainMenu.js :





class SceneMainMenu extends Phaser.Scene 
{
  constructor() {
    super({ key: "SceneMainMenu" });
  }
  create() {
    this.scene.start("SceneMain");
  }
}
      
      



SceneMainMenu, Phaser.Scene. : constructor create. , ( ). :





super({ key: "SceneMainMenu" });
      
      



:





var someScene = new Phaser.Scene({ key: "SceneMainMenu" });
      
      



, Phaser, , , . create . create :





this.scene.start("SceneMain");
      
      



, , . . .





SceneMain.js SceneGameOver.js.





SceneMain.js:





class SceneMain extends Phaser.Scene {
  constructor() {
    super({ key: "SceneMain" });
  }
  create() {}
}
      
      



SceneGameOver.js:





class SceneGameOver extends Phaser.Scene {
  constructor() {
    super({ key: "SceneGameOver" });
  }
  create() {}
}
      
      



, , :





.

, SceneMain preload. constructor create. :





class SceneMain extends Phaser.Scene {
  constructor() {
    super({ key: "SceneMain" });
  }
  preload() {
  
  }
  create() {
	  
  }
}
      
      



. , preload :





this.load.image("sprBg0", "content/sprBg0.png");
      
      



imageKey. . - , . , . , preload :





preload() {
  this.load.image("sprBg0", "content/sprBg0.png");
  this.load.image("sprBg1", "content/sprBg1.png");
  this.load.spritesheet("sprExplosion", "content/sprExplosion.png", {
    frameWidth: 32,
    frameHeight: 32
  });
  this.load.spritesheet("sprEnemy0", "content/sprEnemy0.png", {
    frameWidth: 16,
    frameHeight: 16
  });
  this.load.image("sprEnemy1", "content/sprEnemy1.png");
  this.load.spritesheet("sprEnemy2", "content/sprEnemy2.png", {
    frameWidth: 16,
    frameHeight: 16
  });
  this.load.image("sprLaserEnemy0", "content/sprLaserEnemy0.png");
  this.load.image("sprLaserPlayer", "content/sprLaserPlayer.png");
  this.load.spritesheet("sprPlayer", "content/sprPlayer.png", {
    frameWidth: 16,
    frameHeight: 16
  });
}
      
      



, image, spritesheet. , , . Spritesheet - , . spritesheet .





. , . preload :





this.load.audio("sndExplode0", "content/sndExplode0.wav");
this.load.audio("sndExplode1", "content/sndExplode1.wav");
this.load.audio("sndLaser", "content/sndLaser.wav");
      
      



. .





, , . create() SceneMain :





this.anims.create({
  key: "sprEnemy0",
  frames: this.anims.generateFrameNumbers("sprEnemy0"),
  frameRate: 20,
  repeat: -1
});

this.anims.create({
  key: "sprEnemy2",
  frames: this.anims.generateFrameNumbers("sprEnemy2"),
  frameRate: 20,
  repeat: -1
});

this.anims.create({
  key: "sprExplosion",
  frames: this.anims.generateFrameNumbers("sprExplosion"),
  frameRate: 20,
  repeat: 0
});

this.anims.create({
  key: "sprPlayer",
  frames: this.anims.generateFrameNumbers("sprPlayer"),
  frameRate: 20,
  repeat: -1
});
      
      



- , . (, ), explosions. :





this.sfx = {
  explosions: [
    this.sound.add("sndExplode0"),
    this.sound.add("sndExplode1")
  ],
  laser: this.sound.add("sndLaser")
};
      
      



, :





this.scene.sfx.laser.play();
      
      



Game Over. SceneMainMenu.js (preload()) SceneMainMenu. , :





, . , . Chrome Firefox, F12, . (Console), ( .) , !





.

, js Entities.js. . , , . ., . Entities.js index.html SceneMainMenu.js. Entity.





class Entity {
	constructor(scene, x, y, key, type) {}
}
      
      



, . , , , , . , Phaser.Scene . Entity:





class Entity extends Phaser.GameObjects.Sprite
      
      



, super . , , , , , . , Phaser.GameObjects.Sprite .





(scene, x, y, key type, , .) super , :





super(scene, x, y, key);
      
      



super :





this.scene = scene;
this.scene.add.existing(this);
this.scene.physics.world.enableBody(this, 0);
this.setData("type", type);
this.setData("isDead", false);
      
      



, . . , . . .





, . Entity Player , Entity. Player : scene, x, y key. super , :





super(scene, x, y, key, "Player");
      
      



, . / , . super :





this.setData("speed", 200);
      
      



:





this.play("sprPlayer");
      
      



, .





moveUp() {
  this.body.velocity.y = -this.getData("speed");
}

moveDown() {
  this.body.velocity.y = this.getData("speed");
}

moveLeft() {
  this.body.velocity.x = -this.getData("speed");
}

moveRight() {
  this.body.velocity.x = this.getData("speed");
}
      
      



x y.





update(). update() moveRight. :





this.body.setVelocity(0, 0);

this.x = Phaser.Math.Clamp(this.x, 0, this.scene.game.config.width);
this.y = Phaser.Math.Clamp(this.y, 0, this.scene.game.config.height);
      
      



! . , . , . create . :





this.player = new Player(
  this,
  this.game.config.width * 0.5,
  this.game.config.height * 0.5,
  "sprPlayer"
);

      
      



. SceneMain. . , , . , SceneMain . this.player , . :





this.player.update();

if (this.keyW.isDown) {
  this.player.moveUp();
}
else if (this.keyS.isDown) {
  this.player.moveDown();
}
if (this.keyA.isDown) {
  this.player.moveLeft();
}
else if (this.keyD.isDown) {
  this.player.moveRight();
}
      
      



, this.player.update() , , , . create() SceneMain :





this.keyW = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W);
this.keyS = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S);
this.keyA = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
this.keyD = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
this.keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
      
      



, W, S, A, D. ( .)





.

Entities.js . Entities.js , ChaserShip, GunShip CarrierShip:





class ChaserShip extends Entity {
  constructor(scene, x, y) {
    super(scene, x, y, "sprEnemy1", "ChaserShip");
  }
}

class GunShip extends Entity {
  constructor(scene, x, y) {
    super(scene, x, y, "sprEnemy0", "GunShip");
    this.play("sprEnemy0");
  }
}

class CarrierShip extends Entity {
  constructor(scene, x, y) {
    super(scene, x, y, "sprEnemy2", "CarrierShip");
    this.play("sprEnemy2");
  }
}
      
      



ChaserShip, GunShip CarrierShip Entity, . . super :





this.body.velocity.y = Phaser.Math.Between(50, 100);
      
      



50 100. , .





SceneMain.js. , , , , , . create this.keySpace :





this.enemies = this.add.group();
this.enemyLasers = this.add.group();
this.playerLasers = this.add.group();
      
      



, - , . . -, EnemyLaser . Entities.js. Entity.





class EnemyLaser extends Entity {
  constructor(scene, x, y) {
    super(scene, x, y, "sprLaserEnemy0");
    this.body.velocity.y = 200;
  }
}
      
      



, . , , .





this.shootTimer = this.scene.time.addEvent({
  delay: 1000,
  callback: function() {
    var laser = new EnemyLaser(
      this.scene,
      this.x,
      this.y
    );
    laser.setScale(this.scaleX);
    this.scene.enemyLasers.add(laser);
  },
  callbackScope: this,
  loop: true
});
      
      



, this.shootTimer. GunShip onDestroy. onDestroy - , Phaser, . , , . onDestroy GunShip :





if (this.shootTimer !== undefined) {
  if (this.shootTimer) {
    this.shootTimer.remove(false);
  }
}
      
      



:





, , . . , , , . SceneMain.js .





delay: 1000,
      
      



Entities.js, ChaserShip:





this.states = {
  MOVE_DOWN: "MOVE_DOWN",
  CHASE: "CHASE"
};
this.state = this.states.MOVE_DOWN;
      
      



: , -, MOVE_DOWN.





ChaserShip. - , -. , . Entities.js, ChaserShip :





if (!this.getData("isDead") && this.scene.player) {
  if (Phaser.Math.Distance.Between(
    this.x,
    this.y,
    this.scene.player.x,
  	this.scene.player.y
  ) < 320) {
    this.state = this.states.CHASE;
  }

  if (this.state == this.states.CHASE) {
    var dx = this.scene.player.x - this.x;
    var dy = this.scene.player.y - this.y;

    var angle = Math.atan2(dy, dx);

    var speed = 100;
    this.body.setVelocity(
      Math.cos(angle) * speed,
      Math.sin(angle) * speed
    );
  }
}
      
      



- . , 320 , . , - , ( ) :





if (this.x < this.scene.player.x) {
  this.angle -= 5;
} else {
  this.angle += 5;
} 
      
      



-, SceneMain.js getEnemiesByType. :





getEnemiesByType(type) {
  var arr = [];
  for (var i = 0; i < this.enemies.getChildren().length; i++) {
    var enemy = this.enemies.getChildren()[i];
    if (enemy.getData("type") == type) {
      arr.push(enemy);
    }
  }
  return arr;
}
      
      



. , , .





getEnemiesByType, spawner. :





:





var enemy = null;

if (Phaser.Math.Between(0, 10) >= 3) {
  enemy = new GunShip(
  	this,
  	Phaser.Math.Between(0, this.game.config.width),
		0
	);
} else if (Phaser.Math.Between(0, 10) >= 5) {
  if (this.getEnemiesByType("ChaserShip").length < 5) {
    enemy = new ChaserShip(
    	this,
    	Phaser.Math.Between(0, this.game.config.width),
			0
		);
	}
} else {
  enemy = new CarrierShip(
  	this,
  	Phaser.Math.Between(0, this.game.config.width),
		0
	);
}

if (enemy !== null) {
  enemy.setScale(Phaser.Math.Between(10, 20) * 0.1);
	this.enemies.add(enemy);
}
      
      



, , : GunShip, ChaserShip CarrierShip, . enemy, enemies. CarrierShip , , ChaserShip, . , . Entity, , , Phaser.GameObjects.Sprite , Phaser.GameObjects.Sprite.





this.enemies. :





for (var i = 0; i < this.enemies.getChildren().length; i++) {
	var enemy = this.enemies.getChildren()[i];
	enemy.update();
}
      
      



, , , .





.

Player :





this.setData("isShooting"false);
this.setData("timerShootDelay"10);
this.setData("timerShootTick"this.getData("timerShootDelay") - 1);
      
      



, “ ”. . , , . “ ”:





if (this.getData("isShooting")) {
  if (this.getData("timerShootTick") < this.getData("timerShootDelay")) {
    //     timerShootTick  ,      timerShootDelay
    this.setData("timerShootTick"this.getData("timerShootTick") + 1);
  } else { //  " " :
    var laser = new PlayerLaser(this.scene, this.x, this.y);
    this.scene.playerLasers.add(laser);

    this.scene.sfx.laser.play(); //    
    this.setData("timerShootTick"0);
  }
}
      
      



, , Entities.js . Player EnemyLaser. , , , , . PlayerLaser , EnemyLaser. , . , , . :





class PlayerLaser extends Entity {
    constructor(scene, x, y) {
        super(scene, x, y, "sprLaserPlayer");
        this.body.velocity.y = -200;
    }
}
      
      



, , , - SceneMain.js :





if (this.keySpace.isDown) {
	this.player.setData("isShooting"true);
} else {
	this.player.setData("timerShootTick"this.player.getData("timerShootDelay") - 1);
	this.player.setData("isShooting"false);
}
      
      



, !





.

, , . , , . , , :





, . for, :





for (var i = 0; i < this.enemies.getChildren().length; i++) {
	var enemy = this.enemies.getChildren()[i];

	enemy.update();
}
      
      



enemy.update(), :





if (enemy.x < -enemy.displayWidth ||
	enemy.x > this.game.config.width + enemy.displayWidth ||
	enemy.y < -enemy.displayHeight * 4 ||
	enemy.y > this.game.config.height + enemy.displayHeight) {
    if (enemy) {
      if (enemy.onDestroy !== undefined) {
      	enemy.onDestroy();
      }

      enemy.destroy();
    }
}
      
      



:





        for (var i = 0; i < this.enemyLasers.getChildren().length; i++) {
            var laser = this.enemyLasers.getChildren()[i];
            laser.update();
        
            if (laser.x < -laser.displayWidth ||
                laser.x > this.game.config.width + laser.displayWidth ||
                laser.y < -laser.displayHeight * 4 ||
                laser.y > this.game.config.height + laser.displayHeight) {
                if (laser) {
                laser.destroy();
                }
            }
        }
    
        for (var i = 0; i < this.playerLasers.getChildren().length; i++) {
            var laser = this.playerLasers.getChildren()[i];
            laser.update();
        
            if (laser.x < -laser.displayWidth ||
                laser.x > this.game.config.width + laser.displayWidth ||
                laser.y < -laser.displayHeight * 4 ||
                laser.y > this.game.config.height + laser.displayHeight) {
                if (laser) {
                laser.destroy();
                }
            }
        }
      
      



.

, SceneMain.js create. , , . . , , , , . . :





this.physics.add.collider(this.playerLasers, this.enemies, function(playerLaser, enemy{

});
      
      



, , :





if (enemy) {
  if (enemy.onDestroy !== undefined) {
  	enemy.onDestroy();
  }

  enemy.explode(true);
  playerLaser.destroy();
}
      
      



, , explode - . , , Entities.js Entity. Entity explode. canDestroy . canDestroy , explode . explode :





explode(canDestroy) {
  if (!this.getData("isDead")) {
    //     
    this.setTexture("sprExplosion");  //       ,     this.anims.create     
    this.play("sprExplosion"); //  
    //         this.sfx  SceneMain
    this.scene.sfx.explosions[Phaser.Math.Between(0this.scene.sfx.explosions.length - 1)].play();
    if (this.shootTimer !== undefined) {
      if (this.shootTimer) {
      	this.shootTimer.remove(false);
      }
    }
    this.setAngle(0);
    this.body.setVelocity(00);
    this.on('animationcomplete'function({
      if (canDestroy) {
      	this.destroy();
      } else {
      	this.setVisible(false);
      }
    }, this);
    this.setData("isDead"true);
  }
}
      
      



, , , . , SceneMain.js :





if (!this.player.getData("isDead")) {
  this.player.update();
  if (this.keyW.isDown) {
  	this.player.moveUp();
  }
  else if (this.keyS.isDown) {
  	this.player.moveDown();
  }
  if (this.keyA.isDown) {
  	this.player.moveLeft();
  }
  else if (this.keyD.isDown) {
  	this.player.moveRight();
  }

  if (this.keySpace.isDown) {
  	this.player.setData("isShooting"true);
  }
  else {
  	this.player.setData("timerShootTick"this.player.getData("timerShootDelay") - 1);
  	this.player.setData("isShooting"false);
  }
}
      
      



. .





.

, , , . , , . , .





. . -, Entities.js. , . .





class ScrollingBackground {
  constructor(scene, key, velocityY) {
    
  }
}
      
      



, , . , . . :





this.scene = scene;
this.key = key;
this.velocityY = velocityY;
      
      



createLayers. , .





this.layers = this.scene.add.group();
      
      



createLayers :





for (var i = 0; i < 2; i++) {
  var layer = this.scene.add.sprite(0, 0, this.key);
  layer.y = (layer.displayHeight * i);
  var flipX = Phaser.Math.Between(0, 10) >= 5 ? -1 : 1;
  var flipY = Phaser.Math.Between(0, 10) >= 5 ? -1 : 1;
  layer.setScale(flipX * 2, flipY * 2);
  layer.setDepth(-5 - (i - 1));
  this.scene.physics.world.enableBody(layer, 0);
  layer.body.velocity.y = this.velocityY;

  this.layers.add(layer);
}
      
      



, . for. .





, , i.





createLayers .





this.createLayers();
      
      



SceneMain.js . this.player this.sfx.





this.backgrounds = [];
for (var i = 0; i < 5; i++) { //    
  var bg = new ScrollingBackground(this"sprBg0", i * 10);
  this.backgrounds.push(bg);
}
      
      



, . Entities.js :





if (this.layers.getChildren()[0].y > 0) {
  for (var i = 0; i < this.layers.getChildren().length; i++) {
  	var layer = this.layers.getChildren()[i];
  	layer.y = (-layer.displayHeight) + (layer.displayHeight * i);
  }
}
      
      



for (var i = 0; i < this.backgrounds.length; i++) {
	this.backgrounds[i].update();
}
      
      



! , , .





, GameOver. SceneMainMenu , SceneMain. , SceneMainMenu. create:





this.sfx = {
	btnOverthis.sound.add("sndBtnOver"),
	btnDownthis.sound.add("sndBtnDown")
};
      
      



, .





this.btnPlay = this.add.sprite(
	this.game.config.width * 0.5,
	this.game.config.height * 0.5,
	"sprBtnPlay"
);
      
      



SceneMain, . , this.btnPlay:





this.btnPlay.setInteractive();
      
      



, , over, out, down up. , . , , - pointerover. sprBtnPlayHover.png, . , :





this.btnPlay.on("pointerover"function({
	this.btnPlay.setTexture("sprBtnPlayHover"); //    
	this.sfx.btnOver.play(); //      
}, this);
      
      



pointerout. . :





this.btnPlay.on("pointerout", function() {
  this.setTexture("sprBtnPlay");
});
      
      



, , , .





pointerdown. sprBtnPlayDown.png.





this.btnPlay.on("pointerdown"function({
	this.btnPlay.setTexture("sprBtnPlayDown");
	this.sfx.btnDown.play();
}, this);
      
      



pointerup .





this.btnPlay.on("pointerup"function({
	this.setTexture("sprBtnPlay");
}, this);
      
      



pointerup, . pointerup :





this.btnPlay.on("pointerup"function({
	this.btnPlay.setTexture("sprBtnPlay");
	this.scene.start("SceneMain");
}, this);
      
      



, !





, , . - . , . pointerup:





this.title = this.add.text(this.game.config.width * 0.5128"SPACE SHOOTER", {
	fontFamily'monospace',
	fontSize48,
	fontStyle'bold',
	color'#ffffff',
	align'center'
});
      
      



, . , title:





this.title.setOrigin(0.5);
      
      



Con esto concluye el artículo. Este es mi primer artículo sobre Habré y traté de transmitir el significado del original con la mayor precisión posible. Si nota alguna inexactitud o error, escriba los comentarios y lo discutiremos.





Enlace al artículo original





Enlace a la fuente original








All Articles