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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
        this.balls_images = [];
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('ball', 'assets/sprites/shinyball.png');
    }

    create ()
    {
        const Matter = Phaser.Physics.Matter;
        this.matter.world.setBounds();
        this.matter.add.mouseSpring();

        // add bodies
        this.stack = this.matter.add.stack(100, 185, 10, 10, 20, 0, (x, y) => {
            return Matter.Matter.Bodies.circle(x, y, 32/2);
        });

        this.balls_images = this.stack.bodies.map(body => {
            return this.add.image(body.position.x, body.position.y, 'ball');
        });
    }

    update ()
    {
        for (let i = 0; i < this.stack.bodies.length; i++)
        {
            const body = this.stack.bodies[i];
            const ball = this.balls_images[i];

            ball.x = body.position.x;
            ball.y = body.position.y;
            ball.rotation = body.angle;
        }
    }
}

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

const game = new Phaser.Game(config);

Инициализация Matter.js и курсора

В первую очередь необходимо настроить физический мир и добавить элемент интерактивности — пружину, привязанную к курсору мыши. Это позволит нам толкать и перемещать шары в сцене.

const Matter = Phaser.Physics.Matter;
this.matter.world.setBounds();
this.matter.add.mouseSpring();

Метод setBounds() автоматически создаёт статические стены по границам мира, предотвращая вылет шаров за пределы экрана. Функция mouseSpring() добавляет интерактивную пружину: зажатие левой кнопки мыши позволяет хватать и бросать физические тела, что идеально для отладки и игрового взаимодействия.

Генерация стека (пирамиды) тел

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

this.stack = this.matter.add.stack(100, 185, 10, 10, 20, 0, (x, y) => {
    return Matter.Matter.Bodies.circle(x, y, 32/2);
});

Рассмотрим параметры: - 100, 185 — координаты X и Y верхнего левого шара. - 10, 10 — количество столбцов и рядов в стеке. Мы создаём квадратную пирамиду 10x10. - 20, 0 — горизонтальный и вертикальный отступы между центрами шаров. Поскольку диаметр шара 32 пикселя, отступ 20 создаёт эффект плотной упаковки, и шары начинают скатываться. - Последний аргумент — фабричная функция, которая для каждой позиции (x, y) возвращает новое физическое тело — круг (Matter.Bodies.circle) радиусом 16 пикселей. Важно: Phaser хранит ссылку на Matter.js в Phaser.Physics.Matter.Matter.

Создание и привязка спрайтов

Физическое тело в Matter.js не отрисовывается само по себе. Нам нужно создать визуальное представление — спрайты изображения 'ball' — и разместить их в тех же позициях, что и тела.

this.balls_images = this.stack.bodies.map(body => {
    return this.add.image(body.position.x, body.position.y, 'ball');
});

Здесь this.stack.bodies — это массив сгенерированных физических тел. Для каждого тела методом map мы создаём спрайт изображения (this.add.image), используя текущие координаты тела body.position.x и body.position.y. Все спрайты сохраняются в массив this.balls_images для последующего обновления.

Синхронизация спрайтов с физикой

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

for (let i = 0; i < this.stack.bodies.length; i++) {
    const body = this.stack.bodies[i];
    const ball = this.balls_images[i];

    ball.x = body.position.x;
    ball.y = body.position.y;
    ball.rotation = body.angle;
}

Мы проходим по всем телам и соответствующим им спрайтам, присваивая спрайту координаты (position.x, position.y) и угол поворота (angle) из физической модели. Это стандартный паттерн для связки рендеринга и физики в Phaser с Matter.js.

Конфигурация игры и сцены

Класс сцены Example регистрируется в конфигурации игры. Ключевой момент — настройка физического движка.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1b1464',
    parent: 'phaser-example',
    physics: {
        default: 'matter', // Активация Matter.js
        matter: {}
    },
    scene: Example
};

Указание default: 'matter' в разделе physics активирует движок Matter.js для этой сцены. Дополнительные свойства мира (например, гравитацию) можно задать внутри объекта matter.

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

Использование matter.add.stack значительно ускоряет создание сложных физических конструкций. Вы можете экспериментировать: измените форму тела в фабричной функции на прямоугольник (Matter.Bodies.rectangle) или многоугольник, варьируйте отступы для создания рыхлых или плотных структур, добавлите клик-обработчик на спрайты для нанесения урона или удаления. Попробуйте применить силу ко всем шарам одновременно, чтобы создать эффект взрыва пирамиды.