О чем этот пример

Управлять десятками или сотнями однотипных объектов в игре — частая задача. Вручную обновлять координаты каждого спрайта в цикле неэффективно и громоздко. В этом примере мы рассмотрим, как использовать утилиты `Phaser.Actions.IncX` и `Phaser.Actions.WrapInRectangle` для лаконичного и производительного управления группой объектов. Вы научитесь создавать простые, но визуально привлекательные эффекты вроде параллакса или бесконечно прокручивающегося фона.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();

        this.donuts = [];
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/skies/grid.png');
        this.load.image('donut', 'assets/sprites/donut.png');
    }

    create ()
    {
        this.add.image(400, 600, 'bg').setOrigin(0.5, 1);

        this.cameras.main.setBounds(0, 0, 800, 600);

        for (let i = 0; i < 16; i++)
        {
            const x = Phaser.Math.Between(0, 800);
            const y = Phaser.Math.Between(200, 600);

            this.donuts.push(this.add.image(x, y, 'donut'));
        }
    }

    update ()
    {
        Phaser.Actions.IncX(this.donuts, -2, -0.5);

        Phaser.Actions.WrapInRectangle(this.donuts, this.cameras.main.getBounds(), 128);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и создание объектов

В методе preload загружаются две текстуры: фон (bg) и спрайт пончика (donut). Обратите внимание, что фон устанавливается с помощью setOrigin(0.5, 1). Это означает, что точка его привязки (origin) находится посередине по горизонтали (0.5) и внизу по вертикали (1). Таким образом, изображение позиционируется относительно координат (400, 600) — то есть по центру нижней границы окна игры.

В методе create мы создаем массив this.donuts для хранения спрайтов и устанавливаем границы камеры (setBounds), которые будут использоваться позже. Затем в цикле создаются 16 пончиков в случайных позициях в пределах заданной области экрана, и каждый добавляется в массив.

create ()
{
    this.add.image(400, 600, 'bg').setOrigin(0.5, 1);
    this.cameras.main.setBounds(0, 0, 800, 600);

    for (let i = 0; i < 16; i++)
    {
        const x = Phaser.Math.Between(0, 800);
        const y = Phaser.Math.Between(200, 600);
        this.donuts.push(this.add.image(x, y, 'donut'));
    }
}

Массовое перемещение объектов с IncX

Каждый кадр (в методе update) вызывается Phaser.Actions.IncX. Этот метод принимает массив объектов и увеличивает (или уменьшает, если значение отрицательное) свойство `x` (горизонтальную координату) каждого из них.

Первым аргументом передается наш массив спрайтов this.donuts. Второй аргумент (-2) — это базовое значение, на которое сдвигается каждый объект. Третий аргумент (-0.5) — это случайная вариация. Итоговое смещение для каждого спрайта вычисляется по формуле: базовое значение + случайное число от 0 до вариации. Таким образом, пончики движутся влево со скоростью от -2 до -2.5 пикселя за кадр, создавая эффект небольшого разброса в скорости.

Phaser.Actions.IncX(this.donuts, -2, -0.5);

Бесконечная циклическая телепортация с WrapInRectangle

Если объекты просто уходят за левый край экрана, они исчезнут навсегда. Чтобы создать эффект бесконечного пространства или циклического движения, используется Phaser.Actions.WrapInRectangle.

Этот метод проверяет каждый объект в переданном массиве. Если объект полностью покидает заданный прямоугольник (в нашем случае — границы камеры this.cameras.main.getBounds()), он «телепортируется» на противоположную сторону. Второй аргумент (128) — это отступ (padding). Он расширяет зону проверки. Объект будет телепортирован только тогда, когда он удалится от границы прямоугольника больше чем на 128 пикселей. Это предотвращает резкие, заметные глазу телепортации прямо у края экрана.

Phaser.Actions.WrapInRectangle(this.donuts, this.cameras.main.getBounds(), 128);

В результате пончики, улетая влево, плавно возвращаются справа, создавая непрерывный поток объектов.

Конфигурация игры и запуск

Стандартная конфигурация игры Phaser 3. В поле scene передается класс нашей сцены Example. Это единственная сцена в данном примере. После создания экземпляра Phaser.Game с этой конфигурацией автоматически вызываются методы preload, create, а затем на каждом кадре — update.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Что попробовать дальше

Комбинация Phaser.Actions.IncX и WrapInRectangle — это мощный и производительный инструмент для создания динамических фонов, частиц или групп врагов с патрульным поведением. Для экспериментов попробуйте: заменить IncX на IncY для вертикального движения; использовать Phaser.Actions.Rotate для добавления вращения пончикам; или привязать прямоугольник телепортации не к границам камеры, а к произвольной области на карте.