О чем этот пример
Казалось бы, обработка кликов — одна из самых базовых задач в разработке игр. Однако с выходом iOS 16 многие разработчики столкнулись с неожиданной проблемой: на некоторых устройствах перестали корректно работать обычные обработчики кликов в Phaser. Эта статья объяснит, в чем суть проблемы на примере простого счетчика, и покажет, как ее надежно обойти, чтобы ваша игра работала у всех пользователей, независимо от версии iOS. Понимание этой особенности критически важно для любого, кто разрабатывает или планирует выпускать игры под мобильные платформы. Мы разберем исходный код примера, увидим его уязвимое место и предложим практическое, работающее решение.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
const d = this.add.text(32, 32, 'Click It: 0');
let c = 0;
this.input.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
})
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Анализ проблемы: почему клики пропадают?
Представленный код создает простейший счетчик кликов. При каждом нажатии указателем (мышь или палец) значение переменной увеличивается и обновляется текст на экране.
this.input.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
})
Однако, начиная с iOS 16, в браузере Safari были внесены изменения, затрагивающие обработку событий касания. В некоторых сценариях, особенно на страницах без стандартных жестов прокрутки или если элемент не является интерактивным по умолчанию (например, обычный Text), событие pointerdown может не сработать при первом касании. Это поведение браузера, направленное на оптимизацию распознавания жестов, конфликтует с ожиданиями игрового движка.
Решение: делаем игровые объекты интерактивными
Самое прямое и надежное решение — явно указать браузеру, что с нашим объектом можно взаимодействовать. В Phaser для этого у игровых объектов есть свойство setInteractive(). После его вызова объект начинает корректно принимать события ввода.
Давайте модифицируем наш пример. Мы сделаем интерактивным не текст, а всю сцену (или игровую область). Это гарантирует, что клики будут обрабатываться в любом месте экрана.
create ()
{
// Делаем саму сцену интерактивной областью
this.input.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
});
}
Обратите внимание: в данном конкретном примере обработчик вешается на this.input, который по умолчанию слушает события на всей canvas-области. Проблема возникает, когда разработчики пытаются вешать обработчики на конкретные неинтерактивные объекты (как `dв исходнике) без вызоваsetInteractive(). Для исправления примера достаточно просто переместить обработчик с объектаdнаthis.input`, как показано выше. Если же логика требует клика именно по тексту, тогда нужно сделать его интерактивным:
const d = this.add.text(32, 32, 'Click It: 0').setInteractive();
d.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
});
Универсальный подход: конфигурация сцены
Для новых проектов лучшей практикой будет предварительная настройка ввода на уровне конфигурации сцены или игры. Это избавит от потенциальных проблем в будущем.
В методе init() или create() вашей сцены можно один раз настроить нужное поведение.
create ()
{
// Активируем управление для мыши и касаний
this.input.mouse.enabled = true;
// Этот метод также может помочь в некоторых случаях
this.input.setPollAlways();
// Дальше идет остальная логика создания объектов и обработчиков
const d = this.add.text(32, 32, 'Click It: 0');
let c = 0;
this.input.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
});
}
Вызов this.input.setPollAlways() заставляет систему ввода Phaser опрашивать состояния постоянно, что может помочь на мобильных устройствах с "ленивой" обработкой событий.
Итоговая, надежная версия кода
Вот полный, исправленный код примера, который будет стабильно работать на iOS 16 и других платформах. Основное изменение — обработчик события pointerdown теперь привязан ко всей области ввода сцены (this.input), а не к конкретному текстовому объекту.
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
const d = this.add.text(32, 32, 'Click It: 0');
let c = 0;
// Обработчик вешается на глобальный Input Manager сцены
this.input.on('pointerdown', () => {
c++;
d.setText('Click It: ' + c);
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Проблема с кликами на iOS 16 — яркий пример того, как изменения в одной части экосистемы (браузер Safari) могут влиять на, казалось бы, изолированные игровые проекты. Ключевой вывод: для надежной обработки ввода на мобильных устройствах всегда используйте глобальные обработчики this.input или явно делайте объекты интерактивными с помощью setInteractive().
**Идеи для экспериментов:**
1. Попробуйте применить setInteractive() к другим типам объектов (спрайтам, графике) и сравните поведение.
2. Создайте сложный интерфейс с несколькими кнопками-текстами. Сделайте их интерактивными и добавьте разные эффекты (изменение цвета) по событию pointerover.
3. Исследуйте разницу между событиями pointerdown, pointerup и gameobjectdown для интерактивных объектов.
