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

При разработке игр с физикой часто возникает необходимость определить, с каким именно объектом взаимодействует игрок. Например, для нанесения урона по врагу или активации механизма. Встроенный метод `intersectPoint` в Phaser с плагином Matter.js позволяет легко и эффективно решить эту задачу. В этой статье мы разберем пример сцены, которая создает комплексный физический мир со статическими и динамическими телами, соединениями разного типа, и покажем, как по координатам клика мыши получить массив всех физических тел в этой точке.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    create()
    {
        this.matter.world.setBounds();

        // First, we'll create a few static bodies
        const body1 = this.matter.add.rectangle(250, 50, 200, 32, { isStatic: true });

        this.matter.add.polygon(600, 100, 3, 40, { isStatic: true });
        this.matter.add.polygon(100, 500, 8, 50, { isStatic: true });
        this.matter.add.rectangle(750, 200, 16, 180, { isStatic: true });

        //  Now a body that shows off internal edges + convex hulls
        const star = '50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38';

        this.matter.add.fromVertices(700, 500, star, { restitution: 0.9 }, true);

        // Some different joint types
        const body2 = this.matter.add.circle(150, 250, 16);
        const body3 = this.matter.add.circle(400, 450, 16);
        const body4 = this.matter.add.circle(500, 50, 16);

        // A spring, because length > 0 and stiffness < 0.9
        this.matter.add.spring(body1, body2, 140, 0.001);

        // A joint, because length > 0 and stiffness > 0.1
        this.matter.add.worldConstraint(body3, 140, 1, {
            pointA: {
                x: 400, y: 250
            }
        });

        // A pin, because length = 0 and stiffness > 0.1
        this.matter.add.worldConstraint(body4, 0, 1, {
            pointA: {
                x: 500, y: 50
            }
        });

        // Finally some random dynamic bodies
        for (let i = 0; i < 12; i++)
        {
            const x = Phaser.Math.Between(100, 700);
            const y = Phaser.Math.Between(100, 500);

            if (Math.random() < 0.5)
            {
                const sides = Phaser.Math.Between(3, 14);
                const radius = Phaser.Math.Between(8, 50);

                this.matter.add.polygon(x, y, sides, radius, { restitution: 0.9 });
            }
            else
            {
                const width = Phaser.Math.Between(16, 128);
                const height = Phaser.Math.Between(8, 64);

                this.matter.add.rectangle(x, y, width, height, { restitution: 0.9 });
            }
        }

        this.input.on('pointerdown', function (pointer, gameObject) {
            const bodies = this.matter.intersectPoint(pointer.worldX, pointer.worldY);
            if (bodies.length)
            {
                console.log(bodies);
            }
        }, this);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#4d4d4d',
    parent: 'phaser-example',
    physics: {
        default: 'matter',
        matter: {
            enableSleeping: false,
            gravity: {
                y: 0
            },
            debug: {}
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

Перед созданием тел необходимо настроить физический мир. В конфигурации игры (config) указывается, что используется физический движок matter. Важные настройки включают отключение силы тяжести (gravity.y: 0) для статичной демонстрации и включение отладки (debug: {}), что поможет визуализировать тела при разработке.

В методе create сцены первым делом устанавливаются границы мира с помощью this.matter.world.setBounds().

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#4d4d4d',
    parent: 'phaser-example',
    physics: {
        default: 'matter',
        matter: {
            enableSleeping: false,
            gravity: { y: 0 },
            debug: {}
        }
    },
    scene: Example
};
create() {
    this.matter.world.setBounds();
    // ... создание тел
}

Создание разнообразных физических тел

Matter.js в Phaser предоставляет фабричные методы для создания тел разных форм. Тела могут быть статическими (isStatic: true) — они не двигаются под воздействием сил, и динамическими.

В примере создаются: * Статические тела: прямоугольник (rectangle), треугольник (polygon с 3 сторонами), восьмиугольник и тонкий прямоугольник-столб. * Динамическое тело в форме звезды из вершин (fromVertices). Параметр true указывает на необходимость построения выпуклой оболочки. * Динамические тела-шары (circle), которые будут соединены с миром. * Случайные динамические тела (многоугольники и прямоугольники), разбросанные по сцене.

// Статический прямоугольник
const body1 = this.matter.add.rectangle(250, 50, 200, 32, { isStatic: true });
// Динамическая звезда из вершин
this.matter.add.fromVertices(700, 500, star, { restitution: 0.9 }, true);
// Динамический круг
const body2 = this.matter.add.circle(150, 250, 16);
// Случайный многоугольник
this.matter.add.polygon(x, y, sides, radius, { restitution: 0.9 });

Соединения: Пружины, joints и пины

Matter.js позволяет связывать тела между собой или тело с точкой в мире. В примере показаны три типа соединений, создаваемых через this.matter.add.spring и this.matter.add.worldConstraint.

1. **Пружина (Spring)**: Соединяет body1 и body2. Задается длиной (140) и низкой жесткостью (0.001), что позволяет телу колебаться. 2. **Жесткое соединение (Joint)**: Соединяет body3 с фиксированной точкой в мире (pointA). Имеет длину (140) и высокую жесткость (`1), поэтому тело будет вращаться вокруг точкиpointA` как на жестком стержне. 3. **Шарнир (Pin)**: Соединяет body4 с фиксированной точкой в мире. Нулевая длина (`0) и высокая жесткость (1) означают, что тело закреплено в точкеpointA`, но может свободно вращаться вокруг нее.

// Пружина
this.matter.add.spring(body1, body2, 140, 0.001);
// Жесткое соединение (Joint)
this.matter.add.worldConstraint(body3, 140, 1, {
    pointA: { x: 400, y: 250 }
});
// Шарнир (Pin)
this.matter.add.worldConstraint(body4, 0, 1, {
    pointA: { x: 500, y: 50 }
});

Определение тел под точкой с помощью `intersectPoint`

Ключевая функциональность примера — обработка клика мыши. При нажатии (pointerdown) вычисляются мировые координаты курсора (pointer.worldX, pointer.worldY).

Метод this.matter.intersectPoint() принимает эти координаты и возвращает **массив всех физических тел движка Matter.js**, которые в данный момент пересекаются с данной точкой. Это мощный инструмент для обработки кликов в сложных физических мирах, где тела могут перекрывать друг друга.

Если массив не пуст, его содержимое (объекты тел) выводится в консоль для отладки.

this.input.on('pointerdown', function (pointer, gameObject) {
    const bodies = this.matter.intersectPoint(pointer.worldX, pointer.worldY);
    if (bodies.length) {
        console.log(bodies);
    }
}, this);

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

Метод intersectPoint — это простой и прямой способ реализовать точное взаимодействие с физическими объектами в Phaser при использовании Matter.js. Он идеально подходит для игр, где важна точность: стратегиях, головоломках или симуляторах. Для экспериментов попробуйте: 1. В обработчике клика не просто выводить тела в консоль, а применять к ним силу (body.applyForce) или менять их свойства (например, делать статическими). 2. Реализовать систему выделения объекта под курсор с визуальной подсветкой найденного тела. 3. Комбинировать intersectPoint с рейкастингом (raycast) для более сложных механик, например, определения первого тела на пути снаряда.