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

При разработке сложных игровых миров часто возникает необходимость в тонком контроле за тем, какие объекты должны сталкиваться друг с другом. Простая физика «все со всем» здесь не подходит. Встроенный в Phaser движок Matter.js предоставляет мощный инструмент — фильтры коллизий на основе категорий. Эта система позволяет назначать объектам принадлежность к определенным группам и гибко настраивать правила их взаимодействия, что незаменимо для создания многослойных платформеров, головоломок или игр со сложной логикой препятствий.

Версия 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');
    }

    create ()
    {
        const blockA = this.matter.add.image(200, 300, 'block').setBounce(1).setFriction(0);
        const blockB = this.matter.add.image(400, 300, 'block');

        const blockC = this.matter.add.image(750, 300, 'block').setStatic(true);
        const blockD = this.matter.add.image(50, 300, 'block').setStatic(true);

        const cat1 = this.matter.world.nextCategory();

        blockA.setCollisionCategory(cat1);
        blockC.setCollisionCategory(cat1);

        const cat2 = this.matter.world.nextCategory();

        blockD.setCollisionCategory(cat2);

        blockA.setCollidesWith([ cat1, cat2 ]);

        // blockA.setCollidesWith(cat1);

        blockA.setVelocityX(25);

        this.matter.world.on('collisionstart', event =>
        {

            event.pairs[0].bodyA.gameObject.setTint(0xff0000);
            event.pairs[0].bodyB.gameObject.setTint(0x00ff00);

        });
    }
}

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

const game = new Phaser.Game(config);

Основа системы: категории и маски

Matter.js использует битовую маску для фильтрации коллизий. Каждому физическому телу можно назначить категорию (один бит) и маску коллизий (набор битов). Столкновение произойдет, если категория тела A присутствует в маске коллизий тела B, и наоборот.

Phaser упрощает работу с этой системой. Метод this.matter.world.nextCategory() автоматически генерирует новую уникальную категорию (сдвигая бит), избавляя разработчика от ручного подсчета.

const cat1 = this.matter.world.nextCategory();
const cat2 = this.matter.world.nextCategory();

Назначение категорий объектам

Созданные категории назначаются физическим телам с помощью метода setCollisionCategory(). В примере мы видим четыре блока. Два из них (blockA и blockC) относятся к категории cat1, а один статичный блок (blockD) — к категории cat2. Блок blockB не получает явной категории, а значит, использует категорию по умолчанию.

blockA.setCollisionCategory(cat1);
blockC.setCollisionCategory(cat1);
blockD.setCollisionCategory(cat2);

Определение правил столкновений

Ключевой метод setCollidesWith() определяет, с какими категориями может сталкиваться данный объект. Он принимает либо одну категорию, либо массив категорий. В исходном коде blockA настроен на столкновение и с cat1, и с cat2.

Это значит, что движущийся blockA будет взаимодействовать с блоком blockC (оба в cat1) и с блоком blockDcat2). При этом blockA проигнорирует blockB, так как его категория по умолчанию не входит в список разрешенных для blockA.

blockA.setCollidesWith([ cat1, cat2 ]);
// Альтернативная запись для одной категории:
// blockA.setCollidesWith(cat1);

Обработка событий столкновения

Для визуализации и обработки коллизий в примере используется событие collisionstart мира Matter. В обработчике события event.pairs содержит массив пар столкнувшихся тел. Каждому игровому объекту в паре назначается tint для наглядности.

this.matter.world.on('collisionstart', event => {
    event.pairs[0].bodyA.gameObject.setTint(0xff0000);
    event.pairs[0].bodyB.gameObject.setTint(0x00ff00);
});

Настройка сцены и физики

Для работы с Matter.js необходимо правильно сконфигурировать физический плагин в настройках игры. В примере гравитация отключена (установлена в 0), чтобы движение блока blockA было равномерным.

const config = {
    // ... другие настройки
    physics: {
        default: 'matter',
        matter: {
            gravity: { x: 0, y: 0 }
        }
    },
    scene: Example
};

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

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