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

Один из ключевых вопросов при работе с Phaser 3 — как эффективно управлять множеством игровых объектов. Встроенный Display List отлично справляется с базовыми задачами, но для сложной организации элементов нужны слои (Layers). В этой статье разберем практический пример переноса спрайтов между основным списком отображения и отдельным слоем, что позволит вам гибко управлять группами объектов, их видимостью и порядком отрисовки.

Версия 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('coffee', 'assets/sprites/coffee.png');
        this.load.image('donut', 'assets/sprites/donut.png');
    }

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

        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 layer = this.add.layer();

        const clickSprite = function ()
        {
            if (this.displayList === layer)
            {
                //  By calling addToDisplayList it will automatically remove
                //  it from the Layer and add it back onto the Display List.
                this.addToDisplayList();

                //  If you want to remove it from the Layer and NOT add it
                //  to the Display List, then call layer.remove(this)

                this.x -= 400;
            }
            else
            {
                //  Move from Display List to Layer
                //  Doing this will automatically remove it from the Display List
                layer.add(this);

                this.x += 400;
            }

            size1.setText('Display List size: ' + this.scene.children.length);
            size2.setText('Layer size: ' + layer.length);
        }

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

            const x2 = Phaser.Math.Between(464, 736);
            const y2 = Phaser.Math.Between(128, 536);

            const sprite1 = new Phaser.GameObjects.Sprite(this, x1, y1, 'coffee');
            const sprite2 = new Phaser.GameObjects.Sprite(this, x2, y2, 'donut');

            sprite1.addToDisplayList();

            layer.add(sprite2);

            sprite1.setInteractive();
            sprite2.setInteractive();

            sprite1.on('pointerdown', clickSprite);
            sprite2.on('pointerdown', clickSprite);
        }

        size1.setText('Display List size: ' + this.children.length);
        size2.setText('Layer size: ' + layer.length);
    }
}

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

const game = new Phaser.Game(config);

Что такое Display List и Layer?

Каждая сцена Phaser 3 содержит главный контейнер для отображения объектов — Display List. Все добавленные через this.add или метод addToDisplayList() объекты попадают в него. Layer — это особый тип контейнера, который также наследуется от GameObject и может содержать в себе другие объекты. Слой можно воспринимать как отдельную группу, которую можно трансформировать, скрывать или отрисовывать поверх других элементов целиком.

Ключевое отличие: объект может находиться только в одном родительском контейнере одновременно — либо в основном списке сцены, либо в слое.

Разбор примера: создание сцены и объектов

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

Создается слой и объявляется функция-обработчик клика clickSprite. Затем в цикле генерируются 8 пар спрайтов со случайными координатами.

const layer = this.add.layer();

const clickSprite = function ()
{
    // Логика перемещения...
}

Особенность создания объектов: спрайты создаются через конструктор Phaser.GameObjects.Sprite, а не через фабрику this.add.sprite. Это важно, потому что изначально они не добавлены ни в один список отображения.

const sprite1 = new Phaser.GameObjects.Sprite(this, x1, y1, 'coffee');
const sprite2 = new Phaser.GameObjects.Sprite(this, x2, y2, 'donut');

Первый спрайт добавляется в основной Display List, второй — в созданный слой.

sprite1.addToDisplayList();
layer.add(sprite2);

Логика перемещения объектов между контейнерами

Функция clickSprite вызывается при клике на любой спрайт. Она проверяет, в каком контейнере (displayList) находится объект в данный момент.

Если объект находится в слое (this.displayList === layer), то вызов this.addToDisplayList() автоматически удалит его из слоя и поместит в основной список сцены. Объект также смещается по оси X, чтобы визуально оказаться в левой части экрана.

if (this.displayList === layer)
{
    this.addToDisplayList();
    this.x -= 400;
}

Если объект находится в основном Display List, то метод layer.add(this) автоматически удалит его оттуда и добавит в слой, после чего объект сместится вправо.

else
{
    layer.add(this);
    this.x += 400;
}

После каждого перемещения обновляется текст, отображающий количество объектов в каждом контейнере, через свойства this.scene.children.length и layer.length.

Важные нюансы работы с Layers

1. **Автоматическое удаление:** Методы addToDisplayList() и layer.add() сами заботятся об удалении объекта из предыдущего родительского контейнера. Вручную это делать не нужно. 2. **Альтернативное удаление из слоя:** Если нужно просто удалить объект из слоя, не добавляя его обратно в основной список, используйте layer.remove(this). Объект станет невидимым, пока снова не будет куда-либо добавлен. 3. **Свойство displayList:** Каждый GameObject имеет свойство displayList, которое ссылается на его текущий родительский контейнер. Это может быть либо this.scene.children (основной список), либо объект Layer. 4. **Интерактивность:** Обратите внимание, что интерактивность (setInteractive) назначается каждому спрайту индивидуально. Она сохраняется при перемещении между контейнерами.

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

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