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

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

Версия 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/crate32.png');
        this.load.html('body', 'assets/html/arcade-body.html');
    }

    create ()
    {
        // var data = this.cache.html.get('body');
        const domElement = this.add.dom(400, 0).createFromCache('body')
            .setOrigin(0);
        this.physics.add.existing(domElement, false);

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

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

        this.element.body.setCollideWorldBounds(true);

        const element = this.add.dom(100, 100, 'div', 'font-size: 96px; background-color: #FFFFFF', '💩').setOrigin(0);

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

        element.body.setCollideWorldBounds(true)
        // .setSize(element.width, element.height);
        // console.log(element.displayWidth, element.displayHeight);

        const box = this.physics.add.image(100, 100, 'block');
    }

    update ()
    {
        return;
        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',
    scale: {
        mode: Phaser.Scale.ScaleModes.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH,
    },
    width: 600,
    height: 450,
    dom: {
        createContainer: true
    },
    physics: {
        default: 'arcade',
        arcade: {
            debug: true
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

В классе сцены Example определены свойства для хранения ссылок на DOM-элемент, игрока (который не используется в итоговой версии) и управления с клавиатуры.

В методе preload() загружаются два ключевых ресурса: спрайт в виде блока и HTML-шаблон. Обратите внимание, что базовый URL меняется на репозиторий с примерами Phaser.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('block', 'assets/sprites/crate32.png');
this.load.html('body', 'assets/html/arcade-body.html');

Создание DOM-элементов с физикой

В методе create() происходит основная работа. Сначала из кеша загружается и добавляется в мир DOM-элемент с помощью this.add.dom. К этому элементу сразу добавляется физическое тело через this.physics.add.existing(). Параметр false указывает, что это не StaticBody.

Затем создаётся второй DOM-элемент — большой смайлик. Для него также создаётся физическое тело, и оно настраивается на столкновение с границами мира.

this.element = this.add.dom(300, 200, 'div', 'font-size: 96px', '💩').setOrigin(0);
this.physics.add.existing(this.element, false);
this.element.body.setCollideWorldBounds(true);

Создаётся третий, аналогичный элемент, и рядом с ним — обычный спрайт-блок для визуального сравнения.

const element = this.add.dom(100, 100, 'div', 'font-size: 96px; background-color: #FFFFFF', '💩').setOrigin(0);
this.physics.add.existing(element, false);
element.body.setCollideWorldBounds(true);
const box = this.physics.add.image(100, 100, 'block');

Проблема размера Arcade Body

Ключевая проблема, которую иллюстрирует этот пример, заключается в автоматическом определении размеров физического тела для DOM-элементов. Phaser может некорректно вычислить width и height для таких объектов.

В исходном коде есть закомментированная строка:

// .setSize(element.width, element.height);

Именно она является решением. Метод setSize() физического тела (Arcade.Body) позволяет вручную задать его ширину и высоту, чтобы они соответствовали визуальным границам DOM-элемента.

Без этой настройки тело может быть слишком большим или маленьким, что приведёт к некорректной работе столкновений, даже если debug: true в настройках физики будет включен. Отладочная отрисовка покажет реальные границы тела, которые могут не совпадать с контентом на экране.

Настройка игры и физики

Конфигурация игры включает обязательные настройки для работы с DOM и Arcade Physics.

Параметр dom.createContainer: true в конфиге активирует создание отдельного контейнера для DOM-элементов, что необходимо для их корректной работы внутри канваса Phaser. Во-вторых, в расширенных настройках Arcade Physics включён режим отладки (debug: true). В этом режиме физические тела отображаются цветными контурами, что бесценно для поиска проблем, подобных описанной в статье.

dom: {
    createContainer: true
},
physics: {
    default: 'arcade',
    arcade: {
        debug: true
    }
}

Зачем нужен пустой update()?

Метод update() в примере содержит только инструкцию return;, что делает его пустым. Однако, ниже этого оператора закомментирован код, который реализует управление первым DOM-элементом (this.element) с клавиатуры.

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

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

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

Главный вывод: при использовании DOM-элементов с Arcade Physics в Phaser 3 всегда проверяйте размер их физического тела в отладочном режиме. Если он не совпадает с визуальными границами, используйте метод body.setSize(width, height) для ручной коррекции. Для экспериментов попробуйте

  1. раскомментировать строку с setSize и увидеть, как изменится отладочный контур
  2. убрать return; из update() и поуправлять элементом с клавиатуры
  3. создать столкновения (collider) между DOM-элементами и обычными спрайтами, чтобы протестировать интерактивность