О чем этот пример
Одна из частых задач в 2D-играх — корректное отображение объектов по глубине (z-index), чтобы элементы, находящиеся "ближе" к игроку, перекрывали те, что "дальше". Phaser предоставляет несколько способов управления глубиной. В этой статье мы разберем пример использования `Layer` — специального контейнера, который позволяет централизованно управлять видимостью и, что самое важное, автоматически сортировать своих дочерних объектов по свойству `depth`. Мы увидим, как это работает на практике с движущимися спрайтами.
Версия 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('mushroom', 'assets/sprites/mushroom2.png');
}
create ()
{
const layer = this.add.layer();
console.log(layer);
for (let i = 0; i < 1024; i++)
{
let x = Phaser.Math.Between(100, 700);
let y = Phaser.Math.Between(100, 500);
let f = Phaser.Math.Between(1, 9);
const image = layer.add(this.make.image({ x, y, key: 'atlas', frame: 'veg0' + f }, false));
image.depth = image.y;
}
this.mushroom0 = layer.add(this.make.image({ x: 400, y: 300, key: 'mushroom' }, false));
this.mushroom1 = layer.add(this.make.image({ x: 400, y: 300, key: 'mushroom' }, false));
this.mushroom2 = layer.add(this.make.image({ x: 400, y: 300, key: 'mushroom' }, false));
this.input.on('pointerdown', () => {
layer.visible = !layer.visible;
});
}
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.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое Layer и зачем он нужен
Layer в Phaser — это игровой объект-контейнер, который может включать в себя другие объекты (спрайты, изображения, текст). Его ключевые особенности:
- Он сам является дочерним элементом Scene и управляет списком своих детей.
- Позволяет применять действия (например, скрытие, перемещение) сразу ко всем своим дочерним объектам.
- Может автоматически сортировать своих детей по значению свойства depth каждого объекта. Эта сортировка происходит на каждом кадре (preRender), что критически важно для динамически движущихся объектов.
В предоставленном примере создается один слой, в который помещаются все объекты сцены. Это позволяет легко управлять их видимостью одной командой и гарантирует правильный порядок отрисовки.
Создание Layer и наполнение его объектами
В методе create() сцены создается экземпляр Layer. Затем в цикле создается множество изображений овощей из атласа и добавляются в этот слой.
const layer = this.add.layer();
for (let i = 0; i < 1024; i++) {
let x = Phaser.Math.Between(100, 700);
let y = Phaser.Math.Between(100, 500);
let f = Phaser.Math.Between(1, 9);
const image = layer.add(this.make.image({ x, y, key: 'atlas', frame: 'veg0' + f }, false));
image.depth = image.y;
}
Обратите внимание на два важных момента:
1. Объект создается через фабрику this.make.image, но на сцену он попадает не через this.add, а через метод layer.add(). Второй аргумент false означает, что объект не должен добавляться в список обновления сцены, так как обновлением будет заниматься слой.
2. Сразу после создания объекту устанавливается свойство depth. В данном случае оно равно координате `y. Это основа для сортировки: объекты, расположенные ниже на экране (с большим значениемy), будут иметь большуюdepthи, следовательно, будут отрисованы поверх объектов с меньшейdepth` (если сорт включен).
Затем в этот же слой добавляются три спрайта гриба.
Динамическое обновление глубины
Сортировка внутри Layer работает, только если у его дочерних объектов меняется свойство depth. В статичной сцене достаточно установить depth один раз при создании. Но если объекты движутся, их глубина должна пересчитываться каждый кадр.
В методе update() примера три гриба движутся по разным траекториям. После расчета новых координат (`x,y) для каждого гриба немедленно пересчитывается егоdepth`.
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;
Здесь используется формула глубина = y + halfHeight. Зачем прибавляется половина высоты? Это небольшой, но важный трюк. Поскольку точка `yспрайта — это его верхний левый угол, а для восприятия глубины важна его середина (или "ноги"), добавлениеheight / 2` делает сортировку более естественной. Без этого грибы начинали бы перекрывать друг друга в моменты, когда их верхние края находятся на одной высоте, что выглядело бы некорректно.
Слой автоматически, на каждом кадре, перед отрисовкой отсортирует все 1024 овоща и 3 гриба по их актуальному значению depth.
Управление видимостью всего слоя
Одно из преимуществ использования Layer — возможность массовых операций. В примере показано, как по клику мыши можно скрыть или показать сразу все объекты слоя.
this.input.on('pointerdown', () => {
layer.visible = !layer.visible;
});
Установка свойства visible в false для слоя делает невидимыми все его дочерние объекты, независимо от их индивидуальных настроек видимости. Это гораздо эффективнее, чем перебирать все объекты вручную.
Что попробовать дальше
Использование Layer с включенной сортировкой по depth — это мощный и производительный способ управления порядком отрисовки в Phaser. Он особенно полезен для сцен с большим количеством динамических объектов, которые постоянно меняют свое положение на экране.
Для экспериментов попробуйте:
1. Изменить формулу расчета depth (например, использовать только `yилиx + y`).
2. Создать несколько слоев для разных групп объектов (например, слой фона, слой игрока, слой эффектов) и управлять их глубиной относительно друг друга через layer.depth.
3. Добавить статичный объект (например, UI-панель) не в слой, а напрямую на сцену, и посмотреть, как он взаимодействует с отсортированными объектами слоя.
