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

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

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

Живой запуск

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

Исходный код


class Menu extends Phaser.Scene
{
    constructor ()
    {
        super('menu');
    }

    create ()
    {
        this.add.text(10, 10, 'Press 1, 2 or 3', { font: '16px Courier', fill: '#00ff00' });

        this.input.keyboard.once('keyup-ONE', function ()
        {

            this.scene.start('demo', { id: 0, image: 'acryl-bladerunner.png' });

        }, this);

        this.input.keyboard.once('keyup-TWO', function ()
        {

            this.scene.start('demo', { id: 1, image: 'babar-phaleon-coco.png' });

        }, this);

        this.input.keyboard.once('keyup-THREE', function ()
        {

            this.scene.start('demo', { id: 2, image: 'babar-pym-wait.png' });

        }, this);

        this.events.on('shutdown', this.shutdown, this);
    }

    shutdown ()
    {
        //  We need to clear keyboard events, or they'll stack up when the Menu is re-run
        this.input.keyboard.shutdown();
    }
}

class Demo extends Phaser.Scene
{
    constructor ()
    {
        super({ key: 'demo' });
    }

    init (data)
    {
        console.log('init', data);

        this.imageID = data.id;
        this.imageFile = data.image;
    }

    preload ()
    {
        // this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
        this.load.image(`pic${this.imageID}`, `assets/pics/${this.imageFile}`);
    }

    create ()
    {
        this.add.text(10, 10, 'Click to Return', { font: '16px Courier', fill: '#00ff00' });

        this.add.image(400, 300, `pic${this.imageID}`).setScale(2);

        this.input.once('pointerup', function ()
        {

            this.scene.start('menu');

        }, this);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d8d',
    pixelArt: true,
    parent: 'phaser-example',
    scene: [ Menu, Demo ]
};

const game = new Phaser.Game(config);

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

Пример состоит из двух сцен: Menu (меню) и Demo (демонстрационная). Их взаимодействие — это классический паттерн "меню -> игровая сцена -> меню".

Ключевая связь между ними устанавливается методом this.scene.start(). Первым аргументом передается ключ целевой сцены, а вторым — объект с данными.

this.scene.start('demo', { id: 0, image: 'acryl-bladerunner.png' });

Этот вызов не только запускает сцену demo, но и передает ей объект { id: 0, image: 'acryl-bladerunner.png' }.

Инициализация сцены: метод init()

Переданные данные попадают в специальный метод init() целевой сцены. Этот метод выполняется один раз, до preload() и create(), и идеально подходит для получения и разбора входящих параметров.

В нашем примере сцена Demo сохраняет полученные данные в свои свойства.

init (data)
{
    console.log('init', data);
    this.imageID = data.id;
    this.imageFile = data.image;
}

Теперь значения id и image доступны во всех последующих методах сцены через this.imageID и this.imageFile. Важно: загрузка ресурсов происходит уже на следующем шаге, в preload().

Динамическая загрузка ресурсов в preload()

Используя полученные данные, мы можем динамически определять, какие ресурсы нужно загрузить. В методе preload() сцены Demo формируется уникальный ключ и путь к файлу изображения на основе переданных параметров.

preload ()
{
    this.load.image(`pic${this.imageID}`, `assets/pics/${this.imageFile}`);
}

Здесь используется шаблонная строка для создания ключа pic0, pic1 и т.д. и пути к конкретному файлу. Phaser загрузит только это одно изображение, что эффективно с точки зрения памяти и времени загрузки.

Использование данных в create() и возврат

После загрузки ресурс доступен по сформированному ключу. В методе create() мы создаем изображение и текст-подсказку.

create ()
{
    this.add.text(10, 10, 'Click to Return', { font: '16px Courier', fill: '#00ff00' });
    this.add.image(400, 300, `pic${this.imageID}`).setScale(2);
}

Для завершения цикла в create() также добавляется обработчик клика, который возвращает нас в меню. Обратите внимание на использование once, чтобы обработчик сработал только один раз.

this.input.once('pointerup', function () {
    this.scene.start('menu');
}, this);

Критически важный шаг: очистка событий

Частая ошибка при перезапуске сцен — накопление слушателей событий. Если сцена Menu будет запущена несколько раз, обработчики клавиш keyup-ONE, keyup-TWO, keyup-THREE добавятся снова, и каждый будет срабатывать по несколько раз.

Чтобы этого избежать, в примере используется событие shutdown и метод shutdown().

create ()
{
    // ... настройка клавиатуры ...
    this.events.on('shutdown', this.shutdown, this);
}

shutdown ()
{
    this.input.keyboard.shutdown();
}

Событие shutdown генерируется, когда сцена останавливается (например, при вызове this.scene.start('demo')). В его обработчике мы вызываем this.input.keyboard.shutdown(), который удаляет все слушатели событий клавиатуры, связанные с этой сценой. Это гарантирует чистый старт при следующем открытии меню.

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

Передача данных через this.scene.start() и их обработка в init() — это мощный и чистый способ организации взаимодействия между сценами в Phaser. Он позволяет создавать модульные и переиспользуемые сцены, такие как игровые уровни или диалоговые окна, которые могут настраиваться извне. Для экспериментов попробуйте

  1. Передавать больше данных (например, сложность уровня или стартовое здоровье игрока)
  2. Создать одну сцену-загрузчик, которая на основе полученного ID динамически загружает целую JSON-конфигурацию уровня
  3. Реализовать цепочку из нескольких сцен, последовательно передающих данные друг другу