О чем этот пример
В Phaser игровые объекты (Image, Sprite и другие) могут быть не просто статичной графикой, а активными участниками логики сцены. Они умеют генерировать и обрабатывать пользовательские события, что открывает путь к созданию чистого, слабосвязанного кода. В этой статье мы разберем, как использовать встроенную систему событий для взаимодействия между объектами, что особенно полезно для управления анимациями, реакцией на игровые действия и организации сложных поведений без спагетти-кода.
Версия 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 ()
{
// All Game Objects can emit and receive events
const plush1 = this.add.image(400, 300, 'plush');
// If the plush1 object emits the turnRed event, it will change itself to tint red
plush1.on('turnRed', this.handler);
// Emit the event and pass over a reference to itself
plush1.emit('turnRed', plush1);
}
handler (gameObject)
{
gameObject.setTint(0xff0000);
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Зачем объектам свои события?
Представьте, что ваш игровой персонаж подбирает предмет. Вместо того чтобы из метода подбора напрямую вызывать десяток функций для обновления интерфейса, звука и статистики, персонаж может просто испустить событие itemPicked. Все заинтересованные системы (UI, аудио, логика) подпишутся на него и отреагируют самостоятельно. Такой подход, основанный на событиях, делает код модульным и расширяемым. В примере мы увидим, как один объект может вызвать событие и обработать его самостоятельно, что является фундаментом для более сложных взаимодействий.
Каждый объект, унаследованный от Phaser.GameObjects.GameObject, имеет методы on() (подписаться), once() (подписаться один раз) и emit() (испустить).
Создание объекта и подписка на событие
В методе create() сцены мы создаем изображение и сразу же настраиваем его реакцию на пользовательское событие с помощью метода on().
const plush1 = this.add.image(400, 300, 'plush');
plush1.on('turnRed', this.handler);
Первая строка создает объект Image с текстурой 'plush' и помещает его в центр экрана (координаты 400x300). Вторая строка — ключевая. Она говорит объекту plush1: "Когда на тебе произойдет событие с именем 'turnRed', выполни функцию this.handler". Имя события — произвольная строка, вы можете называть их как угодно (takeDamage, playAnimation, destroy).
Инициация события и передача данных
Подписка сама по себе ничего не делает. Чтобы запустить процесс, событие нужно инициировать (эмитировать). Это делается методом emit().
plush1.emit('turnRed', plush1);
Здесь объект plush1 испускает событие 'turnRed'. Вторым аргументом в метод emit передается сам объект plush1. Этот аргумент станет параметром для функции-обработчика handler. Мы передаем объект явно, чтобы обработчик знал, с каким именно объектом нужно работать.
Функция-обработчик и изменение состояния
Функция-обработчик получает переданные при вызове emit() данные и выполняет нужные действия над объектом.
handler (gameObject) {
gameObject.setTint(0xff0000);
}
В данном примере обработчик handler принимает один параметр gameObject (это и есть наш plush1, переданный вторым аргументом в emit). Затем он вызывает метод setTint() этого объекта, чтобы установить ему красный оттенок (0xff0000 — это hex-код красного цвета). После выполнения кода объект plush1 на экране окрасится в красный цвет.
Практическое применение: от простого к сложному
Показанный пример — минимальная демонстрация. На практике это мощный инструмент.
1. **Один источник, много подписчиков:** Объект-пуля при создании может испустить событие shotFired. На него могут подписаться система звука (проиграть выстрел), система интерфейса (обновить счетчик патронов) и контроллер камеры (добавить тряску).
// Где-то в логике выстрела
bullet.emit('shotFired', bullet, shooter);
2. **Внешние обработчики:** Обработчик не обязан быть методом сцены. Это может быть метод отдельного класса (например, EffectsManager), что еще лучше разделяет ответственность.
// В менеджере эффектов
effectsManager.onExplosion = function(gameObject) {
// Создать анимацию взрыва на месте gameObject
};
// Подписка
bomb.on('explode', effectsManager.onExplosion);
Что попробовать дальше
События игровых объектов в Phaser — это элегантный способ наладить коммуникацию между различными частями вашей игры без жестких зависимостей. Они делают код чище и облегчают его отладку и расширение. Для экспериментов попробуйте: создать два объекта и сделать так, чтобы событие, испущенное первым, меняло цвет второго; реализовать простую систему сообщений, где объект Player испускает событие healthChanged, а UI обновляет шкалу здоровья; или использовать once() для одноразовых событий, например, воспроизведения уникальной анимации при первом клике.
