О чем этот пример
При разработке интерфейсов в Phaser, особенно элементов HUD, контейнеры — мощный инструмент для группировки объектов. Однако их неправильное использование может привести к неожиданным и трудноуловимым ошибкам рендеринга. В этой статье мы разберём пример, где текст, добавленный в контейнер, отображается не там, где ожидалось, и объясним, как избежать подобных проблем, правильно позиционируя элементы.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class MainScene extends Phaser.Scene
{
constructor()
{
super({ key: "MainScene" });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
this.load.image('block', 'assets/sprites/block.png');
}
create ()
{
// render out the player health bar
const playerMonsterName = this.add.text(30, 20, 'IGUANIGNITE', {
color: '#7E3D3F',
fontSize: '32px',
});
this.add.container(556, 318, [
this.add.image(0, 0, BATTLE_ASSET_KEYS.HEALTH_BAR_BACKGROUND).setOrigin(0),
playerMonsterName,
this.add.text(30, 55, 'HP', { // this line
color: '#FF6505',
fontSize: '24px',
fontStyle: 'italic',
}),
this.add.text(playerMonsterName.width + 35, 23, 'L5', {
color: '#ED474B',
fontSize: '28px',
}),
this.add
.text(443, 80, '25/25', {
color: '#7E3D3F',
fontSize: '16px',
})
.setOrigin(1, 0),
]);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: 0xFFFFFF,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: [ MainScene ]
};
const game = new Phaser.Game(config);
Проблема: "уплывающий" текст
В данном примере создаётся сцена с контейнером, который должен отображать полоску здоровья и связанные с ней текстовые метки. Контейнер позиционируется в точке (556, 318). Внутрь него добавляются фон полоски здоровья (HEALTH_BAR_BACKGROUND) и несколько объектов текста.
Ключевая проблема заключается в том, что объект playerMonsterName создаётся и добавляется на сцену *до* создания контейнера, в точке (30, 20) относительно глобальных координат сцены. После этого тот же самый текстовый объект добавляется в массив дочерних элементов контейнера. Это приводит к конфликту систем координат. Текст начинает учитывать позицию контейнера, но его исходная позиция (30, 20) задана в мировых координатах, что даёт неверный финальный результат на экране.
Анализ кода: где скрывается ошибка
Давайте внимательно посмотрим на метод create. Первый текстовый объект создаётся с помощью this.add.text.
const playerMonsterName = this.add.text(30, 20, 'IGUANIGNITE', {
color: '#7E3D3F',
fontSize: '32px',
});
Этим вызовом объект сразу добавляется в дисплейный список сцены с координатами X=30, Y=20.
Затем создаётся контейнер в точке (556, 318). Вторым элементом в его массив передаётся уже существующий объект playerMonsterName.
this.add.container(556, 318, [
this.add.image(0, 0, BATTLE_ASSET_KEYS.HEALTH_BAR_BACKGROUND).setOrigin(0),
playerMonsterName, // <-- Проблемный объект, уже добавленный на сцену
this.add.text(30, 55, 'HP', {
color: '#FF6505',
fontSize: '24px',
fontStyle: 'italic',
}),
// ... другие элементы
]);
В отличие от него, текстовый объект 'HP' создаётся непосредственно внутри вызова container. Его координаты (30, 55) будут корректно интерпретированы как локальные относительно контейнера, а не сцены.
Решение: единый источник координат
Чтобы все элементы контейнера позиционировались предсказуемо, их координаты должны быть заданы в одной системе отсчёта — локальной для этого контейнера. Есть два основных подхода.
**Подход 1: Создавать все элементы внутри контейнера.** Это самый чистый и рекомендуемый способ. Координаты каждого text или image будут отсчитываться от левого верхнего угла контейнера.
create ()
{
const containerX = 556;
const containerY = 318;
const container = this.add.container(containerX, containerY);
// Все элементы создаются как дочерние для сцены, но сразу передаются в контейнер
const background = this.add.image(0, 0, BATTLE_ASSET_KEYS.HEALTH_BAR_BACKGROUND).setOrigin(0);
const playerMonsterName = this.add.text(30, 20, 'IGUANIGNITE', {
color: '#7E3D3F',
fontSize: '32px',
});
const hpLabel = this.add.text(30, 55, 'HP', {
color: '#FF6505',
fontSize: '24px',
fontStyle: 'italic',
});
container.add([background, playerMonsterName, hpLabel]);
}
**Подход 2: Явно задавать позицию после добавления в контейнер.** Если объект уже был создан, можно сбросить его позицию после включения в контейнер, используя локальные координаты.
// ... playerMonsterName создан ранее ...
container.add(playerMonsterName);
playerMonsterName.setPosition(30, 20); // Теперь это локальные координаты контейнера
Важное правило работы с Container
Объект в Phaser может иметь только одного родителя в дисплейном списке. Когда вы передаёте уже существующий на сцене объект (как playerMonsterName) в метод container.add() или в конструктор контейнера, он автоматически удаляется со своего старого места в списке отображения сцены и становится дочерним элементом контейнера. Однако его свойства `xиy` при этом не сбрасываются и продолжают хранить старые значения в мировых координатах, что и приводит к визуальному "сдвигу".
Всегда помните: **координаты объектов внутри контейнера должны быть локальными относительно его позиции (0,0).** Если вам нужно преобразовать мировые координаты в локальные, можно использовать методы container.getLocalTransformMatrix() или просто пересчитывать позицию вручную, вычитая позицию контейнера.
Что попробовать дальше
Ошибка рендеринга текста в примере наглядно демонстрирует важность понимания иерархии и систем координат в Phaser. Контейнеры — это не просто визуальные группы, а полноценные родительские объекты, меняющие точку отсчёта для своих детей. Для экспериментов попробуйте
- Создать интерфейс с несколькими вложенными контейнерами и проследить цепочку координат
- Написать вспомогательную функцию, которая автоматически корректирует позицию существующего объекта при его добавлении в новый контейнер
- Использовать отладку, выводя в консоль мировые (`x
,y) и локальные (getLocalPoint`) координаты проблемных элементов
