О чем этот пример

В 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() для одноразовых событий, например, воспроизведения уникальной анимации при первом клике.