О чем этот пример
В разработке игр часто возникает необходимость разделить логику интерфейса, игрового мира и спецэффектов. Phaser предлагает мощный инструмент для этого — систему сцен (Scenes). В этой статье мы разберем, как несколько сцен могут работать одновременно, перекрывая друг друга и рисуя графические примитивы. Это полезно для создания сложных HUD, меню, которые не мешают игровому процессу, или разделения отрисовки разных слоев графики.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class SceneA extends Phaser.Scene {
constructor ()
{
super({ key: 'SceneA', active: true });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('megaset', 'assets/atlas/megaset-0.png', 'assets/atlas/megaset-0.json');
}
create ()
{
this.add.image(200, 300, 'megaset', 'phaser2');
var graphics = this.add.graphics();
graphics.fillStyle(0xffffff, 1);
graphics.fillTriangle(200, 200, 400, 50, 500, 300);
graphics.fillStyle(0x00ffff, 1);
graphics.fillTriangle(60, 500, 60, 400, 500, 500);
this.scene.launch('SceneC');
}
}
class SceneB extends Phaser.Scene {
constructor ()
{
super({ key: 'SceneB', active: true });
}
create ()
{
// this.cameras.main.setBackgroundColor(0xff0000);
this.cameras.main.setViewport(100, 100, 600, 400);
var graphics = this.add.graphics();
graphics.fillStyle(0x00ff00, 1);
graphics.beginPath();
graphics.moveTo(400, 100);
graphics.lineTo(200, 278);
graphics.lineTo(340, 430);
graphics.lineTo(650, 300);
graphics.lineTo(700, 180);
graphics.lineTo(600, 80);
graphics.closePath();
graphics.fillPath();
}
}
class SceneC extends Phaser.Scene {
constructor ()
{
super('SceneC');
}
create ()
{
this.add.image(400, 300, 'megaset', 'cactuar');
const graphics = this.add.graphics();
graphics.fillStyle(0xffff00, 0.8);
graphics.fillTriangle(400, 400, 690, 50, 780, 300);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
scene: [ SceneA, SceneB, SceneC ]
};
const game = new Phaser.Game(config);
Настройка и запуск нескольких активных сцен
В примере конфигурации игры ключевым параметром является scene: [ SceneA, SceneB, SceneC ]. Это массив классов сцен, которые будут созданы при старте игры.
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
scene: [ SceneA, SceneB, SceneC ] // Все три сцены будут инициализированы
};
Обратите внимание на параметр active: true в конструкторах SceneA и SceneB. Это указывает движку, что эти сцены должны быть запущены сразу после создания. Сцена SceneC не имеет этого флага и будет запущена вручную из кода SceneA.
constructor ()
{
super({ key: 'SceneA', active: true }); // Сцена активна с начала работы игры
}
Создание графических примитивов в сценах
Каждая сцена использует объект Graphics для рисования простых фигур. Это прямой и эффективный способ отрисовки геометрии без использования текстур.
В SceneA создаются два залитых треугольника разного цвета с помощью метода fillTriangle().
var graphics = this.add.graphics(); // Создаем объект Graphics
graphics.fillStyle(0xffffff, 1); // Устанавливаем цвет заливки (белый) и непрозрачность
graphics.fillTriangle(200, 200, 400, 50, 500, 300); // Рисуем треугольник по трем точкам (x1, y1, x2, y2, x3, y3)
В SceneB используется более сложный путь (path) для создания произвольного многоугольника. Методы moveTo() и lineTo() задают вершины, а closePath() автоматически соединяет последнюю точку с первой.
graphics.beginPath(); // Начинаем определение нового пути
graphics.moveTo(400, 100); // Перемещаем "перо" в начальную точку без рисования
graphics.lineTo(200, 278); // Рисуем линию к следующей точке
graphics.closePath(); // Замыкаем путь, соединяя последнюю точку с первой
graphics.fillPath(); // Заливаем область, ограниченную путем
Управление вьюпортами камер для разделения экрана
Одна из самых мощных возможностей — управление областью отрисовки каждой сцены через камеру. В SceneB метод setViewport() ограничивает область видимости этой сцены определенным прямоугольником на основном canvas.
this.cameras.main.setViewport(100, 100, 600, 400); // (x, y, width, height)
Это означает, что вся графика из SceneB (зеленый многоугольник) будет отображаться только в пределах этой области. Области сцен могут перекрываться, создавая сложные композиции. Сцена SceneA и SceneC используют весь экран (вьюпорт по умолчанию).
Динамический запуск сцен и порядок отрисовки
Сцены можно запускать не только при старте игры, но и в процессе ее работы. В методе create() сцены SceneA происходит запуск SceneC.
this.scene.launch('SceneC'); // Запускает сцену с ключом 'SceneC'
Порядок отрисовки сцен по умолчанию соответствует порядку их в массиве в конфиге. Сцены, указанные позже, отрисовываются поверх более ранних. В нашем примере:
1. SceneA (фон, белый и голубой треугольники) — самый нижний слой.
2. SceneB (зеленый многоугольник в своем вьюпорте) — поверх SceneA.
3. SceneC (изображение кактуса и желтый треугольник) — запущена последней и отрисовывается поверх всех.
Это позволяет гибко управлять слоями интерфейса, например, чтобы окно диалога всегда было поверх игрового мира.
Что попробовать дальше
Использование стека сцен в Phaser — это основа для создания хорошо структурированных и производительных игр. Вы можете экспериментировать: попробуйте менять порядок сцен в массиве конфига, управлять их видимостью методами setVisible(), останавливать и возобновлять сцены для пауз, или использовать разные вьюпорты для создания сплит-скрин режима. Также попробуйте передавать данные между сценами с помощью this.scene.get('SceneKey') и событий.
