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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('cake', 'assets/sprites/cake.png');
    }

    create ()
    {
        this.add.text(10, 10, 'Click Sprite to move between Containers');

        const size1 = this.add.text(10, 48);
        const size2 = this.add.text(410, 48);

        this.add.line(400, 300, 0, 0, 0, 600, 0xffffff);

        const container1 = this.add.container();
        const container2 = this.add.container();

        for (let i = 0; i < 8; i++)
        {
            const x = Phaser.Math.Between(64, 336);
            const y = Phaser.Math.Between(128, 536);

            const sprite = new Phaser.GameObjects.Sprite(this, x, y, 'cake');

            container1.add(sprite);

            sprite.setInteractive();

            sprite.on('pointerdown', () => {

                if (sprite.parentContainer === container1)
                {
                    container2.add(sprite);

                    sprite.x += 400;
                }
                else
                {
                    container1.add(sprite);

                    sprite.x -= 400;
                }

                size1.setText('Container 1 size: ' + container1.length);
                size2.setText('Container 2 size: ' + container2.length);

            });
        }

        size1.setText('Container 1 size: ' + container1.length);
        size2.setText('Container 2 size: ' + container2.length);
    }
}

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

const game = new Phaser.Game(config);

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

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

В примере создаются два контейнера, и спрайты с изображением торта могут переключаться между ними по клику.

Создание контейнеров и добавление спрайтов

Инициализация происходит в методе create() сцены. Сначала создаются два пустых контейнера и разделительная линия для наглядности. Затем в цикле генерируются спрайты с случайными координатами и добавляются в первый контейнер.

const container1 = this.add.container();
const container2 = this.add.container();

for (let i = 0; i < 8; i++)
{
    const x = Phaser.Math.Between(64, 336);
    const y = Phaser.Math.Between(128, 536);
    const sprite = new Phaser.GameObjects.Sprite(this, x, y, 'cake');
    container1.add(sprite);
}

Каждый спрайт создается через new Phaser.GameObjects.Sprite. Важно: изначально спрайт не находится в списке отображения сцены. Он попадает в него только после добавления в контейнер (который сам является дочерним объектом сцены). Метод container1.add(sprite) делает спрайт видимым.

Взаимодействие и логика перемещения

Чтобы спрайт реагировал на клик, ему нужно назначить обработчик событий. Для этого используется setInteractive(). Далее на событие pointerdown вешается функция, которая проверяет текущий родительский контейнер спрайта и перемещает его в другой.

sprite.setInteractive();
sprite.on('pointerdown', () => {
    if (sprite.parentContainer === container1)
    {
        container2.add(sprite);
        sprite.x += 400;
    }
    else
    {
        container1.add(sprite);
        sprite.x -= 400;
    }
});

Ключевой момент: свойство parentContainer хранит ссылку на контейнер, в котором в данный момент находится объект. Метод container2.add(sprite) автоматически удаляет спрайт из предыдущего контейнера и добавляет в новый. Смещение координаты sprite.x на 400 пикселей компенсирует визуальное расположение контейнеров по разные стороны от разделительной линии.

Отслеживание состояния контейнеров

Контейнер имеет свойство length, которое показывает количество содержащихся в нем объектов. В примере это используется для отображения текстовых счетчиков, обновляющихся после каждого перемещения.

size1.setText('Container 1 size: ' + container1.length);
size2.setText('Container 2 size: ' + container2.length);

Такой подход полезен для отладки и создания UI, отображающего, например, количество предметов в инвентаре игрока или на игровом поле.

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

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