Создание вашей первой игры Phaser 3 на современном Javascript. Часть 5.

В четвертой части мы добавили сбор звезд и отображение счета. Если вы еще этого не сделали, то перейдите к четвертой части и выполните указания в ней.

Мы собираемся значительно отклониться от официального руководства Phaser, так что держитесь за свои шляпы.

Для начала мы будем использовать класс BombSpawner (производитель бомб) для создания бомб.

import Phaser from 'phaser'

export default class BombSpawner
{
    /**
     * @param {Phaser.Scene} scene
     */
    constructor(scene, bombKey = 'bomb')
    {
        this.scene = scene;
        this.key = bombKey;

        this._group = this.scene.physics.add.group();
    }

    get group()
    {
        return this._group;
    }

    spawn(playerX = 0)
    {
        const x = (playerX < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);

        const bomb = this.group.create(x, 16, this.key);
        bomb.setBounce(1);
        bomb.setCollideWorldBounds(true);
        bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);

        return bomb;
    }
}

Мы создали файл BombSpawner.js в папке src/scenes, но вы можете выбрать другое место.

Наличие отдельного класса для логики создания бомб позволяет нам инкапсулировать общую логику и повторно использовать код. Кроме того, не загромождается сцена GameScene. По этим же причинам мы создали класс ScoreLabel.

Класс BombSpawner можно использовать в нескольких сценах и создавать бомбы в большем количестве мест без дублирования кода.

Объект constructor принимает ссылку на сцену и необязательный параметр bombKey. Обратите внимание, что в строке 25 мы используем свойство key . Таким образом мы продолжим сохранять наши строковые литералы по принципу DRY. Оба параметра будут переданы из GameScene.

Примечание: комментарии в синтаксисе JSDoc для constructor предоставляют информацию о типе данных. Это помогает с автозаполнением (вроде IntelliSense от Microsoft) в редакторе кода.

Теперь давайте создадим экземпляр BombSpawner в GameScene.

import Phaser from 'phaser'

import ScoreLabel from '../ui/ScoreLabel'
// добавляем импорт класса
import BombSpawner from './BombSpawner'

const GROUND_KEY = 'ground';
const DUDE_KEY = 'dude';
const STAR_KEY = 'star';
// создаем константу
const BOMB_KEY = 'bomb';

export default class GameScene extends Phaser.Scene
{
    constructor()
    {
        super('game-scene');

        this.player = undefined;
        this.cursors = undefined;
        this.scoreLabel = undefined;
        // добавляем свойство
        this.bombSpawner = undefined;
    }

    preload()
    {
        this.load.image('sky', 'assets/sky.png');
        this.load.image(GROUND_KEY, 'assets/platform.png');
        this.load.image(STAR_KEY, 'assets/star.png');
        // меняем строковый литерал на константу
        this.load.image(BOMB_KEY, 'assets/bomb.png');
        this.load.spritesheet(DUDE_KEY, 
            'assets/dude.png',
            { frameWidth: 32, frameHeight: 48 }
        );
    }

    create()
    {
        this.add.image(400, 300, 'sky');

        const platforms = this.createPlatforms();
        this.player = this.createPlayer();
        const stars = this.createStars();

        this.scoreLabel = this.createScoreLabel(16, 16, 0);
        // создаем экземпляр производителя бомб
        this.bombSpawner = new BombSpawner(this, BOMB_KEY);

        this.physics.add.collider(this.player, platforms);
        this.physics.add.collider(stars, platforms);

        this.physics.add.overlap(this.player, stars, this.collectStar, null, this);

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

     // ...
}

Сначала мы импортируем класс BombSpawner в строке 5. Затем мы создаем константу BOMB_KEY в строке 11.

Хотя это и не обязательно, мы рекомендуем добавить свойство bombSpawner в конструктор.

В методе preload() строчный литерал 'bomb' заменяем на константу BOMB_KEY в строке 32.

Затем в строке 49 мы создаем экземпляр BombSpawnerи сохраняем его в свойстве this.bombSpawner.

Вы не увидите ничего нового по адресу http://localhost:8000, потому что мы еще не все настроили ... давайте продолжим для лучшего результата!

Мы собираемся создавать бомбу каждый раз, когда собираем звезду, и бомбы сталкиваются с платформами.

import Phaser from 'phaser'

//...

export default class GameScene extends Phaser.Scene
{
    constructor()
    {
        super('game-scene');

        this.player = undefined;
        this.cursors = undefined;
        this.scoreLabel = undefined;
        //Создаем свойство для хранения звезд
        this.stars = undefined;
        this.bombSpawner = undefined;
    }

    //...

    create()
    {
        this.add.image(400, 300, 'sky');

        const platforms = this.createPlatforms();
        this.player = this.createPlayer();
        // Заменяем константу stars на свойство this.stars
        this.stars = this.createStars();

        this.scoreLabel = this.createScoreLabel(16, 16, 0);

        this.bombSpawner = new BombSpawner(this, BOMB_KEY);
        // Создаем константу для хранения бомб
        const bombsGroup = this.bombSpawner.group;

        this.physics.add.collider(this.player, platforms);
        // Заменяем константу stars на свойство this.stars
        this.physics.add.collider(this.stars, platforms);
        // Проверяем коллизии между бомбами и платформами
        this.physics.add.collider(bombsGroup, platforms);
        // Заменяем константу stars на свойство this.stars
        this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);

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

    collectStar(player, star)
    {
        star.disableBody(true, true);

        this.scoreLabel.add(10);
        // пересоздаем звезды, когда все соберем
        if (this.stars.countActive(true) === 0)
        {
            //  Новый пакет звезд в коллекцию
            this.stars.children.iterate((child) => {
                child.enableBody(true, child.x, 0, true, true)
            })
        }

        this.bombSpawner.spawn(player.x);
    }

    //...
}

В конструкторе добавляется новое свойство stars. Нам нужна эта ссылка для сброса звезд после того, как все они будут собраны в collectStar(player, start), начиная со строки 53.

Затем мы присваиваем this.stars созданную группу звезд в строке 28 и используем ее в this.physics.add.collider() в строке 38 и this.physics.add.overlap() строке 42.

Помните, что у класса BombSpawner было свойство group? Мы используем его для создания коллайдера для бомб и платформ в строке 40.

Наконец, в строке 61 мы создаем бомбу каждый раз, когда собираем звезду.

Попробуйте на http://localhost:8000, чтобы увидеть, как сбрасываются бомбы!

Чтобы замкнуть цикл в нашей игре, мы будем обрабатывать столкновение игрока с бомбой.

import Phaser from 'phaser'

//...

export default class GameScene extends Phaser.Scene
{
    constructor()
    {
        super('game-scene');

        this.player = undefined;
        this.cursors = undefined;
        this.scoreLabel = undefined;
        this.stars = undefined;
        this.bombSpawner = undefined;
        //Флаг окончания игры
        this.gameOver = false;
    }

    preload()
    {
        this.load.image('sky', 'assets/sky.png');
        this.load.image(GROUND_KEY, 'assets/platform.png');
        this.load.image(STAR_KEY, 'assets/star.png');
        this.load.image(BOMB_KEY, 'assets/bomb.png');
        this.load.spritesheet(DUDE_KEY, 
            'assets/dude.png',
            { frameWidth: 32, frameHeight: 48 }
        );
    }

    create()
    {
        this.add.image(400, 300, 'sky');

        const platforms = this.createPlatforms();
        this.player = this.createPlayer();
        this.stars = this.createStars();

        this.scoreLabel = this.createScoreLabel(16, 16, 0);

        this.bombSpawner = new BombSpawner(this, BOMB_KEY);
        const bombsGroup = this.bombSpawner.group;

        this.physics.add.collider(this.player, platforms);
        this.physics.add.collider(this.stars, platforms);
        this.physics.add.collider(bombsGroup, platforms);
        //бомба попадает в игрока
        this.physics.add.collider(this.player, bombsGroup, this.hitBomb, null, this);

        this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);

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

   //...

    update()
    {
        //если игра окончена, то не обновляем
        if (this.gameOver)
        {
            return;
        }
        //...
    }

    //...
    //Обработчик попадания бомбы в игрока
    hitBomb(player, bomb)
    {
        this.physics.pause();

        player.setTint(0xff0000);

        player.anims.play('turn');

        this.gameOver = true;
    }
}

Игра заканчивается, когда в игрока попадает бомба. Мы управляем этим свойством gameOver. Этот флаг создается в конструкторе и используется для раннего выхода из метода update(). Коллайдер для игрока и бомб создается в строке 49, который вызывает функцию обратного вызова this.hitBomb, когда происходит столкновение.

Метод hitBomb(player, bomb) просто отключает физику, перекрашивает игрока в красный, а затем устанавливает свойство this.gameOver в значение true.

И это все! Попробуйте по адресу http://localhost:8000 пройти завершенную игру!

Оригинал.