О чем этот пример
При разработке игр на Phaser часто возникает необходимость управлять группами объектов. Слои (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(
"elephant", "assets/sprites/elephant.png"
);
}
create() {
const elephant1 = this.add.sprite(100, 100, "elephant");
const elephant2 = this.add.sprite(130, 120, "elephant");
elephant1.depth = 2;
elephant1.setInteractive({
pixelPerfect: false,
useHandCursor: true
});
elephant2.depth = 3;
elephant2.setInteractive({
pixelPerfect: false,
useHandCursor: true
});
elephant1.on('pointerdown', function (){
alert("elephant 1");
});
elephant2.on('pointerdown', function (){
alert("elephant 2");
});
const layer = this.add.layer();
// remove the line below and the alerts work as expected
layer.add([elephant1, elephant2]);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: "#2d2d2d",
parent: "phaser-example",
scene: Example
};
const game = new Phaser.Game(config);
Проблема: клики перестают работать
В представленном примере создаются два спрайта слона, каждому назначается интерактивность и обработчик события pointerdown. По отдельности они работают корректно. Однако при добавлении этих спрайтов в слой с помощью layer.add(), клики по ним больше не вызывают ожидаемых alert-сообщений.
Код настройки интерактивности для спрайтов выглядит так:
elephant1.setInteractive({
pixelPerfect: false,
useHandCursor: true
});
elephant1.on('pointerdown', function (){
alert("elephant 1");
});
Парадокс в том, что спрайты по-прежнему отображаются, их свойства depth установлены, но события мыши «проходят сквозь них».
Причина: приоритет обработки ввода
В Phaser объекты Layer по умолчанию также являются интерактивными игровыми объектами (Game Objects). Когда слой добавляется на сцену (даже без явного вызова setInteractive), он создает свою собственную хит-зону (hit area), которая по умолчанию может покрывать всю свою область.
Ключевой момент: система ввода Phaser обрабатывает события от объектов, находящихся **выше** в порядке отрисовки (с учетом depth). В данном случае, оба спрайта добавлены в слой. Сам слой, как родительский контейнер, получает свой собственный depth и может оказаться в иерархии обработки событий таким образом, что он «перехватывает» клик раньше, чем его дочерние спрайты. Если у слоя нет своих обработчиков событий, клик просто игнорируется.
Добавление объектов в слой:
const layer = this.add.layer();
layer.add([elephant1, elephant2]);
Решение: отключение интерактивности слоя
Самое простое и прямое решение — явно отключить интерактивность для самого слоя. Это можно сделать с помощью метода setInteractive с аргументом null или false, либо установив свойство input после создания.
Вот как это исправить в коде примера:
const layer = this.add.layer();
layer.setInteractive(false); // Или layer.input = null;
layer.add([elephant1, elephant2]);
После этого события мыши будут корректно доставляться до интерактивных дочерних спрайтов elephant1 и elephant2, и alert-сообщения снова появятся.
Важно: если вам нужна интерактивность на самом слое (например, для фонового клика), вам следует назначить ему обработчик события pointerdown и, возможно, управлять всплытием событий вручную.
Альтернатива: работа без слоя
Если функциональность слоя в вашем случае ограничивается только группировкой объектов для управления depth, и интерактивность критически важна, рассмотрите альтернативы. Вы можете управлять глубиной объектов индивидуально или использовать другие методы группировки, которые не затрагивают обработку ввода.
Например, порядок отрисовки в данном примере уже управляется через свойство depth спрайтов:
elephant1.depth = 2;
elephant2.depth = 3;
Если убрать строку с добавлением в слой, интерактивность работает, а порядок отрисовки (где elephant2 с глубиной 3 будет поверх elephant1) сохраняется. Слои нужны для более сложных операций над группой, таких как массовое перемещение, видимость или отрисовка.
Что попробовать дальше
Добавление интерактивных объектов в слой Phaser может неожиданно отключить их реакцию на ввод из-за того, что сам слой становится интерактивным контейнером. Решение простое: явно отключите интерактивность слоя через layer.setInteractive(false). Для экспериментов попробуйте
- Назначить обработчик события самому слою и посмотреть, как события распределяются между родителем и детьми
- Использовать
pixelPerfect: trueдля спрайтов в сочетании со слоем - Создать несколько слоев с разной интерактивностью и проверить их взаимодействие
