О чем этот пример
При разработке игр часто возникает необходимость запустить одну сцену с разными параметрами. Например, меню выбора уровня должно сообщить игровой сцене, какой именно уровень загружать. Механизм передачи данных между сценами в 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. Он позволяет создавать модульные и переиспользуемые сцены, такие как игровые уровни или диалоговые окна, которые могут настраиваться извне.
Для экспериментов попробуйте
- Передавать больше данных (например, сложность уровня или стартовое здоровье игрока)
- Создать одну сцену-загрузчик, которая на основе полученного ID динамически загружает целую JSON-конфигурацию уровня
- Реализовать цепочку из нескольких сцен, последовательно передающих данные друг другу
