О чем этот пример
Частая проблема при разработке гибридных интерфейсов в Phaser — это непредсказуемое перекрытие событий между игровыми объектами и HTML-элементами, добавленными через `add.dom`. Эта статья наглядно покажет, почему клики могут "пролетать" сквозь ваш UI, и даст практические решения для управления их обработкой. Понимание этих механизмов критично для создания отзывчивых игр с веб-формами или сложным интерфейсом.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
new Phaser.Game({
type: Phaser.AUTO,
width: 300,
height: 300,
backgroundColor: 0x2d2d88,
scene: {
create () {
let count = 0
const text = this.add.text(0, 0, 'Click blue')
const phaserRedRectangle = this.add.rectangle(50, 50, 150, 150, 0xFF4444).setOrigin(0).setAlpha(0.5)
phaserRedRectangle.setInteractive()
phaserRedRectangle.on('pointerdown', () => {
count++;
text.setText(`Clicked ${count} times`)
console.log('clicked', count)
})
const element = this.add.dom(100, 100, 'div', 'width: 150px; height: 150px; background: #0099FF; opacity: 0.5;');
}
},
dom: {
createContainer: true
},
parent: 'phaser-example',
// input: { windowEvents: false, touch: { capture: true } }
})
Проблема: невидимый барьер для кликов
В приведённом примере создаётся синий полупрозрачный DOM-элемент (div) и красный полупрозрачный игровой прямоугольник (rectangle). Оба объекта имеют одинаковые размеры и частично перекрываются.
Красный прямоугольник является интерактивным — на него назначен слушатель события pointerdown. Однако, если кликнуть в область пересечения этих двух фигур, событие не сработает, и счётчик не увеличится. В консоли также не появится сообщение. Это происходит потому, что DOM-элемент, добавленный поверх канваса, перехватывает все события мыши, не позволяя им "дойти" до игровых объектов Phaser.
const phaserRedRectangle = this.add.rectangle(50, 50, 150, 150, 0xFF4444).setOrigin(0).setAlpha(0.5)
phaserRedRectangle.setInteractive()
phaserRedRectangle.on('pointerdown', () => {
count++;
text.setText(`Clicked ${count} times`)
console.log('clicked', count)
})
const element = this.add.dom(100, 100, 'div', 'width: 150px; height: 150px; background: #0099FF; opacity: 0.5;');
Причина: порядок рендеринга и система событий
Phaser использует отдельный HTML-контейнер для DOM-элементов, который по умолчанию располагается поверх основного canvas, где отрисовываются все игровые объекты (спрайты, прямоугольники, текст). Это архитектурное решение.
1. **Порядок слоёв:** Сначала рендерится canvas с игрой, затем поверх него — контейнер для DOM. Поэтому DOM-элементы всегда визуально поверх игровых объектов.
2. **Всплытие событий:** События мыши (pointer events) в браузере всегда возникают на самом верхнем элементе в иерархии. Клик попадает сначала в DOM-div, и стандартное поведение браузера не передаёт это событие "ниже", на canvas.
Ключевая конфигурация, которая создаёт контейнер для DOM-элементов, находится в объекте конфигурации игры:
dom: {
createContainer: true
}
Решение 1: Отключение событий у DOM-элемента
Самый простой способ — сделать DOM-элемент "невидимым" для событий мыши с помощью CSS-свойства pointer-events. Это можно задать прямо при создании элемента через add.dom.
const element = this.add.dom(100, 100, 'div', 'width: 150px; height: 150px; background: #0099FF; opacity: 0.5; pointer-events: none;');
После этого клики будут беспрепятственно проходить сквозь синий div и активировать интерактивный красный прямоугольник под ним. Это идеально подходит для статичных или анимированных декоративных HTML-элементов, которые не должны реагировать на ввод.
Решение 2: Обработка событий на самом DOM-элементе
Если ваш DOM-элемент является частью интерфейса (например, кнопка или форма), и ему тоже нужна реакция на клики, обрабатывайте события непосредственно на нём. У объекта, возвращаемого add.dom, есть свойство node, содержащее ссылку на нативный HTML-элемент.
const element = this.add.dom(100, 100, 'div', 'width: 150px; height: 150px; background: #0099FF; opacity: 0.5;');
element.node.addEventListener('click', () => {
count++;
text.setText(`Clicked from DOM: ${count} times`);
console.log('DOM clicked', count);
});
В этом случае оба объекта (красный прямоугольник Phaser и синий div) будут иметь свои независимые обработчики. Однако клик в области пересечения вызовет только событие у DOM-элемента, так как он находится сверху.
Решение 3: Тонкая настройка ввода (Input Configuration)
В закомментированной части исходного кода показана продвинутая настройка. Раскомментирование параметра touch.capture может изменить поведение в мобильных браузерах.
input: { windowEvents: false, touch: { capture: true } }
* windowEvents: false — отключает автоматическую привязку событий Phaser к глобальному объекту window. Это полезно, если игра встроена в сложную страницу.
* touch: { capture: true } — включает режим capture для touch-событий. Это может повлиять на порядок обработки событий между Phaser и другими элементами страницы в мобильных браузерах.
Эти настройки не решат базовую проблему перекрытия DOM и canvas, но важны для управления вводом в специфичных окружениях.
Что попробовать дальше
Ключ к управлению вводом в гибридной среде Phaser — понимание слоёв: DOM-элементы всегда поверх canvas. Для декоративных элементов используйте pointer-events: none. Для интерактивных — обрабатывайте события напрямую на DOM-узле. Поэкспериментируйте: создайте интерфейс, где кнопка в DOM меняет цвет спрайта в canvas, или реализуйте сложную форму входа, которая взаимодействует с игровым миром, корректно управляя фокусом событий.
