О чем этот пример
Система событий Phaser — это гибкий инструмент для организации взаимодействия между частями игры. По умолчанию для идентификации событий используются строки, что может привести к конфликтам имён в больших проектах или при использовании сторонних плагинов. В этой статье мы рассмотрим, как использовать ES6 Символы (Symbols) в качестве уникальных ключей для событий. Этот подход гарантирует абсолютную уникальность события, предотвращает случайные коллизии и делает код более надёжным и предсказуемым, особенно в сложных сценах с множеством обработчиков.
Версия 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('girl', 'assets/pics/manga-girl.png');
this.load.image('plush', 'assets/pics/profil-sad-plush.png');
}
create ()
{
// Our ES6 Symbols we'll use for both the handler and event emitter
const plushSymbol = Symbol();
const girlSymbol = Symbol();
// This handler will only be called once, no matter how many times the event fires
this.events.once(plushSymbol, this.addPlushHandler, this);
this.events.once(girlSymbol, this.addGirlHandler, this);
this.events.emit(girlSymbol);
this.events.emit(girlSymbol);
this.events.emit(girlSymbol);
this.events.emit(plushSymbol);
this.events.emit(plushSymbol);
this.events.emit(plushSymbol);
}
addPlushHandler ()
{
let x = Phaser.Math.Between(100, 700);
let y = Phaser.Math.Between(0, 300);
this.add.image(x, y, 'plush');
}
addGirlHandler ()
{
let x = Phaser.Math.Between(100, 700);
let y = Phaser.Math.Between(300, 600);
this.add.image(x, y, 'girl');
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
let game = new Phaser.Game(config);
Проблема строковых идентификаторов событий
Традиционно в Phaser события регистрируются и испускаются с использованием строковых ключей, например, 'player_hit' или 'collect_coin'. Этот подход прост, но имеет скрытый риск: разные модули игры или плагины могут случайно использовать одинаковые имена для разных событий. Это приводит к трудноотлавливаемым ошибкам, когда обработчик реагирует на «чужое» событие.
Символы, введённые в стандарт ES6, — это примитивный тип данных, гарантирующий уникальность. Каждый символ, созданный с помощью Symbol(), является уникальным, даже если его описание совпадает с другим. Это свойство делает их идеальными кандидатами для использования в качестве ключей в системах событий.
Создание и использование символов для событий
В примере кода мы создаём два уникальных символа для двух разных типов событий. Эти символы затем используются как для регистрации обработчиков, так и для испускания событий через this.events.
const plushSymbol = Symbol();
const girlSymbol = Symbol();
Обратите внимание, что обработчики регистрируются с помощью метода .once(). Это означает, что каждый обработчик будет вызван ровно один раз, независимо от того, сколько раз было испущено соответствующее событие. После вызова обработчик автоматически удаляется.
this.events.once(plushSymbol, this.addPlushHandler, this);
this.events.once(girlSymbol, this.addGirlHandler, this);
Испускание событий и работа обработчиков
После регистрации обработчиков код многократно испускает оба события. Однако, благодаря использованию .once(), каждый обработчик сработает только один раз.
this.events.emit(girlSymbol);
this.events.emit(girlSymbol);
this.events.emit(girlSymbol);
this.events.emit(plushSymbol);
this.events.emit(plushSymbol);
this.events.emit(plushSymbol);
Методы-обработчики addPlushHandler и addGirlHandler добавляют изображения на сцену в случайных позициях, используя Phaser.Math.Between. Разные диапазоны координат `y` для каждого обработчика гарантируют, что изображения появятся в разных частях экрана.
addPlushHandler ()
{
let x = Phaser.Math.Between(100, 700);
let y = Phaser.Math.Between(0, 300);
this.add.image(x, y, 'plush');
}
Практические преимущества подхода
1. **Абсолютная уникальность**: Символ, созданный в одном модуле, никогда не будет конфликтовать с символом из другого модуля, даже если они созданы с одинаковым описанием. Это решает проблему коллизий имён событий. 2. **Контроль доступа**: Символ, объявленный внутри модуля (например, в замыкании или классе), является приватным по своей природе. Внешний код не может случайно или намеренно испустить это событие, не имея доступа к самому символу. Это усиливает инкапсуляцию. 3. **Ясность намерений**: Использование символов явно указывает на то, что событие является внутренним, уникальным и предназначено для конкретного, чётко определённого взаимодействия.
Такой подход особенно полезен для организации сложной внутренней логики сцены или игрового объекта, где нужно гарантировать, что обработчик сработает только на конкретный, строго определённый вызов.
Что попробовать дальше
Использование ES6 Символов в качестве ключей событий в Phaser — это мощный паттерн для повышения надёжности и структурированности кода. Он устраняет классическую проблему конфликта имён и способствует созданию хорошо изолированных модулей.
**Идеи для экспериментов:**
1. Попробуйте использовать символы с описанием (Symbol('playerJumped')) для отладки, помня, что уникальность от описания не зависит.
2. Создайте менеджер событий на основе символов для сложного игрового объекта (например, персонажа с десятками анимаций и состояний), чтобы избежать путаницы в его внутренней логике.
3. Экспериментируйте с комбинацией .once() и обычного .on() для символов, чтобы создавать как одноразовые, так и постоянные обработчики уникальных событий.
