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

При разработке игр часто возникает необходимость группировать игровые объекты и управлять ими как единым целым. 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).