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

При создании игр с физикой часто нужно не просто зафиксировать факт столкновения, но и понять, с какой именно стороны объекта оно произошло. Это критически важно для реалистичной реакции: персонаж должен остановиться, упершись в стену, а не провалиться сквозь нее, а мяч — отскочить от пола. Пример из официальной документации Phaser наглядно демонстрирует, как Arcade Physics отмечает стороны (`faces`) динамического тела, которые контактируют с другим объектом. Мы разберем, чем отличаются свойства `blocked` и `touching`, и как визуализировать эту информацию для отладки и создания сложной игровой логики.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    text;
    graphics;
    block;
    atari;

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

    create ()
    {
        this.atari = this.physics.add.staticImage(400, 300, 'atari');

        this.block = this.physics.add.image(0, 0, 'block');
        this.block.setVelocity(200, 200);
        this.block.setBounce(1, 1);
        this.block.setCollideWorldBounds(true);

        this.graphics = this.add.graphics();

        this.text = this.add.text(0, 0, '-');

        // During a Body vs. Static Body collision or overlap the Body becomes 'blocked' (and also 'touching').
        // It's easier to see during an overlap:
        this.physics.add.overlap(this.atari, this.block);
    }

    update ()
    {
        this.graphics.clear();

        this.draw(this.atari);
        this.draw(this.block);

        this.text.setText([
            'Box:',
            '',
            JSON.stringify(
                Phaser.Utils.Objects.Pick(this.block.body, [
                    'blocked',
                    'touching',
                    'embedded'
                ]),
                null,
                2
            )
        ]);
    }

    draw (gameObject)
    {
        this.graphics.lineStyle(5, 0xffff00, 0.8);

        this.drawFaces(gameObject.body, gameObject.body.touching);

        this.graphics.lineStyle(5, 0xff0000, 0.8);

        this.drawFaces(gameObject.body, gameObject.body.blocked);
    }

    drawFaces (body, faces)
    {
        if (faces.left)
        {
            this.graphics.lineBetween(body.left, body.top, body.left, body.bottom);
        }

        if (faces.up)
        {
            this.graphics.lineBetween(body.left, body.top, body.right, body.top);
        }

        if (faces.right)
        {
            this.graphics.lineBetween(body.right, body.top, body.right, body.bottom);
        }

        if (faces.down)
        {
            this.graphics.lineBetween(body.left, body.bottom, body.right, body.bottom);
        }
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 200 }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

Суть примера: отслеживание сторон контакта

В этом примере создаются два объекта: статичный спрайт atari и динамичный блок block, который движется с отскоком от границ мира. Между ними регистрируется взаимодействие через this.physics.add.overlap().

Ключевая задача — в реальном времени отрисовывать цветные линии по краям (faces) блока, которые в данный момент контактируют со статичным объектом. Для этого используются свойства тела (body) физического объекта: touching и blocked. Визуализация помогает понять, как движущееся тело "воспринимает" столкновение.

Настройка физики и создание объектов

В первую очередь настраивается сцена с Arcade Physics. Обратите внимание на конфигурацию: гравитация включена, но отладка (debug) отключена, так как визуализацию мы сделаем самостоятельно.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 200 }
        }
    },
    scene: Example
};

В методе create() создаются объекты. atari — статичный, он служит препятствием. block — динамичный, ему задается начальная скорость, упругость (bounce) и включен отскок от границ холста.

create ()
{
    this.atari = this.physics.add.staticImage(400, 300, 'atari');
    this.block = this.physics.add.image(0, 0, 'block');
    this.block.setVelocity(200, 200);
    this.block.setBounce(1, 1);
    this.block.setCollideWorldBounds(true);
    // ...
}

Важный момент: для отслеживания контакта используется overlap, а не collider. Это сделано специально, чтобы объекты не раздвигались физическим движком, и мы могли наглядно наблюдать состояние blocked.

Чем отличаются `touching` и `blocked`?

Это центральные свойства для понимания примера. Оба являются объектами с полями up, down, left, right, которые принимают булевы значения.

* **touching** указывает, какая сторона тела *касается* другого тела в текущий кадр. Это "мгновенный" контакт. * **blocked** указывает, какая сторона тела *заблокирована* другим телом. Состояние blocked возникает при столкновении динамического тела со статическим (как в нашем примере) или кинематическим и сохраняется, пока контакт не прекратится. Это "постоянный" контакт, препятствующий движению.

В методе update() состояние этих свойств для блока выводится на экран в виде форматированного JSON.

this.text.setText([
    'Box:',
    '',
    JSON.stringify(
        Phaser.Utils.Objects.Pick(this.block.body, [
            'blocked',
            'touching',
            'embedded'
        ]),
        null,
        2
    )
]);

Утилита Phaser.Utils.Objects.Pick создает новый объект, выбрав из тела блока только указанные свойства, что удобно для логирования.

Визуализация контактов с помощью Graphics

Чтобы увидеть контактные стороны, используется объект this.graphics. В каждом кадре (update) он очищается и заново отрисовывает линии.

Метод draw(gameObject) вызывается для каждого физического объекта. Сначала желтым цветом (0xffff00) рисуются стороны из свойства touching, затем красным (0xff0000) — из blocked.

draw (gameObject)
{
    // Желтый для touching
    this.graphics.lineStyle(5, 0xffff00, 0.8);
    this.drawFaces(gameObject.body, gameObject.body.touching);

    // Красный для blocked
    this.graphics.lineStyle(5, 0xff0000, 0.8);
    this.drawFaces(gameObject.body, gameObject.body.blocked);
}

Метод drawFaces принимает тело и объект с флагами сторон. Он проверяет каждый флаг (left, up, right, down) и, если он истинен, рисует линию вдоль соответствующего края хитбокса тела, используя его координаты (body.left, body.top и т.д.).

if (faces.left)
{
    this.graphics.lineBetween(body.left, body.top, body.left, body.bottom);
}

Практическое применение в играх

Понимание blocked и touching открывает возможности для сложной игровой логики:

* **Платформер:** Проверка blocked.down — классический способ определить, стоит ли персонаж на земле. blocked.left и blocked.right помогут реализовать скольжение по стенам. * **Головоломки:** Можно определить, в какую сторону игрок толкнул объект, и разрешить движение только по свободным направлениям. * **Стрелялки:** При столкновении снаряда со стеной (touching) можно создать частицы разлета именно с той стороны, куда он пришелся.

Для этого в update() вашей игры нужно проверять соответствующие свойства тела.

update() {
    const body = this.player.body;
    this.isGrounded = body.blocked.down;
    this.isAgainstWall = body.blocked.left || body.blocked.right;
    // Далее используем эти флаги для анимаций и управления
}

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

Свойства blocked и touching в Arcade Physics — это мощный низкоуровневый инструмент для реакции на столкновения. Они дают точную информацию о том, *как* объекты соприкасаются, что необходимо для создания убедительной физики платформеров, головоломок и аркадных игр. **Идеи для экспериментов:** 1. Замените overlap на collider и понаблюдайте, как изменится поведение: блок будет отталкиваться, а состояние blocked будет мигать. 2. Создайте сцену с несколькими статичными платформами и проверяйте, как меняются флаги при сложном движении. 3. Реализуйте простого платформерного персонажа, который может прыгать только когда blocked.down === true.