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

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

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

Живой запуск

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

Исходный код


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

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

        //  Because the rect is drawn at 0x0 it will start from the top-left of Container1
        //  If you want bg1 to be _centered_ on Container1 then its xy should be
        //  negative half width by negative half height (i.e. -150, -150 in this case)
        const bg1 = this.add.graphics().fillStyle(0xff0000).fillRect(0, 0, 300, 300);

        container1.add(bg1);

        //  This Container is positioned _relative_ to Container1 (at 400x300)
        //  Which is why we use 0x0 here - if you put a different value, see how they adjust
        const container2 = this.add.container(0, 0);

        //  Because the rect is drawn at 0x0 it will start from the top-left of Container2
        const bg2 = this.add.graphics().fillStyle(0x00ff00).fillRect(0, 0, 200, 200);

        container2.add(bg2);

        container1.add(container2);

        const container3 = this.add.container(0, 0);

        //  Because the rect is drawn at 0x0 it will start from the top-left of Container3
        const bg3 = this.add.graphics().fillStyle(0x0000ff).fillRect(0, 0, 100, 100);

        container3.add(bg3);

        container1.add(container3);
    }
}

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

let game = new Phaser.Game(config);

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

Вся иерархия начинается с корневого контейнера. Он позиционируется относительно сцены с помощью координат `xиy, переданных в методеthis.add.container()`.

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

Здесь container1 будет размещён в центре сцены размером 800x600. Важно понимать, что точка (0, 0) этого контейнера теперь находится на мировых координатах (400, 300).

Добавление графики в контейнер

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

const bg1 = this.add.graphics().fillStyle(0xff0000).fillRect(0, 0, 300, 300);
container1.add(bg1);

Ключевой момент: метод fillRect(0, 0, 300, 300) рисует квадрат, начиная от точки (0, 0) самой графики bg1. Эта точка (0, 0) объекта bg1 привязывается к точке (0, 0) его родителя — контейнера container1. Поэтому левый верхний угол красного квадрата окажется в мировых координатах (400, 300). Если нужно, чтобы квадрат был отцентрирован относительно контейнера, его следует рисовать со смещением: fillRect(-150, -150, 300, 300).

Вложенные контейнеры и относительные координаты

Контейнеры могут содержать другие контейнеры, образуя древо объектов. Позиция дочернего контейнера задаётся относительно родительского.

const container2 = this.add.container(0, 0);
const bg2 = this.add.graphics().fillStyle(0x00ff00).fillRect(0, 0, 200, 200);
container2.add(bg2);
container1.add(container2);

Создаётся container2 с координатами (0, 0). Это значит, что его начало будет в точке (0, 0) родителя, то есть в мировых координатах (400, 300). Зелёный квадрат, добавленный в container2, нарисуется от этой точки. Таким образом, изменение позиции container1 автоматически сдвинет всю его внутреннюю иерархию, включая container2 и bg2.

Построение многоуровневой иерархии

Добавим третий, синий контейнер, также как дочерний к корневому container1. Это демонстрирует, как несколько независимых ветвей объектов могут быть сгруппированы под одним родителем.

const container3 = this.add.container(0, 0);
const bg3 = this.add.graphics().fillStyle(0x0000ff).fillRect(0, 0, 100, 100);
container3.add(bg3);
container1.add(container3);

И container2, и container3 являются дочерними для container1. Их координаты (0, 0) означают, что они будут нарисованы в одной и той же точке — начале container1. На экране мы увидим, что красный, зелёный и синий квадраты наложатся друг на друга своими верхними левыми углами в точке (400, 300).

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

Весь пример выполняется внутри одной сцены. Базовая конфигурация игры определяет её размеры, цвет фона и класс сцены.

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

Эта конфигурация создаёт игровое поле 800x600 пикселей с чёрным фоном. Экземпляр сцены Example будет автоматически создан и запущен движком Phaser.

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

Вложенные контейнеры — это основа для организации сложных игровых миров и интерфейсов в Phaser. Главный принцип: координаты дочернего объекта всегда относительны к локальному началу координат его непосредственного родителя. Для экспериментов попробуйте

  1. Изменить координаты container2 на (50, 50) и увидите, как зелёный квадрат сместится относительно красного
  2. Сделать container3 дочерним для container2, чтобы построить трёхуровневую иерархию
  3. Применить к container1 метод setRotation и наблюдать, как вся группа объектов повернётся как единое целое вокруг точки (400, 300)