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

При разработке игр на Phaser часто возникает необходимость обмениваться данными между разными сценами. Например, счёт игрока из игровой сцены должен быть доступен в сцене интерфейса (HUD) или меню. Phaser предоставляет для этого удобный инструмент — глобальный реестр данных (`Registry`). В этой статье мы разберём, как настроить реактивное обновление данных между сценами, используя события реестра. Этот подход позволяет создавать чистую архитектуру без жёсткой связности сцен, что особенно полезно для масштабируемых проектов.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneA' });

        this.score = 0;
    }

    create ()
    {
        //  Store the score in the Registry
        this.registry.set('score', this.score);

        //  Update the score every 500ms
        this.time.addEvent({ delay: 500, callback: this.onEvent, callbackScope: this, loop: true });
    }

    onEvent ()
    {
        this.score++;

        this.registry.set('score', this.score);
    }
}

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneB', active: true });

        this.text;
    }

    create ()
    {
        this.text = this.add.text(100, 100, 'Monitoring Registry');

        //  Check the Registry and hit our callback every time the 'score' value is updated
        this.registry.events.on('changedata', this.updateScore, this);
    }

    updateScore (parent, key, data)
    {
        this.text.setText(`Score: ${data}`);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: [ SceneA, SceneB ]
};

const game = new Phaser.Game(config);

Что такое Registry?

Registry — это глобальное хранилище данных, доступное из любой сцены игры. Его можно представить как общую «корзину» для ключей и значений, где ключ — это строка, а значение — любой тип данных.

Основные преимущества: * **Глобальный доступ:** данные доступны из любой сцены, не требуя прямых ссылок. * **Реактивность:** можно подписаться на события изменения конкретных данных. * **Простота:** API интуитивно понятен и состоит из нескольких основных методов.

Реестр автоматически создаётся экземпляром игры и доступен через this.registry внутри любой сцены.

Сцена A: Отправка данных

Первая сцена (SceneA) отвечает за генерацию данных — в нашем случае это счёт, который увеличивается каждые 500 миллисекунд.

В конструкторе мы инициализируем счёт (this.score = 0). В методе create() происходит две важные вещи: 1. Начальное значение счёта записывается в реестр под ключом 'score'. 2. Запускается таймер, который вызывает функцию обновления с заданным интервалом.

class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneA' });
        this.score = 0;
    }

    create ()
    {
        //  Записываем начальный счёт в реестр
        this.registry.set('score', this.score);

        //  Создаём циклическое событие для обновления счёта каждые 500мс
        this.time.addEvent({ delay: 500, callback: this.onEvent, callbackScope: this, loop: true });
    }

    onEvent ()
    {
        this.score++;
        //  Обновляем значение в реестре
        this.registry.set('score', this.score);
    }
}

Ключевой метод здесь — this.registry.set('score', this.score). Каждый его вызов не только сохраняет новое значение, но и генерирует событие, которое могут «услышать» другие сцены.

Сцена B: Получение данных

Вторая сцена (SceneB) является потребителем данных. Её задача — отображать актуальное значение счёта и обновлять этот текст при каждом изменении данных в реестре.

В методе create() мы создаём текстовый объект и подписываемся на события реестра.

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'sceneB', active: true });
        this.text;
    }

    create ()
    {
        this.text = this.add.text(100, 100, 'Monitoring Registry');

        //  Подписываемся на событие изменения данных в реестре
        this.registry.events.on('changedata', this.updateScore, this);
    }

    updateScore (parent, key, data)
    {
        this.text.setText(`Score: ${data}`);
    }
}
Обратите внимание на строку `this.registry.events.on('changedata', this.updateScore, this)`. Здесь:
*   `'changedata'` — тип события, которое срабатывает при любом вызове `registry.set()`.
*   `this.updateScore` — функция-обработчик, которая будет вызвана.
*   Последний `this` задаёт контекст вызова для обработчика.

Функция updateScore принимает три параметра. В данном примере нам важен последний — data, который содержит новое значение, установленное методом set().

Настройка игры и запуск

Обе сцены передаются в конфигурацию игры в виде массива. Порядок в массиве не имеет значения для их работы, так как связь осуществляется через реестр, а не напрямую.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: [ SceneA, SceneB ] // Обе сцены добавлены в игру
};

const game = new Phaser.Game(config);

Обратите внимание, что SceneB создаётся с параметром active: true в конструкторе. Это гарантирует, что она будет активна и готова принимать события с самого начала, даже если SceneA запустится позже или будет перезапущена.

Как это работает? Поток данных

1. Игра запускается, создаются экземпляры обеих сцен. 2. В SceneB срабатывает create(), создаётся текст и устанавливается слушатель события 'changedata'. 3. В SceneA также срабатывает create(). Начальное значение (`0) записывается в реестр (set('score', 0)). Это действие генерирует первое событие'changedata'`. 4. Событие из реестра доходит до SceneB и вызывает её метод updateScore(registry, 'score', 0). Текст на экране обновляется на "Score: 0". 5. Таймер в SceneA каждые 500 мс увеличивает счёт и снова вызывает registry.set('score', N). Каждый такой вызов генерирует новое событие. 6. SceneB, будучи подписанной на эти события, каждый раз получает обновлённое значение и меняет текст.

Таким образом, связь является однонаправленной и реактивной: SceneA публикует данные, SceneB реагирует на их изменение.

Что попробовать дальше

Использование Registry для обмена данными между сценами — это мощный и элегантный паттерн в Phaser. Он позволяет развязать сцены, сделав их независимыми модулями, которые общаются через общее событийное хранилище. **Идеи для экспериментов:** * Передавайте через реестр не только числа, но и сложные объекты (состояние игрока, инвентарь). * Создайте сцену-меню, которая считывает финальный счёт из реестра после завершения игровой сцены. * Используйте метод registry.get('key') для однократного чтения данных, когда подписка на события не нужна. * Попробуйте удалить данные методом registry.remove('key') и отследить соответствующее событие 'removedata'.