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

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

Версия 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('buttonBG', 'assets/sprites/button-bg.png');
    }

    create ()
    {
        //  Normal image
        this.add.image(200, 150, 'buttonBG').setAngle(20);

        //  Single level container
        const containerImage = this.add.image(0, 0, 'buttonBG').setAngle(20);
        const container = this.add.container(600, 150, containerImage).setAngle(20);

        //  2 level container
        const containerImage2 = this.add.image(0, 0, 'buttonBG').setAngle(20);
        const container2a = this.add.container(0, 0, containerImage2).setAngle(20);
        const container2b = this.add.container(200, 450, container2a).setAngle(20);

        //  3 level container
        const containerImage3 = this.add.image(0, 0, 'buttonBG').setAngle(20);
        const container3a = this.add.container(0, 0, containerImage3).setAngle(20);
        const container3b = this.add.container(0, 0, container3a).setAngle(20);
        const container3c = this.add.container(600, 450, container3b).setAngle(20);
    }
}

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

const game = new Phaser.Game(config);

Что такое Container и зачем его вкладывать?

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

Вложение контейнеров – это создание иерархии, где контейнер сам становится дочерним элементом другого контейнера. Это позволяет создавать модульные структуры. Например, контейнер «рука» может быть вложен в контейнер «персонаж», а контейнер «меч» – в контейнер «рука». Поворот персонажа повернет всю цепочку.

Пример в статье демонстрирует базовый принцип: как итоговые координаты и угол изображения вычисляются с учетом трансформаций на каждом уровне вложенности.

Анализ кода: от простого к сложному

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

**Одиночное изображение:** Базовый случай, точка отсчета. Изображение добавлено напрямую на сцену.

this.add.image(200, 150, 'buttonBG').setAngle(20);

**Одноуровневый контейнер:** Здесь изображение containerImage добавлено не на сцену, а в контейнер. Позиция (600, 150) и угол (20) задаются контейнеру. Изображение внутри имеет свои локальные трансформации (0, 0, угол 20). Итоговая позиция изображения на холсте: позиция контейнера + локальная позиция изображения. Итоговый угол: сумма углов контейнера и изображения.

const containerImage = this.add.image(0, 0, 'buttonBG').setAngle(20);
const container = this.add.container(600, 150, containerImage).setAngle(20);

**Двухуровневый контейнер:** Появляется цепочка: containerImage2 -> container2a -> container2b. Каждое звено вносит свой вклад в финальную трансформацию изображения. Позиция container2b (200, 450) применяется к уже сгруппированному container2a с его изображением.

const containerImage2 = this.add.image(0, 0, 'buttonBG').setAngle(20);
const container2a = this.add.container(0, 0, containerImage2).setAngle(20);
const container2b = this.add.container(200, 450, container2a).setAngle(20);

**Трехуровневый контейнер:** Принцип тот же, но иерархия глубже: Image -> Container3a -> Container3b -> Container3c. Это наглядно показывает, как сложные объекты собираются из простых модулей.

Как вычисляется финальная позиция и поворот?

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

Для примера с двухуровневым контейнером: 1. Изображение имеет локальный угол 20°. 2. container2a добавляет к нему еще 20° (итого 40° у изображения относительно container2a) и не смещает его (позиция 0,0). 3. container2b применяет к результату (к container2a с уже повернутым изображением) смещение на (200, 450) и еще +20° к углу.

Таким образом, изображение на экране будет повернуто на **60 градусов** (20+20+20) и смещено на **(200, 450)** от своей изначальной локальной позиции (0,0). Все преобразования применяются последовательно, от самого вложенного объекта к самому внешнему.

Важно: методы типа setAngle() задают локальную трансформацию объекта относительно его родителя в иерархии.

Практическое применение и типичные сценарии

Понимание вложенных контейнеров открывает путь к созданию сложной игровой логики:

* **Сборный персонаж:** Контейнер «тело» содержит контейнеры «голова», «руки», «ноги». Конечности, в свою очередь, могут содержать контейнеры «оружие» или «щит». Анимация ходьбы вращает контейнеры ног, а атака – контейнер руки с оружием. * **Интерфейс:** Контейнер «панель навыков» содержит несколько контейнеров «слот навыка», каждый из которых включает изображение иконки, текстовую метку уровня и контейнер-индикатор перезарядки. Перемещение или скрытие всей панели — это одна операция с корневым контейнером. * **Транспорт:** Контейнер «машина» содержит контейнер «корпус» и четыре контейнера «колесо». Вращаются только колеса, а вся машина движется как единое целое.

// Эскиз структуры персонажа
const torso = this.add.container(x, y);
const head = this.add.container(0, -30, headSprite);
const armL = this.add.container(-20, 10, armSprite);
const weapon = this.add.container(15, 0, weaponSprite); // Оружие в руке

armL.add(weapon); // Оружие -> Рука
torso.add([head, armL]); // Голова и Рука -> Торс
this.add.existing(torso); // Торс -> Сцена

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

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

  1. Анимировать свойства (angle, `x,y) контейнеров на разных уровнях вложенности с помощьюtweens` и наблюдайте за результатом
  2. Создайте прототип персонажа-робота со сменяемыми частями тела, используя контейнеры
  3. Реализуйте сложный UI-виджет (например, инвентарь), где каждый слот – это независимый контейнер, вложенный в общую прокручиваемую панель