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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    graphics;
    tempParentMatrix;
    tempMatrix;
    container2;
    container1;
    text2;
    text1;
    image;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('lemming', 'assets/sprites/lemming.png');
        this.load.image('topleft', 'assets/sprites/topleft.png');
    }

    create ()
    {
        this.image = this.add.image(0, 0, 'lemming').setName('lemming');

        this.container1 = this.add.container(100, 100).setName('root');
        this.container2 = this.add.container(200, 200).setName('sub');

        this.container1.add(this.container2);

        this.container2.add(this.image);

        //  Visual Container x/y markers
        // this.add.image(container1.x, container1.y, 'topleft').setOrigin(0);
        // this.add.image(container2.x, container2.y, 'topleft').setOrigin(0);

        this.tweens.add({
            targets: this.container1,
            x: 400,
            duration: 6000,
            yoyo: true,
            repeat: -1
        });

        this.tweens.add({
            targets: this.container2,
            x: 400,
            duration: 6000,
            yoyo: true,
            repeat: -1
        });

        this.tweens.add({
            targets: this.image,
            scaleX: 2,
            scaleY: 2,
            duration: 6000,
            yoyo: true,
            repeat: -1
        });

        this.graphics = this.add.graphics();

        this.text1 = this.add.text(10, 10, '', { font: '16px Courier', fill: '#00ff00' });
        this.text2 = this.add.text(500, 10, '', { font: '16px Courier', fill: '#00ff00' });

        this.tempMatrix = new Phaser.GameObjects.Components.TransformMatrix();
        this.tempParentMatrix = new Phaser.GameObjects.Components.TransformMatrix();
    }

    update ()
    {
        this.image.getWorldTransformMatrix(this.tempMatrix, this.tempParentMatrix);

        const d = this.tempMatrix.decomposeMatrix();

        this.text1.setText([
            'local',
            `x: ${this.image.x}`,
            `y: ${this.image.y}`,
            `sx: ${this.image.scaleX}`,
            `sy: ${this.image.scaleY}`,
            `r: ${this.image.angle}`
        ]);

        this.text2.setText([
            'world',
            `x: ${d.translateX}`,
            `y: ${d.translateY}`,
            `sx: ${d.scaleX}`,
            `sy: ${d.scaleY}`,
            `r: ${Phaser.Math.RadToDeg(d.rotation)}`
        ]);

        const bounds = this.image.getBounds();

        this.graphics.clear();

        this.graphics.lineStyle(1, 0x00ff00, 1);

        this.graphics.strokeRect(d.translateX, d.translateY, bounds.width, bounds.height);
    }
}

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

const game = new Phaser.Game(config);

Проблема локальных и мировых координат

В примере создаётся иерархия: спрайт lemming добавлен в container2, который, в свою очередь, добавлен в container1. Каждый объект имеет свои локальные свойства `x,y,scaleX/Y,angle`.

Если мы просто прочитаем this.image.x, то получим локальную координату `0, потому что спрайт помещён в начало координат своего контейнера. Но на экране его реальная позиция — это результат сложения всех смещений и преобразований по цепочке:imagecontainer2container1` → сцена.

this.image = this.add.image(0, 0, 'lemming').setName('lemming');
this.container1 = this.add.container(100, 100).setName('root');
this.container2 = this.add.container(200, 200).setName('sub');

this.container1.add(this.container2);
this.container2.add(this.image);

Решение: getWorldTransformMatrix()

Метод getWorldTransformMatrix() вычисляет итоговую матрицу трансформации объекта в мировом пространстве. Он принимает два аргумента: 1. Матрица для результата (итоговая мировая матрица объекта). 2. Матрица для родительской трансформации (используется внутренне для расчётов).

В примере создаются два экземпляра TransformMatrix для переиспользования в каждом кадре, что эффективно для производительности.

// Создание матриц один раз в create()
this.tempMatrix = new Phaser.GameObjects.Components.TransformMatrix();
this.tempParentMatrix = new Phaser.GameObjects.Components.TransformMatrix();

// Получение мировой матрицы спрайта в каждом кадре в update()
this.image.getWorldTransformMatrix(this.tempMatrix, this.tempParentMatrix);

Декомпозиция матрицы в читаемые значения

Матрица TransformMatrix содержит все преобразования в компактной математической форме. Чтобы получить из неё привычные значения координат, масштаба и вращения, используется метод decomposeMatrix().

Он возвращает объект со свойствами translateX, translateY, scaleX, scaleY и rotation. Важно: угол вращения (rotation) возвращается в радианах.

const d = this.tempMatrix.decomposeMatrix();

// Мировые координаты и масштаб
const worldX = d.translateX;
const worldY = d.translateY;
const worldScaleX = d.scaleX;
const worldScaleY = d.sc
aeY;

// Преобразование угла из радиан в градусы для отображения
const worldAngleInDegrees = Phaser.Math.RadToDeg(d.rotation);

Эти значения выводятся на экран и сравниваются с локальными свойствами объекта, что наглядно показывает разницу.

Визуализация и практическое применение

В методе update() происходит не только вывод текста, но и визуализация. С помощью graphics.strokeRect() рисуется зелёный прямоугольник вокруг спрайта, используя его мировые координаты и размеры (bounds).

const bounds = this.image.getBounds();
this.graphics.clear();
this.graphics.lineStyle(1, 0x00ff00, 1);
this.graphics.strokeRect(d.translateX, d.translateY, bounds.width, bounds.height);

Этот подход незаменим, когда вам нужно: * Рассчитать столкновение объекта внутри контейнера с другим объектом на сцене. * Выставить мировую позицию для частицы или эффекта, которые должны появиться именно на спрайте. * Привязать интерфейсный элемент (например, health bar) к объекту, который движется внутри сложной иерархии контейнеров.

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

Использование getWorldTransformMatrix() — это правильный способ работы с глобальным позиционированием в иерархиях Phaser. Он обеспечивает точность, которую нельзя получить простым суммированием локальных координат, особенно при наличии вращения и масштабирования. **Идеи для экспериментов:** 1. Добавьте вращение (angle) одному из контейнеров и убедитесь, что мировой угол спрайта корректно рассчитывается. 2. Используйте мировые координаты спрайта для создания стреляющих снарядов из его центра, даже если сам спрайт находится внутри анимированного контейнера-корабля. 3. Попробуйте применить этот метод не к Image, а к Container, чтобы получить мировые параметры целой группы объектов.