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

Создание сложных игр часто требует взаимодействия между сценами. Например, одна сцена может управлять игровой логикой, а другая — интерфейсом. Прямой вызов методов другой сцены позволяет избежать глобальных переменных и создает чистую архитектуру. В этой статье мы разберем практический пример вызова функции из одной сцены в другой, используя встроенный менеджер сцен Phaser 3.

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

Живой запуск

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

Исходный код


class SceneA extends Phaser.Scene {

    constructor ()
    {
        super('MyFirstScene');
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('asuna', 'assets/sprites/asuna_by_vali233.png');
    }

    create ()
    {
        this.input.on('pointerup', this.clickHandler, this);

        this.add.text(10, 10, 'Click to get image', { font: '16px Courier', fill: '#00ff00' }).setDepth(1000);
    }

    clickHandler ()
    {
        let sceneB = this.scene.get('MySecondScene');

        let position = sceneB.getPosition();

        this.add.image(position.x, position.y, 'asuna');
    }

}

class SceneB extends Phaser.Scene {

    constructor ()
    {
        super('MySecondScene');
    }

    getPosition ()
    {
        let x = Phaser.Math.Between(0, 800);
        let y = Phaser.Math.Between(0, 600);

        return new Phaser.Math.Vector2(x, y);
    }

}

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

let game = new Phaser.Game(config);

Зачем нужно взаимодействие между сценами

В Phaser 3 сцены (Scene) — это независимые модули с собственным жизненным циклом (preload, create, update). Но игровая логика часто требует обмена данными. Например, сцена меню должна передавать выбранный уровень в игровую сцену, или сцена HUD должна обновлять счет, рассчитанный в игровой логике.

Использование менеджера сцен (this.scene) для получения ссылки на объект другой сцены и вызова её методов — это чистый и контролируемый способ коммуникации. Он предпочтительнее глобальных переменных или событийной системы (EventEmitter) для простых синхронных задач.

Структура примера: две сцены

В нашем примере есть SceneA (ключ 'MyFirstScene') и SceneB (ключ 'MySecondScene'). Обе зарегистрированы в конфигурации игры.

SceneA загружает спрайт и обрабатывает клик. SceneB не отображает ничего, а выступает в роли сервиса, предоставляя метод getPosition() для получения случайных координат.

Код конфигурации игры, который запускает обе сцены:

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

Получение сцены и вызов её метода

Вся магия происходит в методе clickHandler сцены SceneA. Когда игрок кликает, нам нужно получить случайную позицию от SceneB.

Сначала мы получаем ссылку на объект сцены SceneB через менеджер сцен текущей сцены, используя её ключ:

let sceneB = this.scene.get('MySecondScene');

Теперь sceneB — это экземпляр класса SceneB. Мы можем напрямую вызвать его публичный метод:

let position = sceneB.getPosition();

Метод getPosition() возвращает объект Phaser.Math.Vector2 с координатами. После получения позиции SceneA создает изображение в этом месте.

Полный код обработчика:

clickHandler ()
{
    let sceneB = this.scene.get('MySecondScene');
    let position = sceneB.getPosition();
    this.add.image(position.x, position.y, 'asuna');
}

Метод-сервис в SceneB

SceneB в данном примере не является активной сценой с визуальным представлением. Она работает как вспомогательный модуль (сервис). Её ключевой метод генерирует случайную позицию.

Разберем метод getPosition():

getPosition ()
{
    let x = Phaser.Math.Between(0, 800);
    let y = Phaser.Math.Between(0, 600);
    return new Phaser.Math.Vector2(x, y);
}

Здесь Phaser.Math.Between возвращает случайное целое число в заданном диапазоне (от 0 до ширины/высоты игры). Эти значения упаковываются в объект Vector2, который удобен для передачи пар координат `xиy`.

Важно, что метод публичный — он объявлен без особых префиксов, и поэтому доступен извне класса.

Важные нюансы и проверки

Прямой вызов методов между сценами прост, но требует осторожности.

1.  **Существование сцены**: Метод `this.scene.get()` выбросит ошибку, если сцены с указанным ключом не существует. Убедитесь, что сцена зарегистрирована в конфиге игры (`scene: [ SceneA, SceneB ]`) и её ключ в `super('MySecondScene')` указан верно.
2.  **Жизненный цикл**: Вы вызываете метод объекта сцены. Убедитесь, что к моменту вызова сцена уже была инициализирована (обычно это так, если она указана в массиве при запуске).
3.  **Синхронность**: Этот подход синхронный. Если метод `getPosition()` выполнял бы долгую операцию, он бы "заморозил" игру. Для тяжелых задач рассмотрите использование событий (`this.events`) или асинхронных паттернов.

Пример безопасной проверки перед вызовом:

if (this.scene.isActive('MySecondScene')) {
    let sceneB = this.scene.get('MySecondScene');
    // ... вызов метода
}

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

Использование this.scene.get() для прямого вызова методов между сценами — мощный инструмент для создания модульной и чистой архитектуры в Phaser 3. Он отлично подходит для синхронного обмена данными, когда одна сцена выступает в роли поставщика услуг для другой. **Идеи для экспериментов:** 1. Превратите SceneB в менеджер игровых данных: пусть она хранит счет игрока и предоставляет методы для его увеличения/получения. 2. Реализуйте сцену-настройки (SettingsScene), которая хранит громкость звука. Пусть игровая сцена обращается к ней, чтобы применить настройки. 3. Создайте сцену-пул (ObjectPoolScene) для управления переиспользуемыми игровыми объектами (пулями, эффектами), к которой будут обращаться другие сцены.