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

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

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

Живой запуск

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

Исходный код



const backgroundSceneConfig = {
    key: 'background',
    active: true,
    create: createBackground,
    render: renderBackground,
    pack: {
        files: [
            { type: 'image', key: 'face', url: 'assets/pics/bw-face.png' }
        ]
    }
};

const modalSceneConfig = {
    key: 'modal',
    active: true,
    renderToTexture: false,
    x: 64,
    y: 64,
    width: 320,
    height: 200,
    create: createModal,
    render: renderModal,
    pack: {
        files: [
            { type: 'image', key: 'logo', url: 'assets/pics/agent-t-buggin-acf-logo.png' }
        ]
    }
};

const gameConfig = {
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: [ backgroundSceneConfig, modalSceneConfig ]
};

const game = new Phaser.Game(gameConfig);

function createBackground ()
{
    this.add.image(400, 300, 'face');
}

function createModal ()
{
    this.cameras.main.setBackgroundColor('rgba(255,0,0,0.4)');

    console.log(this.cameras.main.backgroundColor);

    this.add.image(0, 0, 'logo').setOrigin(0);
}

const r = 0;
function renderBackground (ctx)
{
    ctx.fillStyle = 'rgb(' + r + ', 0, 0)';
    ctx.fillRect(0, 0, 32, 32);

    r += 2;

    if (r >= 256)
    {
        r = 0;
    }
}

function renderModal (ctx)
{
    ctx.fillStyle = 'rgb(0, ' + r + ', 0)';
    ctx.fillRect(0, 0, 32, 32);

    r += 2;

    if (r >= 256)
    {
        r = 0;
    }
}

Настройка сцен через конфигурационные объекты

Вместо передачи классов в массив scene можно использовать конфигурационные объекты. Это даёт больше контроля на этапе инициализации.

Каждая сцена — это объект с ключевыми свойствами. Например, key — уникальный идентификатор, active — запускать ли сцену сразу, create — функция для начальной настройки.

const backgroundSceneConfig = {
    key: 'background',
    active: true,
    create: createBackground,
    render: renderBackground
};

const modalSceneConfig = {
    key: 'modal',
    active: true,
    renderToTexture: false,
    x: 64,
    y: 64,
    width: 320,
    height: 200,
    create: createModal,
    render: renderModal
};

Обратите внимание на renderToTexture: false. Это важно: если бы значение было true, сцена рендерилась бы в текстуру, а не прямо на холст. Для модального окна мы хотим прямое наложение.

Свойства `x,y,width,heightопределяют область отрисовки сценыmodal` на основном холсте. В данном случае окно смещено на 64 пикселя от краёв и имеет размер 320x200.

Загрузка ассетов и инициализация игры

Каждая сцена может иметь свой собственный блок предзагрузки pack. Это удобно для распределения ресурсов.

pack: {
    files: [
        { type: 'image', key: 'face', url: 'assets/pics/bw-face.png' }
    ]
}

Затем конфиги обеих сцен передаются в настройки игры. Порядок в массиве имеет значение: сцены рендерятся последовательно. Сначала background, потом modal — так модальное окно окажется поверх фона.

const gameConfig = {
    type: Phaser.CANVAS,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: [ backgroundSceneConfig, modalSceneConfig ]
};

const game = new Phaser.Game(gameConfig);

Создание графики в сценах

Функция create выполняется один раз при создании сцены. В ней размещаются основные объекты.

В фоновой сцене просто добавляется изображение по центру.

function createBackground ()
{
    this.add.image(400, 300, 'face');
}

В модальной сцене сначала задаётся цвет фона камеры с прозрачностью. Это создаёт эффект затемнения. Затем добавляется логотип в левый верхний угол окна.

function createModal ()
{
    this.cameras.main.setBackgroundColor('rgba(255,0,0,0.4)');
    this.add.image(0, 0, 'logo').setOrigin(0);
}

Вызов setOrigin(0) привязывает изображение к точке (0,0) внутри области модальной сцены, то есть к её левому верхнему углу.

Кастомный рендеринг с помощью функции render

Функция render предоставляет прямой доступ к контексту рендеринга (ctx). Она вызывается каждый кадр после отрисовки всех объектов сцены. В примере она используется для анимации цветного квадрата.

Важный момент: переменная `rобъявлена глобально и используется в обеих функцияхrender`. Это не лучшая практика для реального проекта, но здесь демонстрирует общее состояние.

let r = 0;

function renderBackground (ctx)
{
    ctx.fillStyle = 'rgb(' + r + ', 0, 0)';
    ctx.fillRect(0, 0, 32, 32);
    r += 2;
    if (r >= 256) { r = 0; }
}

function renderModal (ctx)
{
    ctx.fillStyle = 'rgb(0, ' + r + ', 0)';
    ctx.fillRect(0, 0, 32, 32);
    r += 2;
    if (r >= 256) { r = 0; }
}

Квадрат в фоновой сцене меняет красную компоненту, а в модальной — зелёную. Поскольку они используют одну переменную `r`, их анимации синхронизированы. Квадраты рисуются в координатах (0,0) относительно своей сцены. На фоне он будет в самом верху слева, а в модальном окне — в его левом верхнем углу.

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

Использование конфигурационных объектов для сцен открывает гибкий путь к созданию сложных композиций, таких как модальные окна, HUD или раздельные игровые слои. Для экспериментов попробуйте: изменить renderToTexture на true и посмотреть на эффект, добавить интерактивность кнопкам внутри модальной сцены или организовать обмен данными между сценами через глобальные события (this.events).