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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    element;
    player;
    cursors;

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

    create ()
    {
        this.cursors = this.input.keyboard.createCursorKeys();
        this.element = this.add.dom(400, 300, 'div', 'font-size: 96px', '💩').setOrigin(0);

        this.physics.add.existing(this.element, false);

        this.element.body.setCollideWorldBounds(true);
    }

    update ()
    {
        this.element.body.setVelocity(0);

        if (this.cursors.left.isDown)
        {
            this.element.body.setVelocityX(-300);
        }
        else if (this.cursors.right.isDown)
        {
            this.element.body.setVelocityX(300);
        }

        if (this.cursors.up.isDown)
        {
            this.element.body.setVelocityY(-300);
        }
        else if (this.cursors.down.isDown)
        {
            this.element.body.setVelocityY(300);
        }
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    backgroundColor: '#0072bc',
    width: 800,
    height: 600,
    dom: {
        createContainer: true
    },
    physics: {
        default: 'arcade',
        arcade: {
            debug: true
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

Настройка сцены и загрузка ресурсов

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

const config = {
    // ... другие настройки ...
    dom: {
        createContainer: true
    },
    physics: {
        default: 'arcade',
        arcade: {
            debug: true // Включаем отладку для визуализации hitbox
        }
    },
    scene: Example
};

Без параметра dom: { createContainer: true } система DOM не будет инициализирована, и вызов this.add.dom приведет к ошибке. Параметр debug: true в физике позволяет увидеть границы физического тела вокруг элемента.

Создание и "оживление" DOM-элемента

В методе create мы создаем три ключевых объекта: обработчик клавиш, DOM-элемент и привязываем к нему физическое тело.

create ()
{
    // Создаем объект для отслеживания стрелок клавиатуры
    this.cursors = this.input.keyboard.createCursorKeys();

    // Создаем DOM-элемент (div) в координатах (400, 300)
    // Задаем ему стиль font-size и текстовое содержимое '💩'
    this.element = this.add.dom(400, 300, 'div', 'font-size: 96px', '💩').setOrigin(0);

    // Делаем элемент физическим телом (false = не как спрайт)
    this.physics.add.existing(this.element, false);

    // Разрешаем телу сталкиваться с границами мира
    this.element.body.setCollideWorldBounds(true);
}

Метод this.add.dom() принимает координаты, тег HTML, строку стилей и контент. После создания элемент интегрируется в канвас Phaser. Вызов this.physics.add.existing(this.element, false) добавляет к уже существующему игровому объекту компонент тела Arcade Physics. Второй аргумент false указывает, что это не спрайт (у спрайта тело привязано к текстуре).

Управление движением через физику

Логика управления сосредоточена в update. Мы напрямую манипулируем скоростью физического тела, привязанного к элементу.

update ()
{
    // Сбрасываем скорость по обеим осям каждый кадр
    this.element.body.setVelocity(0);

    // Проверяем состояние клавиш и задаем соответствующую скорость
    if (this.cursors.left.isDown)
    {
        this.element.body.setVelocityX(-300);
    }
    else if (this.cursors.right.isDown)
    {
        this.element.body.setVelocityX(300);
    }

    if (this.cursors.up.isDown)
    {
        this.element.body.setVelocityY(-300);
    }
    else if (this.cursors.down.isDown)
    {
        this.element.body.setVelocityY(300);
    }
}

Важно сбрасывать скорость (setVelocity(0)) в начале каждого кадра. Иначе тело будет продолжать движение по инерции, даже когда клавиши отпущены. Методы setVelocityX() и setVelocityY() задают мгновенную скорость в пикселях в секунду. Условия проверяют свойство .isDown у каждого ключа в объекте this.cursors.

Особенности и ограничения DOM-элементов

DOM-элементы в Phaser — это мощный, но специфический инструмент.

* **Производительность:** Не стоит использовать сотни таких элементов для игровой графики. Они тяжелее обычных спрайтов. Идеально подходят для небольшого числа UI-компонентов. * **Координаты и Origin:** По умолчанию точка вращения и позиционирования (origin) DOM-элемента — его центр. В примере используется .setOrigin(0), что меняет точку отсчета на левый верхний угол. Это влияет на расположение физического тела. * **Взаимодействие:** С DOM-элементом можно работать как с обычным HTML-узлом через свойство this.element.node. Это открывает возможности для сложной стилизации, добавления полей ввода или обработки браузерных событий.

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

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