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

Хотите добавить в игру сложные интерфейсы, формы или динамический текст с полной поддержкой CSS? Стандартные текстовые объекты Phaser имеют ограничения по стилизации. Выход — использование DOM-элементов прямо на игровом холсте. Это открывает доступ ко всем возможностям HTML и CSS, сохраняя привязку элемента к игровой сцене, физике и системам анимации. В этой статье мы разберем пример, где контейнер объединяет DOM-элемент с обычным игровым объектом (прямоугольником) и анимирует их как единое целое. Вы научитесь создавать гибридные интерфейсы, которые идеально вписываются в геймплей.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    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: rgba(0,255,0,0.2); width: 250px; height: 100px; font: 48px Arial; font-weight: bold';
        div.innerText = 'Phaser 3';

        const container = this.add.container(400, 300);
        const element = this.add.dom(0, 0, div);
        const rect = this.add.rectangle(0, 0, 16, 16, 0xff00ff);

        // var element = this.add.dom(400, 300, div).setOrigin(0);
        // var rect = this.add.rectangle(400, 300, 16, 16, 0xff00ff);

        container.add([ element, rect ]);

        this.tweens.add({
            targets: container,
            angle: 360,
            duration: 12000,
            loop: -1
        });

        this.tweens.add({
            targets: element,
            duration: 3000,
            _angle: 360,
            scaleX: 2,
            scaleY: 2,
            ease: 'Sine.easeInOut',
            loop: -1,
            yoyo: true
        });
    }
}

const config = {
    type: Phaser.AUTO,
    scale: {
        _mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
        parent: 'phaser-example',
        width: 800,
        height: 600
    },
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    dom: {
        createContainer: true
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

Ключ к работе с DOM — правильная конфигурация игры. В объекте config необходимо явно указать, что Phaser должен создать контейнер для DOM-элементов. Без этого система add.dom не будет работать.

Также в этом примере используется особый режим масштабирования Phaser.Scale.WIDTH_CONTROLS_HEIGHT, который автоматически подстраивает высоту холста под ширину родительского элемента, сохраняя пропорции.

const config = {
    type: Phaser.AUTO,
    scale: {
        _mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
        parent: 'phaser-example',
        width: 800,
        height: 600
    },
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    dom: {
        createContainer: true // Важно! Активирует подсистему DOM
    },
    scene: Example
};

const game = new Phaser.Game(config);

Создание и стилизация DOM-элемента

В методе create мы создаем обычный HTMLDivElement с помощью стандартного Web API. Его можно стилизовать любым CSS — в примере задан полупрозрачный зеленый фон, размеры и жирный шрифт.

Этот элемент пока существует отдельно от игрового мира. Чтобы встроить его в сцену Phaser, используется фабричный метод this.add.dom(x, y, element). Он возвращает специальный игровой объект типа DOMElement, который оборачивает наш div и управляет его позицией, видимостью и трансформациями внутри игры.

const div = document.createElement('div');
div.style = 'background-color: rgba(0,255,0,0.2); width: 250px; height: 100px; font: 48px Arial; font-weight: bold';
div.innerText = 'Phaser 3';

const element = this.add.dom(0, 0, div);

Объединение объектов в контейнер

Одна из мощнейших фич примера — использование Container. Контейнер (this.add.container) — это объект, который группирует другие игровые объекты, позволяя трансформировать их (двигать, вращать, масштабировать) как одно целое.

В контейнер помещаются и DOM-элемент element, и обычный прямоугольник rect, созданный через this.add.rectangle. Изначально они позиционируются в точке (0,0) относительно центра контейнера, который мы разместили в (400,300).

const container = this.add.container(400, 300);
const rect = this.add.rectangle(0, 0, 16, 16, 0xff00ff);
container.add([ element, rect ]);

Совместная и независимая анимация

Теперь можно анимировать всю группу объектов. Первый твин вращает весь контейнер на 360 градусов за 12 секунд, и DOM-элемент с прямоугольником вращаются вместе, как приклеенные.

Второй твин применяется точечно к самому объекту element. Обратите внимание на префикс нижнего подчеркивания в свойстве _angle. Для DOM-элементов некоторые стандартные свойства твинов требуют такого префикса. Этот твин независимо от контейнера масштабирует и вращает только DOM-блок, создавая сложную составную анимацию.

this.tweens.add({
    targets: container,
    angle: 360,
    duration: 12000,
    loop: -1
});

this.tweens.add({
    targets: element,
    duration: 3000,
    _angle: 360, // Специальное свойство для DOM-элемента
    scaleX: 2,
    scaleY: 2,
    ease: 'Sine.easeInOut',
    loop: -1,
    yoyo: true
});

Система координат и точка вращения

Важно понимать иерархию координат. DOM-элемент и прямоугольник добавлены в контейнер со смещением (0,0). Это значит, их локальная точка (0,0) привязана к позиции контейнера в мире (400,300). Все трансформации контейнера (например, вращение) происходят относительно его точки начала координат.

Если закомментировать создание контейнера и раскомментировать альтернативные строки в исходнике, объекты будут добавлены прямо на сцену с абсолютными координатами (400,300). В этом случае их нельзя будет сгруппировать для совместного вращения одним твином — они будут независимыми объектами в мире.

// Альтернатива без контейнера:
// var element = this.add.dom(400, 300, div).setOrigin(0);
// var rect = this.add.rectangle(400, 300, 16, 16, 0xff00ff);

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

Интеграция DOM-элементов через add.dom и их группировка в контейнерах — мощный прием для создания сложных UI внутри игр на Phaser 3. Вы получаете всю гибкость веб-технологий, не теряя преимуществ игрового движка. **Идеи для экспериментов:** 1. Добавьте в div поле ввода (<input>) и обрабатывайте его события клавиатуры прямо в игре. 2. Используйте CSS-анимации или переходы (transition) внутри DOM-элемента одновременно с Phaser Tween. 3. Привяжите DOM-элемент к физическому телу (через контейнер) и посмотрите, как он будет вести себя при столкновениях. 4. Создайте сложный HUD с несколькими стилизованными блоками и анимируйте их появление через контейнеры.