О чем этот пример
В 2D-играх нет третьей координаты, но создать иллюзию глубины и правильного расположения объектов друг относительно друга — необходимо. Phaser решает это через свойство `depth`, которое управляет порядком отрисовки. В этой статье разберем, как работает система глубины на примере сцены с движущимися грибами на фоне овощей. Вы научитесь контролировать, какой объект будет поверх другого, и создадите базовую Z-сортировку, основанную на положении по вертикали.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class DemoA extends Phaser.Scene
{
constructor ()
{
super({ key: 'DemoA', active: true });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('veg', 'assets/tests/fruit/veg.png', 'assets/tests/fruit/veg.json');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
}
create ()
{
for (var i = 0; i < 2000; i++)
{
var image = this.add.image(Phaser.Math.Between(0, 800), Phaser.Math.Between(0, 600), 'veg', 'veg0' + Math.floor(1 + Math.random() * 9));
image.depth = image.y;
}
this.mushroom0 = this.add.image(400, 300, 'mushroom');
this.mushroom1 = this.add.image(400, 300, 'mushroom');
this.mushroom2 = this.add.image(400, 300, 'mushroom');
}
update ()
{
this.mushroom0.x = 400 + Math.cos(this.move) * 200;
this.mushroom0.y = 300 + Math.sin(this.move) * 200;
this.mushroom0.depth = this.mushroom0.y + this.mushroom0.height / 2;
this.mushroom1.x = 400 + Math.sin(-this.move) * 200;
this.mushroom1.y = 300 + Math.cos(-this.move) * 200;
this.mushroom1.depth = this.mushroom1.y + this.mushroom1.height / 2;
this.mushroom2.y = 300 + Math.sin(this.move) * 180;
this.mushroom2.depth = this.mushroom2.y + this.mushroom2.height / 2;
this.move += 0.01;
}
}
Что такое `depth` и зачем он нужен
В отличие от 3D-движков, где объекты имеют координату Z, в Phaser порядок отрисовки 2D-спрайтов определяется свойством depth. Объект с большим значением depth рисуется поверх объектов с меньшим значением. По умолчанию все объекты имеют depth = 0 и рисуются в порядке добавления на сцену.
Это порождает проблему: если вы добавите дерево, а потом персонажа позади него, персонаж все равно окажется поверх дерева. Свойство depth позволяет решить эту проблему, взяв под контроль порядок отрисовки вручную.
// Установка глубины для спрайта
image.depth = 10;
Инициализация сцены и создание фона
В методе preload загружаются ресурсы: атлас спрайтов veg и изображение гриба mushroom. Атлас — это один файл изображения со множеством отдельных спрайтов, что эффективно для памяти.
В create создается фон: 2000 случайных спрайтов овощей из атласа, разбросанных по всей сцене. Ключевой момент здесь — установка глубины для каждого овоща равной его Y-координате.
for (var i = 0; i < 2000; i++)
{
var image = this.add.image(
Phaser.Math.Between(0, 800),
Phaser.Math.Between(0, 600),
'veg',
'veg0' + Math.floor(1 + Math.random() * 9)
);
image.depth = image.y;
}
Благодаря image.depth = image.y, овощи, расположенные ниже по экрану (с большим значением Y), получают большую глубину и рисуются поверх тех, что выше. Это создает простую, но эффективную иллюзию перспективы.
Создание и анимация движущихся объектов
После фона создаются три спрайта гриба (mushroom0, mushroom1, mushroom2). Изначально они помещены в одну точку (400, 300). Их движение и управление глубиной происходит в методе update, который вызывается каждый кадр.
this.mushroom0 = this.add.image(400, 300, 'mushroom');
this.mushroom1 = this.add.image(400, 300, 'mushroom');
this.mushroom2 = this.add.image(400, 300, 'mushroom');
Переменная this.move (не инициализированная явно в конструкторе, но используемая в update) служит счетчиком времени для тригонометрических функций, задающих круговое и волнообразное движение.
Динамическое обновление глубины в реальном времени
В update координаты и глубина грибов пересчитываются каждый кадр. Движение задается через sin и cos, что создает плавные орбитальные траектории.
this.mushroom0.x = 400 + Math.cos(this.move) * 200;
this.mushroom0.y = 300 + Math.sin(this.move) * 200;
this.mushroom0.depth = this.mushroom0.y + this.mushroom0.height / 2;
Глубина для каждого гриба вычисляется как его текущая Y-координата плюс половина высоты спрайта (this.mushroom0.height / 2). Это важный нюанс: точка отсчета (pivot) спрайта по умолчанию находится в его центре. Добавление половины высоты смещает расчет глубины к "ногам" объекта, делая сортировку более корректной, когда грибы находятся на одном уровне Y.
Таким образом, когда гриб движется вниз, его depth увеличивается, и он рисуется поверх фоновых овощей и других грибов с меньшей глубиной, что соответствует естественному восприятию: ближние к наблюдателю объекты перекрывают дальние.
Что попробовать дальше
Свойство depth — мощный инструмент для управления порядком отрисовки в Phaser. Как показано в примере, его можно привязывать к координате Y для автоматической Z-сортировки, создающей иллюзию глубины. Для экспериментов попробуйте: изменить формулу глубины, добавив смещение по X; реализовать статическую глубину для слоев (фон, персонаж, UI); или использовать this.children.depthSort() для автоматической сортировки списка детей сцены на основе их значения depth.
