О чем этот пример
При разработке игр часто возникает необходимость группировать игровые объекты и управлять ими как единым целым. Phaser предоставляет для этого мощный инструмент — `Container`. Однако при вложении контейнеров друг в друга и попытках получить их реальные границы (`getBounds`) можно столкнуться с неочевидным поведением. Эта статья на практическом примере разбирает, как правильно работать с вложенными контейнерами, трансформациями и методом `getBounds`, чтобы ваши UI-элементы, сложные спрайты или группы врагов всегда точно отображались и взаимодействовали с миром.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
graphics;
bounds;
container;
image;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('disk', 'assets/sprites/copy-that-floppy.png');
}
create ()
{
this.image = this.add.image(0, 0, 'disk');
this.container = this.add.container(0, 0, [ this.image ]);
const container2 = this.add.container(400, 300, [ this.container ]);
this.graphics = this.add.graphics();
this.bounds = this.image.getBounds();
this.tweens.add({
targets: container2,
duration: 2000,
scaleX: 2,
scaleY: 2,
ease: 'Sine.easeInOut',
repeat: -1,
yoyo: true
});
}
update ()
{
this.container.rotation += 0.015;
this.bounds = this.image.getBounds();
this.graphics.clear();
this.graphics.lineStyle(1, 0xffff00);
this.graphics.strokeRectShape(this.bounds);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d8d2d',
scene: Example
};
const game = new Phaser.Game(config);
Создание вложенной иерархии объектов
В примере создается цепочка из зависимых объектов: изображение помещается в контейнер, а тот, в свою очередь, — в другой контейнер. Это позволяет применять трансформации (поворот, масштаб) к целым группам.
Ключевой момент: объекты добавляются в контейнер с локальными координатами (0, 0) относительно родителя. Это значит, что позиция image задана не относительно сцены, а относительно своего непосредственного родителя — container.
this.image = this.add.image(0, 0, 'disk');
this.container = this.add.container(0, 0, [ this.image ]);
const container2 = this.add.container(400, 300, [ this.container ]);
Динамические трансформации и анимация
В коде применяются две независимые трансформации. Первая — плавное изменение масштаба внешнего контейнера с помощью твина. Вторая — постоянный поворот внутреннего контейнера в методе update.
Обратите внимание: твин применяется к container2, который изначально позиционирован в точке (400, 300). Все его дочерние объекты (включая container и image) будут масштабироваться относительно этой точки.
this.tweens.add({
targets: container2,
duration: 2000,
scaleX: 2,
scaleY: 2,
ease: 'Sine.easeInOut',
repeat: -1,
yoyo: true
});
// В методе update:
this.container.rotation += 0.015;
Получение и отрисовка глобальных границ (Bounds)
Самая важная часть примера — метод getBounds(). Он вызывается у изображения (this.image), но возвращает его **глобальные** границы в координатах сцены, учитывая все трансформации всех родительских контейнеров (поворот container и масштаб container2).
Эти границы затем визуализируются с помощью Graphics в виде желтого прямоугольника. Очистка холста (clear()) в каждом кадре необходима, так как границы постоянно меняются из-за анимации.
this.bounds = this.image.getBounds();
this.graphics.clear();
this.graphics.lineStyle(1, 0xffff00);
this.graphics.strokeRectShape(this.bounds);
Именно так getBounds() позволяет точно определить, где находится объект на экране после всех преобразований, что критически важно для проверки столкновений, кликов или размещения интерфейса.
Что попробовать дальше
Вложенные контейнеры в Phaser — это мощный способ структурировать сложные объекты. Метод getBounds() автоматически рассчитывает итоговую позицию и размер, учитывая всю цепочку трансформаций. Для экспериментов попробуйте: вызвать getBounds() у container или container2 и сравнить результаты; добавить больше уровней вложенности; использовать границы для проверки столкновений с курсором мыши с помощью this.bounds.contains(x, y).
