О чем этот пример
Когда в игре множество объектов, они могут перекрывать друг друга в неправильном порядке, ломая иллюзию глубины. Phaser 3 предоставляет простой механизм управления порядком отрисовки через свойство `depth`. Эта статья покажет, как использовать его для создания эффекта псевдо-3D и корректного отображения слоев в 2D-играх.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.move = 0;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('atlas', 'assets/tests/fruit/veg.png', 'assets/tests/fruit/veg.json');
this.load.image('image', 'assets/sprites/mushroom2.png');
}
create ()
{
for (let i = 0; i < 2000; i++)
{
const image = this.add.image(100 + Math.random() * 600, 100 + Math.random() * 400, 'atlas', 'veg0' + Math.floor(1 + Math.random() * 9));
image.depth = image.y;
}
this.mushroom0 = this.add.image(400, 300, 'image');
this.mushroom1 = this.add.image(400, 300, 'image');
this.mushroom2 = this.add.image(400, 300, 'image');
}
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;
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое свойство `depth`?
В Phaser 3 каждый игровой объект (например, Image или Sprite) имеет свойство depth. Оно определяет порядок, в котором рендерер будет отрисовывать объекты на холсте или WebGL-контексте. Объекты с большим значением depth рисуются поверх объектов с меньшим значением.
По умолчанию объекты рисуются в порядке их добавления на сцену с помощью методов типа this.add.image(). Свойство depth позволяет переопределить этот порядок динамически.
image.depth = 100;
Статическая сортировка по оси Y
Классический прием в 2D-играх — сортировка объектов по их координате Y, чтобы создавать иллюзию глубины: объекты, которые находятся «ниже» на экране (имеют большую координату Y), должны перекрывать те, что «выше». В примере это реализовано для 2000 случайно размещенных спрайтов овощей.
В методе create() каждому созданному изображению присваивается depth, равный его координате `y`. В результате спрайты, расположенные ниже по вертикали, будут отрисовываться поверх тех, что выше, создавая естественный вид.
for (let i = 0; i < 2000; i++) {
const image = this.add.image(100 + Math.random() * 600, 100 + Math.random() * 400, 'atlas', 'veg0' + Math.floor(1 + Math.random() * 9));
image.depth = image.y;
}
Динамическое обновление глубины
Для объектов, которые движутся, значение depth необходимо обновлять каждый кадр, чтобы порядок отрисовки оставался корректным. В примере три гриба (mushroom0, mushroom1, mushroom2) движутся по разным траекториям.
Их глубина пересчитывается в методе update() на основе текущей позиции. К координате `yприбавляется половина высоты спрайта (height / 2`). Это смещает точку расчета глубины к центру спрайта, что часто дает более точный визуальный результат, особенно для объектов с высокой «ножкой» или смещенным якорем.
this.mushroom0.depth = this.mushroom0.y + this.mushroom0.height / 2;
this.mushroom1.depth = this.mushroom1.y + this.mushroom1.height / 2;
this.mushroom2.depth = this.mushroom2.y + this.mushroom2.height / 2;
Поведение объектов с одинаковой глубиной
Что произойдет, если несколько объектов получат одинаковое значение depth? Phaser сохранит для них исходный порядок добавления на сцену (order of creation). Однако в динамической сцене, где объекты постоянно движутся и их координаты Y могут совпадать, лучше добавлять небольшие поправки к расчету, чтобы избежать мерцания (z-fighting) и обеспечить стабильный порядок.
Например, можно добавить уникальный идентификатор, умноженный на очень малое число:
// Пример для массива объектов
objects.forEach((obj, index) => {
obj.depth = obj.y + (index * 0.0001);
});
Производительность и оптимизация
Постоянное обновление depth для тысяч объектов в update() может быть затратным. В примере статические овощи получают глубину один раз в create(), так как они не двигаются. Для движущихся объектов обновление неизбежно.
Если в вашей сцене много статичных объектов, рассчитайте и установите depth один раз при создании. Для динамических объектов обновляйте свойство только при реальном изменении их позиции, а не каждый кадр, если это возможно.
Также помните, что сортировка по depth выполняется рендерером Phaser. Чем больше уникальных значений глубины, тем больше работы для рендерера. В некоторых случаях можно группировать объекты в «слои» с фиксированными значениями глубины (например, фон: 0, игрок: 100, эффекты: 200, UI: 1000).
Что попробовать дальше
Свойство depth — это мощный и гибкий инструмент для управления визуальной иерархией объектов в Phaser 3. Используйте его для создания иллюзии глубины в 2D-играх, корректного отображения частиц и эффектов, а также организации сложных UI-слоев.
**Идеи для экспериментов:**
1. Создайте платформер, где игрок корректно проходит за и перед фоновыми объектами.
2. Реализуйте систему погоды (дождь, снег), где частицы имеют динамическую глубину относительно игрока.
3. Сымитируйте изометрическую проекцию, вычисляя глубину как сумму координат x + y.
