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

Работая над игровым интерфейсом, часто нужно сделать кликабельной не простую кнопку, а целую группу объектов — например, иконку с подсветкой и текстом. Phaser Container позволяет объединять объекты, но по умолчанию он не интерактивен. В этой статье разберем, как назначить контейнеру собственную хит-область (hit area), чтобы обрабатывать события наведения и клика для всей группы сразу. Этот подход упрощает создание сложных интерактивных элементов, таких как меню, инвентарь или кастомизируемые кнопки.

Версия 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');
        const text = this.add.image(0, 0, 'buttonText');

        const container = this.add.container(400, 300, [ bg, text ]).setAngle(-30);

        container.setInteractive(new Phaser.Geom.Circle(0, 0, 60), Phaser.Geom.Circle.Contains);

        this.tweens.add({
            targets: container,
            angle: 30,
            duration: 3000,
            ease: 'Sine.easeOut',
            yoyo: true,
            repeat: -1
        });

        this.tweens.add({
            targets: text,
            alpha: 0.5,
            duration: 1000,
            ease: 'Sine.easeOut',
            yoyo: true,
            repeat: -1
        });

        container.on('pointerover', () =>
        {

            bg.setTint(0x44ff44);

        });

        container.on('pointerout', () =>
        {

            bg.clearTint();

        });

        container.once('pointerup', function ()
        {

            this.destroy();

        });

        //  Just to display the hit area, not actually needed to work
        const graphics = this.add.graphics();

        graphics.lineStyle(2, 0x00ffff, 1);

        graphics.strokeCircle(container.x, container.y, container.input.hitArea.radius);
    }
}

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

const game = new Phaser.Game(config);

Создание и настройка контейнера

В Phaser Container — это специальный игровой объект, который выступает в роли родителя для других объектов. Все дочерние элементы наследуют трансформации (позицию, угол, масштаб) контейнера. В примере мы создаем кнопку из двух изображений: фона (buttonBG) и текста (buttonText).

Сначала загружаем изображения в preload(), затем в create() создаем их как отдельные Image объекты с координатами (0, 0) относительно будущего контейнера.

const bg = this.add.image(0, 0, 'buttonBG');
const text = this.add.image(0, 0, 'buttonText');

Далее создаем сам контейнер, передавая ему координаты на сцене (400, 300) и массив дочерних элементов. Сразу поворачиваем его на -30 градусов для наглядности.

const container = this.add.container(400, 300, [ bg, text ]).setAngle(-30);

На этом этапе контейнер отображается, но не реагирует на действия мыши.

Назначение интерактивности и хит-области

Ключевой момент — метод setInteractive(). Он делает любой игровой объект, включая контейнер, чувствительным к событиям ввода. По умолчанию хит-область совпадает с прямоугольными границами объекта, что для повернутого контейнера неудобно. Мы можем задать свою, более подходящую геометрию.

В примере используется круглая хит-область (Phaser.Geom.Circle) с центром в (0, 0) относительно контейнера и радиусом 60 пикселей. Второй параметр — функция проверки попадания точки в эту область (Phaser.Geom.Circle.Contains).

container.setInteractive(new Phaser.Geom.Circle(0, 0, 60), Phaser.Geom.Circle.Contains);

Теперь события pointerover, pointerout и pointerup будут срабатывать, когда курсор находится в пределах этого круга, независимо от реальной формы дочерних спрайтов.

Обработка событий и визуальная обратная связь

После назначения интерактивности можно подписаться на события. В примере реализована стандартная логика для кнопки: - При наведении (pointerover) фон окрашивается в зеленый оттенок с помощью setTint. - При уходе курсора (pointerout) tint сбрасывается. - При клике (pointerup) контейнер и все его дети уничтожаются методом destroy(). Обратите внимание на once, который гарантирует, что обработчик сработает только один раз.

container.on('pointerover', () => {
    bg.setTint(0x44ff44);
});
container.on('pointerout', () => {
    bg.clearTint();
});
container.once('pointerup', function () {
    this.destroy();
});

Для наглядности в примере также добавлена анимация с помощью tweens: контейнер плавно качается, а текст мерцает. Это демонстрирует, что интерактивность и твины работают независимо и не конфликтуют.

Визуализация хит-области (для отладки)

При разработке полезно видеть границы области взаимодействия. Хотя для работы функционала это не требуется, пример показывает, как отрисовать контур хит-области с помощью графики (Graphics).

Радиус круга берется из свойства container.input.hitArea.radius, а его мировые координаты — из container.x и container.y. Это важно, потому что хит-область задавалась в локальных координатах контейнера.

const graphics = this.add.graphics();
graphics.lineStyle(2, 0x00ffff, 1);
graphics.strokeCircle(container.x, container.y, container.input.hitArea.radius);

Такой подход помогает точно настроить зону клика, особенно при использовании сложной геометрии вроде Polygon или Ellipse.

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

Использование Container с кастомной хит-областью — мощный паттерн для создания сложных интерактивных элементов в Phaser. Вы можете объединять спрайты, текст, частицы и анимации в единый кликабельный объект. Для экспериментов попробуйте: задать хит-область в форме Phaser.Geom.Rectangle для кнопок-прямоугольников; сделать область меньше визуальных элементов для более точного клика; или динамически менять hitArea в зависимости от состояния объекта, создавая эффект "активной зоны".