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

Работа с группами спрайтов — частая задача в разработке игр. Phaser предлагает мощный инструмент `Container` для объединения нескольких игровых объектов в одну логическую сущность. Однако, что если к одному из объектов внутри контейнера нужно применить физику 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('platform', 'assets/sprites/platform.png');
        this.load.image('mushroom', 'assets/sprites/mushroom2.png');
    }

    create ()
    {
        const image1 = this.add.image(0, -30, 'mushroom');
        const image2 = this.add.image(-40, 30, 'mushroom');
        const image3 = this.add.image(40, 30, 'mushroom');

        const container = this.add.container(100, 100, [ image1, image2, image3 ]);

        const physicsImage = this.matter.add.gameObject(image1);

        physicsImage.setFrictionAir(0.001);
        physicsImage.setBounce(0.9);

        this.matter.add.image(350, 450, 'platform', null, { isStatic: true }).setScale(2, 0.5).setAngle(10);
    }
}

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

const game = new Phaser.Game(config);

Разбор сцены и загрузки

Класс сцены Example наследуется от Phaser.Scene. В методе preload() мы загружаем два изображения: спрайт гриба ('mushroom') и платформы ('platform'). Базовый URL задается через this.load.setBaseURL, что удобно для загрузки ассетов из удаленного репозитория.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('platform', 'assets/sprites/platform.png');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');

Создание контейнера и его детей

В методе create() происходит основная магия. Сначала создаются три отдельных изображения (image1, image2, image3) с ключом 'mushroom'. Их координаты задаются относительно будущей точки привязки контейтера (0, 0).

const image1 = this.add.image(0, -30, 'mushroom');
const image2 = this.add.image(-40, 30, 'mushroom');
const image3 = this.add.image(40, 30, 'mushroom');

Затем эти три изображения объединяются в контейнер с помощью this.add.container. Первые два аргумента (100, 100) — это мировые координаты контейнера на сцене. Третий аргумент — массив дочерних объектов, которые будут добавлены в контейнер.

const container = this.add.container(100, 100, [ image1, image2, image3 ]);

Контейнер управляет позицией, масштабом и видимостью всех своих детей как единым целым. Дети отрисовываются относительно позиции контейтера.

Применение физики Matter.js к объекту внутри контейнера

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

const physicsImage = this.matter.add.gameObject(image1);

После этого мы можем настраивать физические свойства этого тела. setFrictionAir(0.001) устанавливает очень низкое сопротивление воздуха, делая тело почти не подверженным его влиянию. setBounce(0.9) задает высокий коэффициент упругости (отскока).

physicsImage.setFrictionAir(0.001);
physicsImage.setBounce(0.9);

Важно понимать: физика применяется только к image1. image2 и image3 остаются обычными изображениями. Однако, так как они являются детьми одного контейнера, они будут следовать за image1, когда на него начнет действовать гравитация или другие силы. Таким образом, весь контейнер становится составным физическим объектом, управляемым через одно тело.

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

Чтобы наше составное тело могло с чем-то взаимодействовать, создадим статическую платформу. Используется метод this.matter.add.image. Пятый аргумент — объект конфигурации, где isStatic: true делает тело неподвижным и не подверженным силам.

this.matter.add.image(350, 450, 'platform', null, { isStatic: true })
    .setScale(2, 0.5)
    .setAngle(10);

Платформа масштабируется по ширине в 2 раза и по высоте в 0.5 раза, а также поворачивается на 10 градусов, создавая наклонную поверхность для отскока нашего составного объекта.

Настройка физического движка Matter

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

physics: {
    default: 'matter',
    matter: {
        debug: true,
        gravity: {
            y: 0.3
        }
    }
}

Без этой конфигурации вызовы методов this.matter.add были бы недоступны, а физика не работала бы.

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

Этот пример открывает путь к созданию сложных физических объектов в Phaser. Вы можете экспериментировать: сделать физическими несколько детей внутри контейтера, привязав их друг к другу с помощью Matter.Constraint (соединений), чтобы создать, например, маятник или цепь. Попробуйте применить физику к самому контейнеру (хотя в данном API это делается иначе) или изменить тип тела image1 на составное (fromVertices) для более точного коллайдера. Комбинируя контейнеры для логической группировки и Matter.js для физического моделирования отдельных частей, вы получаете мощный инструмент для симуляции сложных игровых сущностей.