О чем этот пример
При работе с контейнерами (`Container`) в Phaser разработчики часто используют метод `getBounds()` для получения габаритов объекта. Однако вложенность контейнеров может привести к неожиданным результатам. Эта статья разбирает пример, демонстрирующий тонкость вычисления границ у контейнеров после изменения их позиции, и объясняет, как избежать ошибок при позиционировании сложных иерархий объектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: {
create: create
}
};
var game = new Phaser.Game(config);
function create ()
{
// Create a test container
var parentContainer = this.add.container(0,0);
// Create a nested container
var nestedContainer = this.add.container(0,0);
// Create a child object
var testObject = this.add.rectangle(0, 0, 20, 20).setOrigin(0);
// Add object to nested container and this to the parent
nestedContainer.add(testObject);
parentContainer.add(nestedContainer);
// At this point the containter is at 0x0 with a height and width of 20.
console.log(parentContainer.getBounds());
parentContainer.setX(100);
parentContainer.setY(100);
// Ths container should have moved, but not changed in size.
console.log(parentContainer.getBounds());
}
Разбираем пример: иерархия контейнеров
В примере создается простая сцена с двумя контейнерами и одним графическим объектом. Ключевая цель — отследить, как меняются возвращаемые границы (bounds) родительского контейнера после его перемещения.
Сначала инициализируется игра с конфигурацией, указывающей функцию create как точку создания сцены.
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: {
create: create
}
};
var game = new Phaser.Game(config);
Создание иерархии объектов
В функции create строится древовидная структура: родительский контейнер, внутри него — вложенный контейнер, а в том — прямоугольник (Rectangle). Важно отметить, что прямоугольник создается с координатами (0,0) относительно своего родителя и сбросом точки трансформации (setOrigin(0)).
function create ()
{
// Create a test container
var parentContainer = this.add.container(0,0);
// Create a nested container
var nestedContainer = this.add.container(0,0);
// Create a child object
var testObject = this.add.rectangle(0, 0, 20, 20).setOrigin(0);
// Add object to nested container and this to the parent
nestedContainer.add(testObject);
parentContainer.add(nestedContainer);
Поведение метода `getBounds()`
Метод container.getBounds() вычисляет габаритный прямоугольник (bounding box), который включает всех видимых потомков контейнера. При первом вызове, когда вся иерархия находится в точке (0,0), метод корректно возвращает прямоугольник размером 20x20.
// At this point the containter is at 0x0 with a height and width of 20.
console.log(parentContainer.getBounds());
Затем родительский контейнер перемещается в точку (100,100) с помощью методов setX и setY. Логично ожидать, что границы теперь будут иметь те же размеры (20x20), но смещенные координаты. Однако в этом примере демонстрируется проблема (баг #4644), из-за которой getBounds() может не учесть трансформации родителя при определенных условиях, если вычисления были кэшированы или произведены до перемещения. Второй вызов console.log призван это проверить.
parentContainer.setX(100);
parentContainer.setY(100);
// Ths container should have moved, but not changed in size.
console.log(parentContainer.getBounds());
}
На практике, если getBounds() возвращает старые значения, это может сломать логику, зависящую от актуального положения объектов (например, проверки выхода за пределы экрана, расчетов столкновений).
Практические рекомендации
Чтобы избежать проблем с актуальностью границ контейнера:
1. **Принудительный пересчет.** Если вы изменили позицию, масштаб или поворот контейнера и сразу нужны актуальные границы, можно попробовать сбросить кэш вычислений, вызвав container.updateBounds() перед getBounds(). Однако в данном конкретном примере бага это может не сработать, так как проблема глубже.
2. **Осознание вложенности.** Помните, что getBounds() для контейнера вычисляется относительно мировых координат (world), а не локальных. Изменение позиции любого родителя в цепочке влияет на мировые координаты всех потомков.
3. **Альтернативный подход.** Для проверки положения конкретного дисплейного объекта (например, прямоугольника testObject) иногда надежнее использовать его собственные свойства `x,yиdisplayWidth`/displayHeight, учитывая цепочку родителей.
// Получение мировых координат дочернего объекта
let worldX = testObject.x; // Локальные координаты относительно nestedContainer
let worldY = testObject.y;
// Для мировых координат может потребоваться учет матриц трансформации родителей
Что попробовать дальше
Работа с Container.getBounds() требует понимания, что результат может кэшироваться и не всегда мгновенно отражает изменения в иерархии объектов, особенно при наличии багов в конкретных версиях движка. Всегда проверяйте актуальность возвращаемых данных в отладчике. Для экспериментов попробуйте изменить пример: добавьте поворот (setRotation) контейнеру и снова вызовите getBounds(), или создайте более глубокую вложенность из 3-4 контейнеров, чтобы увидеть, как накапливается погрешность вычислений.
