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

При работе с физикой в Phaser часто возникает задача проверять столкновения не просто по форме тела, а по более сложной геометрии. Например, нужно, чтобы частицы отскакивали только внутри многоугольника, а не внутри его ограничивающего прямоугольника (AABB). В этом примере показано, как использовать четвертый аргумент `processCallback` в методе `this.physics.add.overlap`. Это позволяет реализовать собственную логику проверки пересечения объектов, что открывает путь для создания сложных и нестандартных коллизий.

Версия 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('chunk', 'assets/sprites/chunk.png');
    }

    create ()
    {
        const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0xaa6622 } });

        const polygon = new Phaser.Geom.Polygon([ 200, 150, 400, 300, 600, 150, 750, 300, 600, 450, 200, 450, 50, 300 ]);

        const { left, top, width, height } = Phaser.Geom.Polygon.GetAABB(polygon);

        const boundingBox = this.add.zone(left, top, width, height).setOrigin(0, 0);

        // Static body
        this.physics.add.existing(boundingBox, true);

        const chunks = this.physics.add.group({
            key: 'chunk',
            quantity: 240,
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true,
            velocityX: 50,
            velocityY: 50
        });

        Phaser.Actions.RandomRectangle(chunks.getChildren(), { x: 0, y: 0, width: 800, height: 100 });

        this.physics.add.overlap(
            boundingBox,
            chunks,
            null,
            function process (bbox, chunk)
            {
                return Phaser.Geom.Polygon.ContainsPoint(polygon, chunk);
            }
        );

        graphics.fillStyle(0xffffff, 0.2);
        graphics.fillPoints(polygon.points, true);
    }

    update ()
    {
        const bodies = Array.from(this.physics.world.bodies);
        for (const body of bodies)
        {
            body.debugBodyColor = body.touching.none ? 0x00ff00 : 0xff0000;
        }
    }
}

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

const game = new Phaser.Game(config);

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

В методе create() мы создаем полигон и его ограничивающий прямоугольник (AABB). Зона (boundingBox) будет использоваться как статическое физическое тело для грубой проверки столкновений.

const polygon = new Phaser.Geom.Polygon([ 200, 150, 400, 300, 600, 150, 750, 300, 600, 450, 200, 450, 50, 300 ]);
const { left, top, width, height } = Phaser.Geom.Polygon.GetAABB(polygon);
const boundingBox = this.add.zone(left, top, width, height).setOrigin(0, 0);
this.physics.add.existing(boundingBox, true);

Здесь GetAABB вычисляет прямоугольник, который полностью содержит наш полигон. Мы превращаем зону в статическое тело (true во втором аргументе), чтобы оно не двигалось под воздействием физики.

Создание группы частиц

Далее создается группа физических спрайтов (chunks), которые будут летать по сцене.

const chunks = this.physics.add.group({
    key: 'chunk',
    quantity: 240,
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true,
    velocityX: 50,
    velocityY: 50
});
Phaser.Actions.RandomRectangle(chunks.getChildren(), { x: 0, y: 0, width: 800, height: 100 });

Параметры bounceX и bounceY равны 1, что означает идеальный отскок (без потери энергии). RandomRectangle случайным образом размещает все частицы в верхней части экрана, чтобы они падали вниз под действием гравитации.

Ключевой момент: callback-функция process

Самый важный элемент — это вызов this.physics.add.overlap с четвертым аргументом.

this.physics.add.overlap(
    boundingBox,
    chunks,
    null,
    function process (bbox, chunk)
    {
        return Phaser.Geom.Polygon.ContainsPoint(polygon, chunk);
    }
);

Первые два аргумента — объекты для проверки пересечения. Третий аргумент (callback) — null, значит, при реальном столкновении ничего дополнительного не происходит. Четвертый аргумент — processCallback. Эта функция вызывается для каждой пары объектов, которые физический движок Arcade уже определил как потенциально пересекающиеся (на основе их AABB). Функция process должна вернуть true, если объекты действительно пересекаются по нашей кастомной логике, и false — если нет.

Здесь мы используем Phaser.Geom.Polygon.ContainsPoint, чтобы проверить, находится ли центр спрайта chunk внутри нашего полигона. Только в этом случае будет засчитано пересечение с boundingBox.

Визуализация и отладка

Для наглядности полигон отрисовывается с помощью Graphics.

const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0xaa6622 } });
graphics.fillStyle(0xffffff, 0.2);
graphics.fillPoints(polygon.points, true);

В методе update() меняется цвет отладочной отрисовки тел в зависимости от того, касаются они чего-либо или нет.

const bodies = Array.from(this.physics.world.bodies);
for (const body of bodies)
{
    body.debugBodyColor = body.touching.none ? 0x00ff00 : 0xff0000;
}

Это позволяет увидеть: тела, которые находятся внутри полигона (и, следовательно, сталкиваются с boundingBox), будут окрашены в красный, а свободно летящие — в зеленый.

Настройка физического мира

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

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: true, // Включает отладочную отрисовку
            debugShowVelocity: false,
            gravity: { y: 100 } // Гравитация тянет частицы вниз
        }
    },
    scene: Example
};

Параметр debug: true необходим для работы изменения цвета тел в update().

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

Использование processCallback в overlap — это мощный метод для создания сложных условий столкновений, выходящих за рамки простых прямоугольников и кругов. Вы можете проверять пересечение по маске, расстоянию, состоянию объекта или любой другой логике. **Идеи для экспериментов:** 1. Замените полигон на эллипс или линию и измените условие в process. 2. Вместо проверки центра спрайта (chunk) проверяйте его границы или случайную точку. 3. Используйте processCallback для избирательного включения столкновений между объектами в зависимости от их типа или свойств данных.