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

В 2D-играх объекты часто перекрывают друг друга. Управление порядком отрисовки — ключ к реалистичной графике. Метод `depthSort()` из списка детей сцены (`this.children`) позволяет быстро отсортировать массив игровых объектов по их глубине (значению `depth`) от нижнего к верхнему. Это особенно полезно для определения, какой объект находится «сверху» в заданной точке, например, при клике мышью или для логики взаимодействий.

Версия 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.atlas('atlas', 'assets/atlas/megaset-3.png', 'assets/atlas/megaset-3.json');
    }

    create ()
    {
        //  Create a bunch of images and store some of them in a local array
        //  Works even with setDepth() added
        const image1 = this.add.image(100, 300, 'atlas', 'contra2');
        const image2 = this.add.image(200, 300, 'atlas', 'contra3');
        const image3 = this.add.image(300, 300, 'atlas', 'exocet_spaceman');
        const image4 = this.add.image(400, 300, 'atlas', 'helix');
        const image5 = this.add.image(500, 300, 'atlas', 'pacman_by_oz_28x28');
        const image6 = this.add.image(600, 300, 'atlas', 'profil-sad-plush');

        const test1 = [ image1, image2, image4 ];
        //  contra2 -> contra3 -> helix
        this.dump(test1);

        const test2 = [ image6, image4, image2 ];
        //  profil-sad-plush -> helix -> contra3
        this.dump(test2);

        const test3 = [ image6, image4, image2 ];
        //  contra3 -> helix -> profil-sad-plush
        this.children.depthSort(test3);
        this.dump(test3);

        const test4 = [ image3, image1, image2, image1, image1, image6, image4 ];
        this.children.depthSort(test4);
        //  contra2 -> contra2 -> contra2 -> contra3 -> exocet_spaceman -> helix -> profil-sad-plush
        this.dump(test4);
    }

    dump (arr)
    {
        let s = '';
        arr.forEach(function(e, i) {
            s = s.concat(e.frame.name);

            if (i < arr.length - 1)
            {
                s = s.concat(' -> ');
            }
        });
        console.log(s);
    }
}

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

const game = new Phaser.Game(config);

Проблема: кто сверху?

Когда несколько спрайтов занимают одну и ту же область экрана, возникает вопрос: какой из них считать видимым «сверху»? Phaser управляет порядком отрисовки через свойство depth у каждого игрового объекта. Чем выше значение depth, тем «ближе» объект к камере, и тем позже он будет нарисован (перекрывая другие).

Однако, если у вас есть массив объектов (например, собранных по определенным координатам), их порядок в массиве может не соответствовать реальному порядку отрисовки. Метод depthSort() решает эту задачу.

Как работает `depthSort()`

Метод this.children.depthSort() принимает массив игровых объектов и сортирует его **на месте** (in-place) по возрастанию их свойства depth. После сортировки первый элемент массива будет иметь наименьшую глубину (будет «нижним»), а последний — наибольшую (будет «верхним»).

const group = [spriteA, spriteB, spriteC];
this.children.depthSort(group);
// Теперь group[0] — самый нижний, group[2] — самый верхний.

Важно: метод изменяет исходный массив. Исходный порядок элементов теряется.

Разбор примера кода

В примере создается шесть изображений (image1...image6). По умолчанию их depth равен 0, и порядок отрисовки соответствует порядку добавления на сцену (последний добавленный — сверху).

Сначала мы видим базовые тесты. Массив test1 создается вручную, и его дамп показывает порядок, в котором объекты были добавлены в массив. Это не обязательно порядок по глубине.

const test1 = [ image1, image2, image4 ];
// дамп: contra2 -> contra3 -> helix

Затем массив test3 демонстрирует работу depthSort. До сортировки порядок в массиве: image6, image4, image2. После вызова метода порядок меняется согласно depth объектов.

const test3 = [ image6, image4, image2 ];
// До сортировки: profil-sad-plush -> helix -> contra3
this.children.depthSort(test3);
// После сортировки: contra3 -> helix -> profil-sad-plush

Поскольку все объекты имеют depth = 0, сортировка, по сути, возвращает их в порядок добавления на сцену (от первого к последнему). Чтобы увидеть реальный эффект, нужно менять depth через setDepth().

Практическое применение: выбор верхнего объекта

Основное применение — определение, с каким объектом взаимодействует игрок. Например, при клике мыши вы собираете все объекты под курсором в массив, а затем с помощью depthSort() находите тот, что отрисовывается последним (верхний элемент массива).

// Псевдокод: находим объекты под указателем
const objectsUnderPointer = getObjectsAtPoint(pointer.x, pointer.y);
// Сортируем по глубине
this.children.depthSort(objectsUnderPointer);
// Верхний объект — последний в массиве
const topObject = objectsUnderPointer[objectsUnderPointer.length - 1];
// Взаимодействуем с topObject

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

Особенности и ограничения

1. **Сортировка на месте:** Исходный массив изменяется. Если исходный порядок нужно сохранить, передавайте копию массива.

const sortedArray = [...originalArray];
    this.children.depthSort(sortedArray);

2. **Работает только с игровыми объектами:** Метод ожидает массив объектов, являющихся потомками this.children. Передача других данных вызовет ошибку. 3. **Зависит от depth:** Эффективность метода раскрывается при явном управлении глубиной. Без изменения depth сортировка может давать неочевидные результаты.

image3.setDepth(5); // Этот объект будет всегда сверху
    image1.setDepth(-2); // Этот объект будет всегда снизу

4. **Повторяющиеся объекты:** Как видно в test4, метод корректно обрабатывает дубликаты одного и того же объекта в массиве.

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

Метод children.depthSort() — это мощный и эффективный инструмент для работы с порядком отрисовки в Phaser. Он незаменим для логики выбора объектов, управления UI-слоями и создания сложных сцен. **Идеи для экспериментов:** 1. Создайте сцену с накладывающимися друг на друга карточками. Реализуйте перетаскивание, всегда выбирая верхнюю карточку под курсором. 2. Сымитируйте изометрическую проекцию, назначая объектам depth в зависимости от их Y-координаты, и используйте depthSort для корректного отображения. 3. Постройте систему контекстного меню, которая появляется при клике правой кнопкой мыши на самом верхнем объекте в группе.