О чем этот пример
Работа с множеством игровых объектов как с единым целым — частая задача в разработке игр. Phaser предоставляет мощный инструмент `Container`, который группирует объекты, позволяя применять к ним общие трансформации (позицию, угол поворота, масштаб) и упрощая управление. Однако при добавлении интерактивности к объектам внутри контейнера важно понимать, как трансформации контейнера влияют на обработку ввода. В этой статье мы разберем пример, где кнопка с анимированным текстом вращается внутри контейнера, но продолжает корректно реагировать на события мыши.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('buttonBG', 'assets/sprites/button-bg.png');
this.load.image('buttonText', 'assets/sprites/button-text.png');
}
create ()
{
const bg = this.add.image(0, 0, 'buttonBG').setInteractive();
const text = this.add.image(0, 0, 'buttonText');
const container = this.add.container(400, 300, [ bg, text ]);
container.setAngle(20);
this.tweens.add({
targets: text,
alpha: 0.5,
duration: 1000,
ease: 'Sine.easeOut',
yoyo: true,
repeat: -1
});
bg.on('pointerover', function ()
{
this.setTint(0x44ff44);
});
bg.on('pointerout', function ()
{
this.clearTint();
});
bg.once('pointerup', function ()
{
this.tweens.add({
targets: container,
y: 900,
duration: 500
});
}, this);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#010101',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Создание и настройка контейнера
В методе create сцены создаются два изображения: фон кнопки (buttonBG) и текст (buttonText). Оба имеют начальные координаты (0, 0), так как их позиция будет определяться относительно контейнера.
Затем эти объекты передаются в конструктор this.add.container. Контейнер позиционируется в точке (400, 300) на сцене. Все дочерние объекты (фон и текст) теперь будут отрисовываться относительно этой точки.
Ключевой момент — применение поворота ко всему контейнеру с помощью container.setAngle(20). Это поворачивает всю группу на 20 градусов. Важно, что трансформации контейнера (позиция, угол, масштаб) применяются ко всем его дочерним элементам.
const bg = this.add.image(0, 0, 'buttonBG').setInteractive();
const text = this.add.image(0, 0, 'buttonText');
const container = this.add.container(400, 300, [ bg, text ]);
container.setAngle(20);
Добавление интерактивности и анимации
Фон кнопки (bg) сразу делается интерактивным с помощью .setInteractive(). Это позволяет ему генерировать события ввода, такие как pointerover, pointerout и pointerup.
Для создания визуальной обратной связи наводки мыши используются обработчики событий. При наведении курсора (pointerover) фон окрашивается в зеленый оттенок с помощью setTint. Когда курсор уходит (pointerout), оттенок снимается.
Одновременно с этим, для текста создается бесконечная анимация мерцания с помощью this.tweens.add. Анимация меняет свойство alpha (прозрачность) текста от исходного значения до 0.5 и обратно, создавая эффект пульсации.
bg.on('pointerover', function () {
this.setTint(0x44ff44);
});
bg.on('pointerout', function () {
this.clearTint();
});
this.tweens.add({
targets: text,
alpha: 0.5,
duration: 1000,
ease: 'Sine.easeOut',
yoyo: true,
repeat: -1
});
Обработка клика и глобальный контекст
Событие клика (pointerup) обрабатывается с помощью метода once, который гарантирует, что обработчик выполнится только один раз. Это идеально для действия, которое не должно повторяться (например, отправка кнопки за пределы экрана).
Внутри обработчика pointerup запускается твин, который перемещает весь container по оси Y вниз за пределы экрана. Обратите внимание на два важных аспекта:
1. **Цель анимации — контейнер.** Поскольку мы двигаем контейнер, все его дочерние объекты (и фон, и текст) движутся вместе с ним, сохраняя свои относительные позиции и поворот.
2. **Использование контекста this.** В примере обработчик pointerup передается с третьим аргументом this. Это гарантирует, что внутри функции-обработчика ключевое слово this будет ссылаться на экземпляр сцены (Example), а не на игровой объект bg. Без этого вызов this.tweens.add внутри обработчика привел бы к ошибке, так как bg не имеет свойства tweens.
bg.once('pointerup', function () {
this.tweens.add({
targets: container,
y: 900,
duration: 500
});
}, this); // Передача контекста сцены как третьего аргумента
Важность порядка: интерактивность до контейнера
В этом примере есть тонкий, но критически важный момент. Метод .setInteractive() вызывается для фона (bg) **до** того, как он добавляется в контейнер. Почему это важно?
Область взаимодействия (хитбокс) интерактивного объекта рассчитывается в момент вызова setInteractive. Если бы мы сначала добавили объект в контейнер, а потом сделали его интерактивным, Phaser рассчитал бы хитовую область уже в повернутой системе координат контейнера, что могло бы привести к некорректной работе.
В данном примере, так как интерактивность установлена до поворота контейнера, Phaser корректно обрабатывает ввод, учитывая итоговые трансформации объекта на сцене. Кнопка реагирует на события мыши правильно, несмотря на то, что визуально она повернута на 20 градусов.
// Правильный порядок:
const bg = this.add.image(0, 0, 'buttonBG').setInteractive(); // 1. Создали и сделали интерактивным
// ...
const container = this.add.container(400, 300, [ bg, text ]); // 2. Добавили в контейнер
container.setAngle(20); // 3. Применили трансформацию
Что попробовать дальше
Контейнеры в Phaser — это мощный способ организации сложных составных объектов. Они позволяют управлять группой спрайтов как единым целым, применяя общие трансформации и упрощая логику перемещения. Ключевой вывод из примера: для корректной обработки ввода повернутых или масштабированных объектов, интерактивность (setInteractive) следует настраивать **до** добавления объекта в контейнер и применения трансформаций к этому контейнеру. Для экспериментов попробуйте
- изменить угол поворота контейнера и проверить, как это влияет на анимацию твинов
- добавить интерактивность объекту после помещения в контейнер и наблюдать за поведением
- создать иерархию из нескольких вложенных контейнеров с разными трансформациями
