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

В геймдеве часто нужно выходить за рамки простых прямоугольников и кругов. Этот пример демонстрирует мощный подход Phaser 3 и Matter.js: создание полигональных спрайтов с последующим наделением их точной физической формой на основе пользовательских вершин. Вы научитесь создавать сталкивающиеся объекты произвольной формы, такие как стрелы, шевроны или звёзды, с полным контролем над их физическими свойствами – от упругости до трения воздуха.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        this.matter.world.setBounds().disableGravity();

        const arrow = '40 0 40 20 100 20 100 80 40 80 40 100 0 50';
        const chevron = '100 0 75 50 100 100 25 100 0 50 25 0';
        const star = '50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38';

        const poly1 = this.add.polygon(400, 300, arrow, 0x0000ff, 0.2);

        this.matter.add.gameObject(poly1, { shape: { type: 'fromVerts', verts: arrow, flagInternal: true } });

        poly1.setVelocity(6, 3);
        poly1.setAngularVelocity(0.01);
        poly1.setBounce(1);
        poly1.setFriction(0, 0, 0);

        const poly2 = this.add.polygon(400, 100, chevron, 0xff0000, 0.2);

        this.matter.add.gameObject(poly2, { shape: { type: 'fromVerts', verts: chevron, flagInternal: true } });

        poly2.setVelocity(6, 3);
        poly2.setAngularVelocity(0.01);
        poly2.setBounce(1);
        poly2.setFriction(0, 0, 0);

        const poly3 = this.add.polygon(600, 400, star, 0x00ff00, 0.2);

        this.matter.add.gameObject(poly3, { shape: { type: 'fromVerts', verts: star, flagInternal: true } });

        poly3.setVelocity(4, -2);
        poly3.setBounce(1);
        poly3.setFriction(0, 0, 0);
        poly3.setFrictionAir(0.005);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#efefef',
    parent: 'phaser-example',
    physics: {
        default: 'matter',
        matter: {
            debug: {
                renderFill: false,
                showInternalEdges: true,
                showConvexHulls: true
            }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

Первым делом в методе create() настраивается физический мир Matter.js. Границы мира устанавливаются с помощью setBounds(), а гравитация отключается через disableGravity(). Это создаёт идеальную среду для демонстрации движения тел без внешних сил.

this.matter.world.setBounds().disableGravity();

Определение форм через строки вершин

Формы полигонов задаются строками координат. Каждая пара чисел — это координаты X и Y вершины относительно центра фигуры. Например, строка для стрелки описывает её контур от острия к хвосту. Этот подход позволяет легко хранить и передавать сложные формы.

const arrow = '40 0 40 20 100 20 100 80 40 80 40 100 0 50';
const chevron = '100 0 75 50 100 100 25 100 0 50 25 0';
const star = '50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38';

Создание графического и физического объекта

Процесс состоит из двух шагов. Сначала создаётся визуальный полигон с помощью this.add.polygon(). Затем, используя this.matter.add.gameObject(), к этому графическому объекту привязывается физическое тело. Ключевой параметр shape определяет, что форма тела (Body) должна быть построена из вершин (fromVerts). Параметр flagInternal: true помогает Matter.js корректно обрабатывать внутренние рёбра сложных вогнутых полигонов.

const poly1 = this.add.polygon(400, 300, arrow, 0x0000ff, 0.2);
this.matter.add.gameObject(poly1, { shape: { type: 'fromVerts', verts: arrow, flagInternal: true } });

Настройка физических свойств

После создания физического тела ему задаются динамические свойства через методы, предоставляемые плагином Matter. setVelocity() задаёт начальную скорость, setAngularVelocity() — скорость вращения. setBounce(1) делает тело абсолютно упругим. setFriction(0, 0, 0) убирает трение о другие тела и поверхность, а setFrictionAir() регулирует сопротивление воздуха, замедляющее объект.

poly1.setVelocity(6, 3);
poly1.setAngularVelocity(0.01);
poly1.setBounce(1);
poly1.setFriction(0, 0, 0);
// И для третьего тела можно добавить сопротивление воздуха
poly3.setFrictionAir(0.005);

Конфигурация отладки (debug)

Файл конфигурации содержит важные настройки для визуализации физических тел в режиме отладки. renderFill: false отключает заливку, показывая только контур. showInternalEdges: true подсвечивает внутренние рёбра вогнутых полигонов, а showConvexHulls: true показывает выпуклые оболочки, на которые Matter.js разбивает сложные формы для расчётов. Это невероятно полезно для понимания, как движок "видит" ваши объекты.

physics: {
    default: 'matter',
    matter: {
        debug: {
            renderFill: false,
            showInternalEdges: true,
            showConvexHulls: true
        }
    }
}

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

Использование вершин для создания физических тел открывает путь к сложной и разнообразной игровой механике: от неправильной формы астероидов до детализированных персонажей. Для экспериментов попробуйте

  1. Загружать вершины из внешнего JSON-файла или редактора уровней
  2. Динамически изменять вершины тела во время выполнения, создавая "деформируемые" объекты
  3. Комбинировать несколько простых тел (fromVerts) в одно составное (compound body) для ещё более сложных конструкций