О чем этот пример
Создание сложных игровых сущностей, таких как персонаж с оружием и эффектами или интерфейс с группами элементов, часто требует объединения объектов в иерархические структуры. 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 – это фундаментальный механизм для создания модульных и легко управляемых сложных объектов. Они позволяют применять трансформации к группам объектов и строить из них иерархии, где изменение родителя автоматически затрагивает всех потомков. Для экспериментов попробуйте
- Анимировать свойства (
angle, `x,y) контейнеров на разных уровнях вложенности с помощьюtweens` и наблюдайте за результатом - Создайте прототип персонажа-робота со сменяемыми частями тела, используя контейнеры
- Реализуйте сложный UI-виджет (например, инвентарь), где каждый слот – это независимый контейнер, вложенный в общую прокручиваемую панель
