О чем этот пример
Обработка событий ввода — один из столпов интерактивности в играх на Phaser. Однако у новичков часто возникает путаница: почему события срабатывают в разном порядке или как временно отключить взаимодействие с объектом? В этой статье на примере конкретного кода мы разберем, как работает система событий ввода Phaser, как избежать распространенных ошибок и как правильно управлять интерактивностью спрайтов.
Версия 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('char', 'assets/pics/nayuki.png');
}
create ()
{
const sprite = this.add.sprite(400, 300, 'char');
sprite.setInteractive();
this.input.on(Phaser.Input.Events.POINTER_DOWN, () => {
console.log('SCENE POINTER_DOWN');
});
this.input.on(Phaser.Input.Events.POINTER_UP, () => {
console.log('SCENE POINTER_UP');
});
sprite.on(Phaser.Input.Events.POINTER_DOWN, () => {
console.log('>>>> SPRITE POINTER_DOWN');
});
sprite.on(Phaser.Input.Events.POINTER_UP, () => {
console.log('>>>> SPRITE POINTER_UP');
sprite.input.enabled = false;
console.log(sprite.input.enabled);
this.time.delayedCall(1000, () => {
sprite.input.enabled = true;
console.log("------------------------")
});
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Разбираем исходный код примера
В предоставленном примере создается сцена с одним интерактивным спрайтом. На сцену и на сам спрайт навешиваются обработчики событий POINTER_DOWN и POINTER_UP. В обработчике спрайта есть важная деталь: после события POINTER_UP интерактивность спрайта временно отключается на 1 секунду.
Давайте посмотрим на структуру сцены:
class Example extends Phaser.Scene {
constructor() {
super();
}
preload () {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('char', 'assets/pics/nayuki.png');
}
create () {
const sprite = this.add.sprite(400, 300, 'char');
sprite.setInteractive();
}
Иерархия событий ввода: сцена vs спрайт
Ключевой момент — понимание порядка срабатывания событий. В Phaser события ввода всплывают от целевого объекта (например, спрайта) к сцене. Это похоже на модель событий DOM в браузере.
В коде мы видим четыре обработчика:
// Обработчики на уровне сцены
this.input.on(Phaser.Input.Events.POINTER_DOWN, () => {
console.log('SCENE POINTER_DOWN');
});
this.input.on(Phaser.Input.Events.POINTER_UP, () => {
console.log('SCENE POINTER_UP');
});
// Обработчики на уровне спрайта
sprite.on(Phaser.Input.Events.POINTER_DOWN, () => {
console.log('>>>> SPRITE POINTER_DOWN');
});
sprite.on(Phaser.Input.Events.POINTER_UP, () => {
console.log('>>>> SPRITE POINTER_UP');
});
Если кликнуть по спрайту, в консоли сначала появится >>>> SPRITE POINTER_DOWN, а затем SCENE POINTER_DOWN. Аналогично для POINTER_UP. Это происходит потому, что событие сначала обрабатывается спрайтом (целевым объектом), а затем всплывает до сцены.
Управление интерактивностью: `sprite.input.enabled`
В примере используется важное свойство sprite.input.enabled. Оно позволяет динамически включать и отключать обработку ввода для конкретного игрового объекта.
Обратите внимание на этот фрагмент в обработчике POINTER_UP спрайта:
sprite.on(Phaser.Input.Events.POINTER_UP, () => {
console.log('>>>> SPRITE POINTER_UP');
// Отключаем интерактивность спрайта
sprite.input.enabled = false;
console.log(sprite.input.enabled); // Выведет: false
// Включаем обратно через 1 секунду
this.time.delayedCall(1000, () => {
sprite.input.enabled = true;
console.log("------------------------");
});
});
После клика по спрайту его свойство input.enabled устанавливается в false. Это означает, что в течение следующей секунды спрайт не будет реагировать на события мыши или касания. События будут проходить «сквозь» него и срабатывать только на уровне сцены. Через секунду таймер, созданный через this.time.delayedCall, снова включает интерактивность.
Это полезный паттерн для создания кнопок с «периодом восстановления» или предотвращения множественных быстрых кликов.
Почему это важно: предотвращение ошибок
Понимание этой механики помогает избежать распространенных ошибок.
1. **Дублирование логики:** Если вы навесили одинаковую логику и на спрайт, и на сцену, она выполнится дважды. Убедитесь, что обработка происходит на нужном уровне.
2. **Остановка всплытия:** В отличие от DOM, в Phaser нет стандартного метода вроде stopPropagation(). Если нужно, чтобы событие не доходило до сцены, не вешайте обработчик на сцену или обрабатывайте событие только на целевом объекте.
3. **Динамическое управление:** Свойство input.enabled — ваш инструмент для сложной логики. Например, можно отключать интерактивность у всех объектов в группе или включать её только при выполнении определённого условия.
Проверка состояния интерактивности:
if (sprite.input.enabled) {
// Спрайт готов принимать ввод
}
Практические рекомендации
1. **Чёткое разделение:** Используйте обработку на уровне спрайта для логики, связанной непосредственно с ним (например, анимация нажатия кнопки). Логику уровня игры (переход между состояниями, нанесение урона) лучше размещать на уровне сцены или в менеджерах.
2. **Используйте setInteractive с параметрами:** Метод setInteractive может принимать хитбокс. Это повышает точность.
// Делаем спрайт интерактивным, используя его физическое тело как хитбокс
sprite.setInteractive({ hitArea: sprite.getBounds(), useHandCursor: true });
3. **Альтернатива delayedCall:** Для более сложных сценариев отложенного включения можно использовать состояния сцены или асинхронные функции.
async enableSpriteWithDelay(sprite, delay) {
await this.time.delayedCall(delay);
sprite.input.enabled = true;
}
Что попробовать дальше
Главное правило — помнить об иерархии событий в Phaser: они всплывают от спрайта к сцене. Динамическое управление свойством input.enabled — мощный инструмент для контроля интерактивности. Для экспериментов попробуйте создать интерфейс, где кнопка отключается после нажатия и включается только после завершения какой-либо анимации или сетевого запроса. Или реализуйте систему, где клик по одному объекту временно отключает все остальные в его группе.
