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

При создании игр с физикой часто требуется сделать часть объектов интерактивными для игрока, а другую часть — статичной или управляемой иначе. В примере Phaser с Matter.js показан элегантный способ фильтрации объектов, которые можно перетаскивать мышью, используя группы столкновений (collision groups). Этот подход полезен для создания сложных физических сцен, где нужно точно контролировать взаимодействие игрока с окружением, например, в головоломках или симуляторах.

Версия 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('block', 'assets/sprites/block.png');
        this.load.image('mushroom', 'assets/sprites/mushroom2.png');
    }

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

        //  Add a few blocks, you can drag all of these with the pointer

        const canDrag = this.matter.world.nextGroup();

        this.matter.add.image(100, 100, 'block', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(canDrag);
        this.matter.add.image(300, 100, 'block', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(canDrag);
        this.matter.add.image(600, 100, 'block', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(canDrag);

        //  Add some mushrooms, you cannot drag these

        const noDrag = this.matter.world.nextGroup();

        this.matter.add.image(200, 100, 'mushroom', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(noDrag);
        this.matter.add.image(400, 100, 'mushroom', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(noDrag);
        this.matter.add.image(500, 100, 'mushroom', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(noDrag);

        //  Our constraint

        this.matter.add.mouseSpring({ length: 1, stiffness: 0.6, collisionFilter: { group: canDrag } });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1b1464',
    parent: 'phaser-example',
    physics: {
        default: 'matter'
    },
    scene: Example
};

const game = new Phaser.Game(config);

Основа примера: Группы столкновений Matter.js

В Matter.js, физическом движке, который используется в Phaser, collisionFilter — это объект, определяющий, как тела взаимодействуют друг с другом. Свойство group внутри фильтра позволяет объединять тела в группы. Тела из одной группы по умолчанию не сталкиваются друг с другом (если не задано иное), но, что важно для нашего примера, это свойство также используется для фильтрации при взаимодействии с инструментами вроде мышиной пружины.

Ключевой метод для создания новой уникальной группы — this.matter.world.nextGroup(). Каждый вызов возвращает новое числовое значение, идентифицирующее группу.

Создание перетаскиваемых и статичных объектов

В методе create() сначала создаются объекты, которые мы хотим сделать перетаскиваемыми. Для них создается общая группа canDrag. Каждый блок создается через this.matter.add.image с последующей настройкой.

const canDrag = this.matter.world.nextGroup();

this.matter.add.image(100, 100, 'block', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(canDrag);

Здесь { chamfer: 16 } задает скругление углов физического тела для блока. Метод .setCollisionGroup(canDrag) назначает созданное тело в группу canDrag.

Аналогично создается группа noDrag для объектов-грибов, которые не должны реагировать на перетаскивание. Им также назначается своя группа столкновений.

const noDrag = this.matter.world.nextGroup();

this.matter.add.image(200, 100, 'mushroom', null, { chamfer: 16 }).setBounce(0.9).setCollisionGroup(noDrag);

Настройка мышиной пружины с фильтром

Секрет избирательного перетаскивания кроется в создании мышиной пружины — инструмента, который связывает указатель мыши с физическим телом в мире Matter.js.

this.matter.add.mouseSpring({ length: 1, stiffness: 0.6, collisionFilter: { group: canDrag } });

При создании пружины через this.matter.add.mouseSpring() в конфигурации указывается объект collisionFilter. В его свойстве group передается значение canDrag — та самая группа, которую мы создали для блоков. Это означает, что пружина будет "цепляться" и создавать соединение только с телами, чья группа столкновений (collisionFilter.group) совпадает с указанной. Тела из группы noDrag (грибы) или любые другие тела с иным значением группы игнорируются. Параметры length (длина) и stiffness (жесткость) влияют на поведение самой пружины при перетаскивании.

Сборка сцены и конфигурация игры

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

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1b1464',
    parent: 'phaser-example',
    physics: {
        default: 'matter', // Активация Matter.js
        matter: {
            // Здесь можно добавить дополнительные настройки Matter
        }
    },
    scene: Example
};

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

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

Использование групп столкновений Matter.js для фильтрации взаимодействия — мощный и чистый метод управления игровой механикой. Он позволяет гибко разделять объекты по их поведению. Вы можете экспериментировать: создать несколько разных мышиных пружин с разными фильтрами для различных инструментов (например, магнит только для металлических предметов), или динамически менять группу у тела в runtime, чтобы включать и выключать возможность перетаскивания в зависимости от игровых событий.