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

Работа с большим количеством игровых объектов может превратиться в рутину. Вам нужно двигать, вращать или масштабировать каждую единицу отдельно, что усложняет код и снижает производительность. Контейнеры Phaser решают эту проблему, позволяя управлять группой объектов как единым целым. В этой статье мы разберем, как создавать контейнеры, добавлять в них спрайты и применять анимации ко всей группе сразу. Этот подход идеально подходит для создания сложных составных объектов, интерфейсов или управления множеством однотипных элементов, таких как частицы или элементы окружения.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('lemming', 'assets/sprites/lemming.png');
    }

    create ()
    {
        //  Our container
        const container = this.add.container(400, 300);

        //  Add some sprites - positions are relative to the Container x/y
        const sprite0 = this.add.sprite(0, 0, 'lemming');
        const sprite1 = this.add.sprite(-100, -100, 'lemming');
        const sprite2 = this.add.sprite(100, -100, 'lemming');
        const sprite3 = this.add.sprite(100, 100, 'lemming');
        const sprite4 = this.add.sprite(-100, 100, 'lemming');

        container.add(sprite0);
        container.add(sprite1);
        container.add(sprite2);
        container.add(sprite3);
        container.add(sprite4);

        //  You could also pass them in as an array, to save doing them one by one

        this.tweens.add({
            targets: container,
            angle: 360,
            duration: 6000,
            yoyo: true,
            repeat: -1
        });
    }
}

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

const game = new Phaser.Game(config);

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

Контейнер (Container) — это особый тип игрового объекта, который может выступать в роли родителя для других объектов. Все дочерние объекты наследуют его трансформации (положение, угол поворота, масштаб). Это похоже на работу с папками в графическом редакторе: перемещая папку, вы перемещаете все её содержимое.

В методе create() мы создаем контейнер и несколько спрайтов. Ключевой момент: координаты спрайтов задаются относительно позиции контейнера.

//  Our container
const container = this.add.container(400, 300);

//  Add some sprites - positions are relative to the Container x/y
const sprite0 = this.add.sprite(0, 0, 'lemming');
const sprite1 = this.add.sprite(-100, -100, 'lemming');
const sprite2 = this.add.sprite(100, -100, 'lemming');
const sprite3 = this.add.sprite(100, 100, 'lemming');
const sprite4 = this.add.sprite(-100, 100, 'lemming');

Спрайт sprite0 будет находиться прямо в центре контейнера (400, 300 на игровом холсте), а sprite1 — на 100 пикселей выше и левее этого центра. После создания объекты по одному добавляются в контейнер с помощью метода .add().

container.add(sprite0);
container.add(sprite1);
// ... и так далее для всех спрайтов

Эффективное добавление объектов

Phaser предоставляет более компактный способ добавления нескольких дочерних элементов за один вызов — передачу массива объектов. Это делает код чище и удобнее для чтения, особенно когда объектов много.

//  You could also pass them in as an array, to save doing them one by one
container.add([sprite0, sprite1, sprite2, sprite3, sprite4]);

Оба способа — и по одному, и массивом — функционально идентичны. Выбор зависит от стиля и удобства. Например, если спрайты создаются в цикле и складываются в массив, логичнее добавить их сразу всем массивом.

Анимация контейнера

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

В примере используется система твинов Phaser (this.tweens.add). Мы анимируем свойство angle контейнера.

this.tweens.add({
    targets: container, // Цель анимации — наш контейнер
    angle: 360,        // Конечное значение: полный оборот
    duration: 6000,    // Длительность анимации в миллисекундах
    yoyo: true,        // Анимация проиграется в обратном порядке после завершения
    repeat: -1         // Бесконечное повторение
});

В результате все пять спрайтов, прикрепленных к контейнеру, начнут плавно вращаться вокруг общей точки (400, 300), образуя единую вращающуюся композицию. Свойства yoyo и repeat делают анимацию непрерывной и плавной.

Порядок отрисовки и вложенность

Объекты внутри контейнера отрисовываются в том порядке, в котором они были добавлены. Первый добавленный спрайт окажется "ниже" (ближе к фону), а последний — "сверху". Порядок можно менять динамически с помощью методов контейнера, таких как moveTo, bringToTop или sendToBack.

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

const playerContainer = this.add.container(100, 100);
const weaponContainer = this.add.container(0, 0); // Относительно playerContainer
const barrel = this.add.sprite(20, 0, 'barrel');

weaponContainer.add(barrel);
playerContainer.add(weaponContainer);
// Теперь вращение playerContainer повлияет и на weaponContainer, и на barrel.

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

Код примера завершается стандартной для Phaser конфигурацией игры и её инициализацией. В конфиге указывается наша сцена Example.

const config = {
    type: Phaser.AUTO,       // Рендерер: WebGL или Canvas
    width: 800,
    height: 600,
    backgroundColor: '#010101', // Темный фон для контраста
    parent: 'phaser-example',   // ID HTML-элемента для встраивания
    scene: Example             // Класс главной сцены
};

const game = new Phaser.Game(config);

Это базовая конфигурация, достаточная для запуска примера. В реальных проектах сюда часто добавляют настройки физики (physics), масштабирования (scale) или несколько сцен.

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

Контейнеры — это фундаментальный инструмент для организации кода и логики в Phaser. Они позволяют мыслить группами объектов, а не одиночными единицами, что упрощает управление сложными сценами. Для экспериментов попробуйте

  1. Анимировать не только angle, но и scale или alpha контейнера
  2. Создать интерфейс с кнопками (спрайтами) внутри контейнера и двигать его целиком
  3. Реализовать систему частиц, где контейнер управляет всей группой частиц, а каждая частица также имеет свою небольшую анимацию