О чем этот пример
Система событий Phaser — мощный инструмент для организации взаимодействия между частями игры. Однако без правильного управления подписками можно столкнуться с утечками памяти и неожиданным поведением, когда обработчики срабатывают тогда, когда уже не должны. Этот пример показывает, как явно отключать обработчик события изнутри самого себя — ключевая техника для создания чистого и контролируемого кода. Умение вовремя "выключать" слушатели особенно полезно при создании одноразовых анимаций, диалоговых окон, временных бонусов или при уничтожении игровых объектов, чтобы избежать накопления неиспользуемых функций в памяти.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
let i = 0;
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('plush', 'assets/pics/profil-sad-plush.png');
}
create ()
{
// Call this handler.
// Within the handler it will disable itself after 5 calls.
this.events.on('addImage', this.handler, this);
// Emit the event 10 times
for (var i = 0; i < 10; i++)
{
this.events.emit('addImage');
}
}
handler ()
{
const x = Phaser.Math.Between(100, 700);
const y = Phaser.Math.Between(100, 500);
this.add.image(x, y, 'plush');
i++;
if (i === 5)
{
this.events.off('addImage', this.handler);
}
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Суть примера: событие, которое само себя отключает
В этом примере создаётся пользовательское событие addImage. Обработчик этого события (handler) не только выполняет свою основную функцию (добавляет изображение на сцену), но и содержит логику для собственного отключения после пятого вызова.
Это демонстрирует важный паттерн: обработчик события может управлять своим собственным жизненным циклом. Такой подход часто используется, когда реакция на событие должна происходить ограниченное количество раз.
this.events.on('addImage', this.handler, this);
Разбор метода-обработчика
Ключевая логика заключена в методе handler. Давайте разберём его по шагам.
1. **Добавление спрайта:** Сначала обработчик размещает изображение 'plush' в случайной позиции на сцене, используя `Phaser.Math.Between`.
2. **Счётчик вызовов:** Глобальная переменная `i` увеличивается с каждым вызовом. Использование глобальной переменной здесь — упрощение для примера; в реальном проекте счётчик лучше хранить в свойствах сцены или объекта.
3. **Условие отписки:** Когда счётчик достигает 5, срабатывает условие. Здесь происходит самое важное: вызов `this.events.off`.
handler ()
{
const x = Phaser.Math.Between(100, 700);
const y = Phaser.Math.Between(100, 500);
this.add.image(x, y, 'plush');
i++;
if (i === 5)
{
this.events.off('addImage', this.handler);
}
}
Метод off принимает имя события и ссылку на функцию-обработчик, которую нужно удалить из списка слушателей. После этого вызова событие addImage больше не будет вызывать этот конкретный handler.
Что происходит в create()?
Сцена создаёт подписку на событие и сразу же инициирует его вызов 10 раз подряд в цикле.
Важный момент: хотя событие испускается 10 раз (emit вызывается 10 раз), обработчик сработает только 5 раз. После пятого срабатывания он сам себя отключает, и последующие 5 вызовов события addImage просто игнорируются системой событий, так как для него больше не зарегистрировано слушателей.
create ()
{
this.events.on('addImage', this.handler, this);
for (var i = 0; i < 10; i++)
{
this.events.emit('addImage');
}
}
Это наглядно показывает, что emit — это просто сигнал. Будет ли на него реакция, зависит от наличия активных слушателей (on).
Практические сценарии применения
Техника отписки внутри обработчика полезна во многих игровых ситуациях:
* **Лимитированные действия:** Игрок может использовать особую способность только 3 раза за уровень. После третьего использования обработчик события нажатия на кнопку способности отключается.
* **Временные модификаторы:** Бонус скорости действует 10 секунд. При его получении запускается таймер, и по его окончании обработчик, применяющий множитель скорости к персонажу, отключается через events.off.
* **Одноразовые анимации и диалоги:** Отображение кат-сцены или всплывающей подсказки. После завершения анимации или закрытия окна слушатель связанных событий (например, клика для продолжения) должен быть удалён.
Главное правило: если вы добавляете слушатель с помощью on, всегда продумывайте момент, когда его нужно убрать с помощью off. Это предотвратит "зомби"-обработчики, которые продолжают реагировать, хотя их объект-владелец уже уничтожен.
Что попробовать дальше
Управление подписками на события — признак зрелого кода на Phaser. Использование events.off для удаления обработчиков предотвращает утечки памяти и логические ошибки. Пример с самоотключающимся обработчиком — отличная база для более сложной логики жизненного цикла.
Поэкспериментируйте: попробуйте создать обработчик, который отписывается не по счётчику, а по случайному шансу или в зависимости от состояния игры. Или реализуйте систему «одноразовых» событий, где объект может прослушать событие только один раз, после чего подписка автоматически сбрасывается.
