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

В разработке игр часто возникает необходимость интегрировать классические HTML-элементы в игровой интерфейс — для отображения текста, кнопок или сложных виджетов. Phaser предоставляет для этого мощный инструмент — систему DOM-элементов. Этот пример демонстрирует не только добавление HTML-элементов на игровой холст с помощью `add.dom()`, но и важный аспект управления жизненным циклом сцен через методы `sleep()` и `start()`. Понимание этих механизмов позволяет создавать сложные интерфейсы и эффективно управлять ресурсами при переключении между разными состояниями игры.

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

Живой запуск

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

Исходный код


class SceneA extends Phaser.Scene
{
    constructor ()
    {
        super('a');
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('einstein', 'assets/pics/ra-einstein.png');
    }

    create ()
    {
        const div = document.createElement('div');

        div.style = 'background-color: lime; width: 220px; height: 100px; font: 48px Arial; font-weight: bold';
        div.innerText = 'Scene A';
    
        let e = this.add.dom(400, 200, div);
    
        this.add.image(400, 300, 'einstein');

        var i = 0;

        this.input.on('pointerdown', () => {

            if (i === 0)
            {
                e.destroy();
                i++;
            }
            else if (i === 1)
            {
                this.scene.sleep();
            }

        });
    }
}

class SceneB extends Phaser.Scene
{
    constructor ()
    {
        super('b');
    }

    create ()
    {
        const div = document.createElement('div');

        div.style = 'background-color: lime; width: 220px; height: 100px; font: 48px Arial; font-weight: bold';
        div.innerText = 'Scene B';
    
        this.add.dom(400, 400, div);
    
        this.add.image(400, 300, 'einstein').setScale(0.5);

        this.input.once('pointerdown', () => {

            this.scene.start('a');

        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    dom: {
        createContainer: true
    },
    scene: [ SceneA, SceneB ]
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ресурсов

Код начинается с объявления двух сцен: SceneA и SceneB. Ключевой этап — метод preload() в SceneA. В нем задается базовый URL для загрузки и загружается изображение, которое будет использоваться как фон. Важно, что SceneB не загружает ресурсы, а использует уже загруженные в памяти, демонстрируя общий доступ к ассетам.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('einstein', 'assets/pics/ra-einstein.png');
}

Создание и размещение DOM-элементов

В методе create() каждой сцены создается нативный HTML-элемент div, которому задаются стили (цвет, размер, шрифт) и текстовое содержимое. Затем этот элемент добавляется на игровой холст с помощью метода this.add.dom(x, y, element). Этот метод интегрирует HTML-элемент в координатную систему Phaser, позволяя позиционировать его как любой другой игровой объект.

const div = document.createElement('div');
div.style = 'background-color: lime; width: 220px; height: 100px; font: 48px Arial; font-weight: bold';
div.innerText = 'Scene A';
let e = this.add.dom(400, 200, div);

Управление объектами и обработка ввода

В SceneA также добавляется изображение и создается обработчик события клика (pointerdown). При первом клике DOM-элемент `eполностью удаляется из сцены и памяти с помощью методаdestroy(). Это важно для предотвращения утечек памяти. При втором клике вызываетсяthis.scene.sleep()`. Этот метод не уничтожает сцену, а переводит её в спящий режим: она останавливает все свои системы (ввод, время, обновления), но сохраняет все созданные объекты и их состояние в памяти.

this.input.on('pointerdown', () => {
    if (i === 0)
    {
        e.destroy();
        i++;
    }
    else if (i === 1)
    {
        this.scene.sleep();
    }
});

Запуск сцены и переключение между ними

Сцена SceneB запускается первой (так как она указана второй в массиве scene конфига — это особенность примера). В её методе create() также создается свой DOM-элемент и добавляется уменьшенное изображение. По клику она запускает SceneA заново с помощью this.scene.start('a'). Метод start() останавливает и уничтожает текущую сцену (SceneB), а затем запускает или пробуждает целевую сцену (SceneA). Если SceneA была в спящем режиме, она будет разбужена со всеми своими объектами (кроме уничтоженных), что может быть полезно для быстрого переключения контекстов без повторной инициализации.

this.input.once('pointerdown', () => {
    this.scene.start('a');
});

Конфигурация игры и поддержка DOM

Для работы с DOM-элементами в Phaser необходима специальная настройка в конфигурационном объекте игры. Нужно добавить свойство dom с параметром createContainer: true. Это указывает Phaser создать отдельный контейнер для DOM-элементов, обеспечивая их корректное отображение поверх или под WebGL/Canvas-рендерингом.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    dom: {
        createContainer: true // Ключевая настройка для работы с DOM
    },
    scene: [ SceneA, SceneB ]
};

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

Этот пример наглядно показывает интеграцию HTML в игровой контекст Phaser и тонкости управления сценами. DOM-элементы открывают путь к использованию богатых возможностей HTML/CSS для UI, а методы sleep() и start() позволяют гибко управлять производительностью и состоянием приложения. Для экспериментов попробуйте

  1. Добавить анимацию DOM-элементу через CSS или Phaser Tween
  2. Создать кнопку resume() для пробуждения усыплённой сцены
  3. Использовать scene.switch() для переключения между сценами без их остановки