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

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

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

Живой запуск

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

Исходный код


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

        this.wrapRect;
        this.aliens = [];
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('monitor', 'assets/pics/monitor.png');
        this.load.image('alien', 'assets/sprites/space-baddie.png');
    }

    create ()
    {
        //  This is our 'wrapping rectangle'
        //  When a sprite leaves this, it'll be wrapped around
        this.wrapRect = new Phaser.Geom.Rectangle(214, 132, 367, 239);

        this.add.rectangle(this.wrapRect.x, this.wrapRect.y, this.wrapRect.width, this.wrapRect.height, 0x0094bf).setOrigin(0, 0);

        for (let i = 0; i < 64; i++)
        {
            const x = Phaser.Math.Between(this.wrapRect.left, this.wrapRect.right);
            const y = Phaser.Math.Between(this.wrapRect.top, this.wrapRect.bottom);

            this.aliens.push(this.add.image(x, y, 'alien'));
        }

        this.add.image(400, 300, 'monitor');
    }

    update ()
    {
        //  Move all the sprites
        Phaser.Actions.IncXY(this.aliens, 1.5, 2.5, 0.04);

        //  Wrap the sprites within our wrapping rectangle, with an 8px buffer
        Phaser.Actions.WrapInRectangle(this.aliens, this.wrapRect, 8);
    }
}

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

const game = new Phaser.Game(config);

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

Вся логика примера построена вокруг одного прямоугольника (Phaser.Geom.Rectangle), который задаёт границы для «заворачивания» спрайтов. Сначала мы создаём этот прямоугольник в методе create() сцены.

this.wrapRect = new Phaser.Geom.Rectangle(214, 132, 367, 239);

Здесь заданы координаты левого верхнего угла (214, 132), ширина (367) и высота (239). Для наглядности на сцену добавляется графический прямоугольник с таким же размером и полупрозрачной заливкой.

this.add.rectangle(this.wrapRect.x, this.wrapRect.y, this.wrapRect.width, this.wrapRect.height, 0x0094bf).setOrigin(0, 0);

Метод .setOrigin(0, 0) устанавливает точку привязки (origin) в левый верхний угол, чтобы визуальный прямоугольник точно совпал с логической зоной wrapRect.

Создание и расстановка спрайтов

В примере создаётся 64 спрайта пришельцев ('alien'). Их начальные позиции генерируются случайным образом, но строго внутри границ нашего прямоугольника wrapRect. Для этого используется Phaser.Math.Between, которая возвращает случайное целое число в заданном диапазоне.

const x = Phaser.Math.Between(this.wrapRect.left, this.wrapRect.right);
const y = Phaser.Math.Between(this.wrapRect.top, this.wrapRect.bottom);
this.aliens.push(this.add.image(x, y, 'alien'));

Все созданные спрайты сохраняются в массив this.aliens. Это ключевой момент, так как Phaser.Actions работают именно с массивами игровых объектов. После этого на задний план добавляется статичное изображение монитора для создания контекста.

Движение объектов с помощью Phaser.Actions.IncXY

В методе update(), который вызывается каждый кадр, необходимо двигать все спрайты. Вместо перебора массива в цикле, используется удобный хелпер Phaser.Actions.IncXY. Он применяет одно и то же смещение по осям X и Y ко всем объектам в переданном массиве.

Phaser.Actions.IncXY(this.aliens, 1.5, 2.5, 0.04);

Параметры функции: - this.aliens — массив спрайтов. - 1.5 — смещение по оси X за кадр. - 2.5 — смещение по оси Y за кадр. - 0.04 — *шаг* (step). Это необязательный параметр, который добавляет случайное отклонение к смещению, делая движение более естественным. Значение 0.04 означает ±4% от базового смещения.

Магия зацикливания: WrapInRectangle

Сердце примера — метод Phaser.Actions.WrapInRectangle. Он проверяет положение каждого спрайта в массиве относительно заданного прямоугольника и, если спрайт его покинул, «перебрасывает» его на противоположную сторону.

Phaser.Actions.WrapInRectangle(this.aliens, this.wrapRect, 8);
Параметры функции:
- `this.aliens` — массив спрайтов для обработки.
- `this.wrapRect` — прямоугольник-граница.
- `8` — *буфер* (padding). Это важный параметр. Он определяет, насколько спрайт должен выйти за границу прямоугольника, прежде чем сработает «заворачивание». Без буфера спрайт начинает мигать на самой границе, так как в одном кадре он вышел, его вернули, а в следующем он снова вышел. Буфер в 8 пикселей решает эту проблему.

Логика работы: если координата спрайта по X меньше wrapRect.left - 8, она устанавливается в wrapRect.right. Если больше wrapRect.right + 8 — в wrapRect.left. Аналогично для координаты Y.

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

Код завершается стандартной конфигурацией игры Phaser 3 и её инстанцированием. Обратите внимание на фоновый цвет (backgroundColor), который создаёт эффект космоса.

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

const game = new Phaser.Game(config);

Ключевой момент: основная сцена (scene) указана как класс Example, который мы и разбирали. Вся логика «заворачивания» инкапсулирована внутри методов этого класса.

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

Phaser.Actions.WrapInRectangle — это мощный и производительный инструмент для управления группами объектов в ограниченном пространстве. Он избавляет от необходимости писать собственные проверки границ и циклы. **Идеи для экспериментов:** 1. Измените параметры IncXY и буфер WrapInRectangle, чтобы увидеть, как это влияет на плавность и поведение системы. 2. Попробуйте применить WrapInRectangle не ко всем, а к отдельной группе спрайтов, создав второй массив. 3. Вместо прямоугольника используйте Phaser.Actions.WrapInCircle для создания круговой или орбитальной зоны.