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

При разработке сложных игровых интерфейсов или многослойных объектов часто возникает необходимость группировать элементы. 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('eye', 'assets/pics/lance-overdose-loader-eye.png');
    }

    create ()
    {
        const container1 = this.add.container(100, 100);
        const container2 = this.add.container(200, 200);

        const sprite = this.add.image(0, 0, 'eye').setInteractive();
        
        container1.add(container2);
        container2.add(sprite);

        // container1.setVisible(false);

        sprite.on('pointerover', function ()
        {
            console.log('over');
            this.setTint(0xff0000);
        });

        sprite.on('pointerout', function ()
        {
            console.log('out');
            this.clearTint();
        });
    }
}

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

const game = new Phaser.Game(config);

Что такое контейнер и зачем он нужен

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

В примере создаются два контейнера и один спрайт.

const container1 = this.add.container(100, 100);
const container2 = this.add.container(200, 200);
const sprite = this.add.image(0, 0, 'eye').setInteractive();

Иерархия и вложенность

Ключевой момент примера — создание иерархической структуры. Объекты добавляются друг в друга с помощью метода .add(). Спрайт добавляется в container2, а container2, в свою очередь, добавляется в container1.

container1.add(container2);
container2.add(sprite);

Таким образом, спрайт становится «внуком» для container1. Все трансформации (позиция, видимость), примененные к родительскому контейнеру, будут каскадно применены ко всем его потомкам.

Наследование свойства видимости

В закомментированной строке кода показана одна из самых полезных особенностей контейнеров.

// container1.setVisible(false);

Если раскомментировать этот вызов, container1, а вместе с ним и все его дочерние элементы (container2 и sprite), станут невидимыми. Это происходит потому, что свойство visible родительского объекта влияет на конечную видимость всех потомков в дереве сцены. Это мощный механизм для управления целыми слоями или группами объектов одной командой.

Интерактивность внутри контейнеров

Спрайту явно задается интерактивность с помощью .setInteractive(). Это позволяет ему обрабатывать события указателя (мыши, касания).

const sprite = this.add.image(0, 0, 'eye').setInteractive();

Обратите внимание: координаты спрайта (0, 0) задаются относительно своего родителя — container2. А глобальное положение спрайта на холсте рассчитывается как сумма смещений всех его родителей: container1 (100,100) + container2 (200,200) + sprite (0,0). Интерактивность и события работают с итоговыми, мировыми координатами объекта.

Обработка событий указателя

В примере на спрайт повешены обработчики событий pointerover и pointerout.

sprite.on('pointerover', function ()
{
    console.log('over');
    this.setTint(0xff0000);
});

sprite.on('pointerout', function ()
{
    console.log('out');
    this.clearTint();
});

Когда курсор наводится на спрайт, в консоль выводится 'over', и спрайт окрашивается в красный цвет с помощью setTint(). При уходе курсора — выводится 'out', и оттенок снимается. Важно, что эти события будут срабатывать, даже если спрайт находится глубоко внутри иерархии контейнеров, при условии, что все родители видимы.

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

Вложенные контейнеры в Phaser — это фундаментальный инструмент для организации сложных игровых объектов и UI. Они позволяют применять трансформации каскадно и централизованно управлять группами. Для экспериментов попробуйте

  1. Раскомментировать container1.setVisible(false) и убедиться, что интерактивность спрайта тоже пропадает
  2. Изменить порядок добавления объектов в контейнеры
  3. Добавить перемещение container1 с помощью this.tweens.add() и наблюдать, как вся группа плавно движется как единое целое