О чем этот пример
В разработке игр часто возникает необходимость выполнять несколько независимых процессов одновременно — например, анимировать фон и обрабатывать игровую логику. В Phaser для этого существует мощный инструмент: параллельные сцены. В этой статье мы разберем, как создавать и настраивать несколько активных сцен, которые работают одновременно, и как это может упростить архитектуру вашей игры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class SceneA extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneA', active: true });
this.pic;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('arrow', 'assets/sprites/longarrow.png');
}
create ()
{
this.pic = this.add.image(400, 300, 'arrow').setOrigin(0, 0.5);
}
update (time, delta)
{
this.pic.rotation += 0.01;
}
}
class SceneB extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneB', active: true });
}
preload ()
{
// this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('face', 'assets/pics/bw-face.png');
}
create ()
{
const img = this.add.image(400, 300, 'face');
this.tweens.add({
targets: img,
alpha: 0,
yoyo: true,
repeat: -1
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: [ SceneB, SceneA ]
};
const game = new Phaser.Game(config);
Что такое параллельные сцены?
Обычно в Phaser в любой момент времени активна только одна сцена. Однако, указав параметр active: true в конструкторе, можно сделать несколько сцен активными одновременно. Они будут независимо выполнять свои циклы preload, create и update. Это позволяет разделить ответственность: одна сцена может отвечать за фоновую анимацию, другая — за интерфейс, третья — за основную игровую механику.
В примере две сцены, SceneA и SceneB, объявлены как активные с самого начала. Обратите внимание на порядок их в массиве scene в конфигурации игры: [SceneB, SceneA]. Несмотря на то что SceneB указана первой, SceneA будет запущена первой, потому что порядок в массиве определяет порядок инициализации, но не порядок обновления. Обе сцены получают вызовы update каждый кадр.
Структура сцены с анимацией спрайта
Рассмотрим первую сцену, SceneA. Она загружает одно изображение стрелки и в цикле update непрерывно вращает его.
class SceneA extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneA', active: true });
this.pic;
}
В конструкторе мы передаем объект конфигурации с уникальным ключом key и флагом active: true. Это делает сцену активной сразу после создания. Поле this.pic зарезервировано для ссылки на наш спрайт.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('arrow', 'assets/sprites/longarrow.png');
}
В preload устанавливается базовый URL для загрузки и загружается изображение стрелки под ключом 'arrow'.
create ()
{
this.pic = this.add.image(400, 300, 'arrow').setOrigin(0, 0.5);
}
В create создается изображение в центре экрана. Метод .setOrigin(0, 0.5) устанавливает точку вращения в левом центре спрайта, что создает эффект вращения стрелки вокруг своего левого края.
update (time, delta)
{
this.pic.rotation += 0.01;
}
}
Функция update вызывается каждый кадр игры. Здесь мы увеличиваем угол вращения (rotation) спрайта на 0.01 радиана, создавая плавную анимацию вращения.
Сцена с твин-анимацией
Вторая сцена, SceneB, демонстрирует другой подход к анимации — через систему твинов Phaser. Она загружает другое изображение и запускает бесконечную анимацию его исчезновения и появления.
class SceneB extends Phaser.Scene
{
constructor ()
{
super({ key: 'sceneB', active: true });
}
Конструктор аналогичен первой сцене: та же конфигурация с активным флагом.
preload ()
{
this.load.image('face', 'assets/pics/bw-face.png');
}
Загружается изображение лица. Обратите внимание, что строка setBaseURL закомментирована. Поскольку базовый URL уже был установлен в SceneA, загрузчик Phaser использует это же значение для всех сцен. Если бы мы раскомментировали строку и изменили URL, это повлияло бы на все последующие загрузки.
create ()
{
const img = this.add.image(400, 300, 'face');
this.tweens.add({
targets: img,
alpha: 0,
yoyo: true,
repeat: -1
});
}
}
В create создается изображение лица, и к нему добавляется твин. Конфигурация твина:
- targets: объекты для анимации (наше изображение).
- alpha: 0: целевое значение прозрачности (полностью невидимый).
- yoyo: true: после достижения цели анимация проигрывается в обратном порядке.
- repeat: -1: анимация повторяется бесконечно.
Результат — лицо будет плавно исчезать и появляться, накладываясь поверх вращающейся стрелки из SceneA.
Конфигурация игры и запуск
Ключевой момент — регистрация сцен в экземпляре игры. Сцены передаются в виде массива классов в конфигурации.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: [ SceneB, SceneA ]
};
const game = new Phaser.Game(config);
Порядок в массиве scene важен для порядка инициализации. Сначала будет создан и инициализирован экземпляр SceneB, затем SceneA. Однако, поскольку обе сцены активны, их методы update будут вызываться параллельно в каждом кадре. Фоновый цвет backgroundColor применяется ко всему холсту игры и будет виден за всеми сценами.
Что попробовать дальше
Параллельные сцены — это мощный паттерн в Phaser для разделения ответственности и создания сложных визуальных эффектов. Вы можете использовать их для фоновых анимаций, разделения UI и геймплея или создания многослойных сцен.
Идеи для экспериментов:
1. Добавьте третью активную сцену, которая будет рисовать частицы поверх всего.
2. Попробуйте динамически запускать (this.scene.launch) и останавливать (this.scene.stop) сцены из кода другой сцены.
3. Поэкспериментируйте с разным порядком сцен в массиве конфигурации и посмотрите, как это влияет на порядок отрисовки (рендеринг происходит в порядке их создания).
