О чем этот пример
При работе с контейнерами (`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, потому что спрайт помещён в начало координат своего контейнера. Но на экране его реальная позиция — это результат сложения всех смещений и преобразований по цепочке:image→container2→container1` → сцена.
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, чтобы получить мировые параметры целой группы объектов.
