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

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

Версия 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 ]);

        container.setSize(bg.width, bg.height);

        container.setInteractive();

        this.input.setDraggable(container);

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

            bg.setTint(0x44ff44);

        });

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

            bg.clearTint();

        });

        this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
        {

            gameObject.x = dragX;
            gameObject.y = dragY;

        });
    }
}

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

const game = new Phaser.Game(config);

Загрузка ресурсов и создание контейнера

В методе preload мы загружаем два изображения: фон кнопки и текстовую метку. Обратите внимание на использование setBaseURL для указания базового пути.

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 мы сначала создаем два игровых объекта Image, позиционируя их в точке (0, 0). Затем эти объекты передаются в массив при создании контейнера. Контейнер создается с помощью this.add.container(400, 300, [ bg, text ]). Здесь (400, 300) — это мировые координаты центра контейнера на сцене. Все дочерние объекты (bg и text) будут отрисовываться относительно этой точки.

Настройка интерактивности контейнера

Чтобы контейнер мог обрабатывать события ввода (например, клик или перетаскивание), ему необходимо задать хитовую область (hit area). По умолчанию у контейнера её нет.

container.setSize(bg.width, bg.height);
container.setInteractive();

Метод setSize задаёт размер хитовой области, равный размеру фонового изображения кнопки. После этого вызов setInteractive() делает контейнер интерактивным, то есть он начнёт испускать события указателя (pointer events). Далее мы явно разрешаем перетаскивание этого контейнера с помощью this.input.setDraggable(container).

Обработка событий наведения и перетаскивания

Теперь мы можем подписаться на события контейнера. В данном примере мы обрабатываем два события указателя: pointerover (курсор наведён на объект) и pointerout (курсор уведён с объекта).

container.on('pointerover', () => {
    bg.setTint(0x44ff44);
});

container.on('pointerout', () => {
    bg.clearTint();
});

При наведении мы применяем зеленоватый оттенок (tint) к фону кнопки, а при уходе курсора — очищаем его. Это визуальный feedback для игрока.

Для обработки самого процесса перетаскивания мы слушаем глобальное событие drag на this.input. Это событие генерируется для всех перетаскиваемых объектов.

this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
    gameObject.x = dragX;
    gameObject.y = dragY;
});

В колбэке мы получаем текущие координаты перетаскивания (dragX, dragY) и присваиваем их позиции перетаскиваемого игрового объекта (gameObject). В нашем случае этим объектом будет наш контейнер. Таким образом, при движении мыши контейнер (со всеми своими детьми) будет следовать за курсором.

Конфигурация игры и запуск сцены

Весь функционал инкапсулирован в классе сцены Example. Для запуска игры мы создаём конфигурационный объект, стандартный для Phaser, и передаём его в конструктор Phaser.Game.

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

const game = new Phaser.Game(config);

Ключевой параметр здесь — scene: Example, который указывает, какую сцену запустить при старте игры. Остальные параметры задают размеры холста, цвет фона и контейнер в DOM, куда будет встроена игра.

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

Контейнеры — это фундаментальный механизм Phaser для работы с группами объектов. Они экономят силы разработчика, позволяя назначать свойства и обрабатывать события для целой группы разом. На основе этого примера можно экспериментировать: добавьте контейнеру в дочерние объекты анимацию, сделайте кнопку нажимаемой (pointerdown), организуйте систему вложенных контейнеров для сложных HUD-элементов или реализуйте "привязку" контейнера к определённым зонам сцены при перетаскивании.