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

Встроенная гравитация в физических движках действует глобально и однонаправленно. Но что, если вам нужно создать локальный источник притяжения, вроде чёрной дыры или магнита? В этом поможет система аттракторов 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('sun', 'assets/tests/space/sun.png');
        this.load.image('alien', 'assets/sprites/space-baddie.png');
    }

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

        this.matter.add.imageStack('alien', null, 0, 500, 50, 2, 0, 0, {
            mass: 1,
            ignorePointer: true
        });

        const sun = this.matter.add.image(400, 200, 'sun', null, {
            shape: {
                type: 'circle',
                radius: 64
            },
            attractors: [
                (bodyA, bodyB) => ({
                    x: (bodyA.position.x - bodyB.position.x) * 0.000001,
                    y: (bodyA.position.y - bodyB.position.y) * 0.000001
                })
            ]
        });

        this.matter.add.mouseSpring();
    }
}

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

const game = new Phaser.Game(config);

Настройка сцены и загрузка ассетов

В методе preload мы загружаем два изображения: 'sun' (солнце) в качестве аттрактора и 'alien' (пришелец) в качестве притягиваемых тел. Базовый URL задаётся для удобства.

В методе create первым делом устанавливаем границы мира Matter.js, чтобы тела не улетали за пределы экрана. Глобальная гравитация отключена в конфигурации игры (gravity: { scale: 0 }), так как всю работу будет выполнять наш аттрактор.

// В config.physics.matter
gravity: {
    scale: 0
}
// В методе create
this.matter.world.setBounds();

Создание толпы притягиваемых тел

Вместо создания множества спрайтов по одному, используем удобный метод this.matter.add.imageStack. Он создаёт сетку или столбец из одинаковых физических тел.

В нашем примере мы создаём 50 тел в 2 ряда, начиная с координаты (0, 500). Ключевой параметр ignorePointer: true отключает взаимодействие этих тел с курсором мыши, чтобы они не мешали работе mouseSpring.

this.matter.add.imageStack('alien', null, 0, 500, 50, 2, 0, 0, {
    mass: 1,
    ignorePointer: true
});

Сердце системы: создание тела-аттрактора

Самое важное — создание тела sun, которое будет притягивать остальные. Обратите внимание на два ключевых свойства, передаваемых в конфигурационном объекте.

Свойство shape определяет физическую коллизию тела. Мы задаём её как круг радиусом 64 пикселя, совпадающий с размером изображения.

Свойство attractors — это массив функций. Каждая функция будет вызываться движком Matter.js для расчёта силы притяжения между данным телом (bodyA) и любым другим телом в мире (bodyB).

const sun = this.matter.add.image(400, 200, 'sun', null, {
    shape: {
        type: 'circle',
        radius: 64
    },
    attractors: [
        (bodyA, bodyB) => ({
            x: (bodyA.position.x - bodyB.position.x) * 0.000001,
            y: (bodyA.position.y - bodyB.position.y) * 0.000001
        })
    ]
});

Как работает функция-аттрактор

Функция-аттрактор обязана возвращать объект с вектором силы { x, y }, которая будет применена к bodyB (притягиваемому телу).

В нашем примере логика проста: 1. Вычисляется разница между координатами аттрактора (bodyA.position) и притягиваемого тела (bodyB.position). Это вектор, указывающий *от* bodyB *к* bodyA. 2. Этот вектор умножается на очень маленький коэффициент (0.000001), определяющий силу притяжения. Результат — слабая сила, толкающая bodyB в сторону bodyA.

Таким образом, для каждого кадра и для каждой пары тел (sun и каждый alien) вычисляется и применяется эта сила, создавая эффект гравитационного притяжения.

(bodyA, bodyB) => ({
    x: (bodyA.position.x - bodyB.position.x) * 0.000001,
    y: (bodyA.position.y - bodyB.position.y) * 0.000001
})

Интерактивность: добавляем пружину к курсору

Чтобы можно было взаимодействовать с системой, добавляем this.matter.add.mouseSpring(). Этот хелмер создаёт невидимую пружину, которая связывает курсор мыши с ближайшим физическим телом, у которого не установлен флаг ignorePointer. В нашем случае это будет только тело sun.

Это позволяет "таскать" за собой солнце и наблюдать, как вся масса пришельцев начинает двигаться вслед за ним, продолжая подчиняться законам аттрактора.

this.matter.add.mouseSpring();

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

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