О чем этот пример
Работа с событиями — фундамент динамических игр в Phaser. Однако неконтролируемые подписки могут привести к утечкам памяти и неожиданному поведению. На примере простого события мы разберем, как корректно удалять обработчики, когда они больше не нужны, используя метод `off()` и понимая его важные нюансы. Этот навык критически важен для создания стабильных и эффективных игр.
Версия 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('plush', 'assets/pics/profil-sad-plush.png');
}
create ()
{
let i = 0;
this.events.on('addImage', function () {
var x = Phaser.Math.Between(100, 700);
var y = Phaser.Math.Between(100, 500);
this.add.image(x, y, 'plush');
i++;
if (i === 5)
{
// Remove the event after 5 calls.
// By not providing a handler it will clear ALL 'addImage' listeners.
this.events.off('addImage');
}
}, this);
// Emit the event 10 times
for (var e = 0; e < 10; e++)
{
this.events.emit('addImage');
}
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Сценарий: условное удаление обработчика
В примере создается событие 'addImage', которое должно сработать 10 раз. Однако задача — остановить обработку после пятого вызова. Для этого используется счетчик `i`. Ключевой момент: удаление подписки происходит не снаружи, а внутри самого обработчика, на основе внутренней логики сцены.
let i = 0;
this.events.on('addImage', function () {
// ... логика обработки ...
i++;
if (i === 5)
{
this.events.off('addImage');
}
}, this);
Сила и опасность метода `off('eventName')`
Метод this.events.off('addImage') в данном контексте делает больше, чем кажется. Поскольку при вызове не передан конкретный callback-обработчик, он удаляет **все** функции, подписанные на событие 'addImage'. Это мощный, но потенциально опасный инструмент.
// Удаляет ВСЕ обработчики для события 'addImage'
this.events.off('addImage');
В нашем примере это безопасно, так как обработчик только один. Но в проектах, где на одно событие могут реагировать несколько систем (например, звук, анимация, логика), такой вызов «сломает» их все. Для точечного удаления нужно сохранять ссылку на функцию и передавать ее в off().
Почему событие все равно emit'ится 10 раз?
Важно понимать последовательность выполнения. Цикл for инициирует 10 последовательных выбросов события 'addImage'.
for (var e = 0; e < 10; e++)
{
this.events.emit('addImage');
}
Обработчик удаляется только при **пятом** вызове, в момент, когда `iстановится равен 5. Однако вызовыemit` не ставятся в очередь — они выполняются мгновенно. Поэтому первые пять вызовов успешно обработаются и создадут изображения, а последующие пять просто не найдут активных подписчиков и будут проигнорированы. В консоли ошибок не будет.
Лучшие практики: именованные функции и точный контроль
Для полного контроля и избежания побочных эффектов используйте именованные функции. Это позволяет удалять конкретный обработчик, не затрагивая другие.
create () {
const onAddImageHandler = function () {
var x = Phaser.Math.Between(100, 700);
var y = Phaser.Math.Between(100, 500);
this.add.image(x, y, 'plush');
};
// Подписываемся, передавая именованную функцию
this.events.on('addImage', onAddImageHandler, this);
// ... позже, в другом месте кода ...
// Удаляем только этот конкретный обработчик
this.events.off('addImage', onAddImageHandler);
}
Это особенно важно в сложных сценах, где события используются для коммуникации между разными плагинами или модулями игры.
Что попробовать дальше
Умение управлять жизненным циклом обработчиков событий — признак качественного кода на Phaser. Всегда очищайте подписки, когда они становятся ненужными, чтобы избежать утечек памяти. Для экспериментов попробуйте: создать несколько разных обработчиков на одно событие и удалять их выборочно; или отписываться от события в другом сценарии, например, по таймеру или коллизии. Помните, что off() без callback — это «выключение всего», используйте его осознанно.
