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

Создание игр с реалистичной физикой часто упирается в сложность сопоставления визуального спрайта с его физическим телом. Использование грубых примитивов (кругов, прямоугольников) может портить ощущения от игры. Этот пример демонстрирует профессиональный подход: загрузка сложных полигональных коллайдеров из внешнего JSON-файла, созданного в редакторах вроде Physics Editor. Это позволяет добиться pixel-perfect физики для объектов любой формы, что критически важно для аркадных головоломок, платформеров или симуляторов.

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

    create ()
    {
        const shapes = this.cache.json.get('shapes');

        const cat = this.add.container(400, 450);

        cat.add(this.add.sprite(0, 0, 'catstick'));

        this.matter.add.gameObject(cat, { shape: shapes.catstick, isStatic: true });

        // var cat = this.matter.add.sprite(400, 450, 'catstick', null, { shape: shapes.catstick, isStatic: true });
        // cat.setScale(0.5);

        cat.setAngle(75);
        cat.setBounce(0.7);

        for (let i = 0; i < 32; i++)
        {
            const x = Phaser.Math.Between(300, 500);
            const y = Phaser.Math.Between(-300, -100);

            this.matter.add.sprite(x, y, 'block').setAngle(20);
        }

    }
}

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

const game = new Phaser.Game(config);

Загрузка данных и настройка физики

Вся магия начинается с подготовки данных. Вместо того чтобы определять форму коллайдера в коде, мы загружаем её из внешнего JSON-файла. Это результат работы сторонних инструментов (Physics Editor, R.U.B.E), где можно вручную обвести спрайт и экспортировать данные о полигонах.

Конфигурация игры сразу указывает на использование движка Matter.js. Обратите внимание на параметр debug: true — он включает отладочную отрисовку коллайдеров, что невероятно полезно при разработке.

const config = {
    type: Phaser.AUTO,
    physics: {
        default: 'matter',
        matter: {
            gravity: { y: 0.3 },
            debug: true
        }
    },
    scene: Example
};

В методе preload загружается сам спрайт и JSON-файл с описанием его физической формы.

preload ()
{
    this.load.json('shapes', 'assets/physics/catstick.json');
    this.load.image('catstick', 'assets/sprites/catstick.png');
    this.load.image('block', 'assets/sprites/crate32.png');
}

Создание статичного тела сложной формы

Ключевой момент — создание физического тела, форма которого точно повторяет контуры спрайта. Мы используем this.matter.add.gameObject(), чтобы добавить физику к уже существующему игровому объекту — в данном случае, к Container. Это мощный приём, позволяющий комбинировать несколько визуальных элементов в одно физическое тело.

create ()
{
    const shapes = this.cache.json.get('shapes');
    const cat = this.add.container(400, 450);
    cat.add(this.add.sprite(0, 0, 'catstick'));
    this.matter.add.gameObject(cat, { shape: shapes.catstick, isStatic: true });
}

Что здесь происходит: 1. Данные о формах из кэша извлекаются в переменную shapes. 2. Создаётся Container — контейнер для группировки объектов. 3. В этот контейнер помещается спрайт с ключом 'catstick'. 4. К контейнеру добавляется физическое тело Matter.js. В опциях shape указывается загруженная форма shapes.catstick, а isStatic: true делает тело неподвижным (не подверженным силе тяжести и импульсам).

В закомментированной части показан альтернативный способ через this.matter.add.sprite, который создаёт и спрайт, и тело сразу. Использование контейнера в основном примере, вероятно, демонстрирует более гибкий сценарий.

Настройка свойств тела и генерация динамических объектов

После создания тела мы можем настроить его свойства, используя стандартные методы Matter.js, которые Phaser добавляет к игровому объекту.

cat.setAngle(75);
cat.setBounce(0.7);

Метод setAngle() поворачивает физическое тело (и, как следствие, контейнер со спрайтом) на 75 градусов. setBounce() задаёт коэффициент упругости (реституции) равным 0.7. Это значит, что динамические объекты будут отскакивать от нашей статичной фигуры, сохраняя 70% своей скорости.

Далее в сцене генерируются динамические тела — коробки, которые будут падать на нашу сложную фигуру.

for (let i = 0; i < 32; i++)
{
    const x = Phaser.Math.Between(300, 500);
    const y = Phaser.Math.Between(-300, -100);
    this.matter.add.sprite(x, y, 'block').setAngle(20);
}

Цикл создаёт 32 спрайта с физическим телом (по умолчанию — прямоугольным, совпадающим с размером текстуры block). Каждая коробка появляется в случайной позиции по X в районе фигуры и по Y — выше верхней границы экрана (-300 ... -100), чтобы им потребовалось время на падение. Каждая коробка также сразу поворачивается на 20 градусов, что делает их падение более хаотичным и наглядным.

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

Использование внешних коллайдеров — это переход от прототипирования к polish-стадии разработки игры. Он позволяет добиться точного и предсказуемого физического взаимодействия, которое чувствуется игроком. Для экспериментов попробуйте: 1. Отключить debug: true и увидеть чистую картинку. 2. Изменить isStatic на false для основной фигуры и посмотреть, как она поведёт себя под градом коробок. 3. Поиграть с другими свойствами Matter.js, например, setFriction для фигуры и коробок. 4. Загрузить другой JSON с формой (например, для спрайта block) и создать коробки сложной формы, падающие на статичный объект.