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

В Phaser при использовании физики Matter.js разработчики часто сталкиваются с неожиданным поведением: изменение свойства `scale` у спрайта с физическим телом не приводит к изменению размеров коллайдера. Это может вызвать проблемы с обнаружением столкновений, когда визуальное представление объекта не соответствует его физической модели. В этой статье мы разберем, почему это происходит, и как правильно изменять размер физических тел в 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('blue', 'assets/sprites/columns-blue.png');
    }

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

        //  By default it will create a rectangular body the size of the texture
        const rect = this.matter.add.image(200, 50, 'blue');

        //  Just make the body move around and bounce
        rect.setVelocity(3, 1);
        rect.setAngularVelocity(0.01);
        rect.setBounce(1);
        rect.setFriction(0, 0, 0);

        rect.setInteractive();

        rect.once('pointerup', () =>
        {
            rect.scale = 2;
        });
    }
}

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

const game = new Phaser.Game(config);

Проблема: scale не влияет на физическое тело

В примере мы создаем физический объект с помощью this.matter.add.image. При клике на объект мы пытаемся увеличить его в два раза, устанавливая свойство rect.scale = 2. Визуально спрайт увеличивается, но его физическое тело (коллайдер) остается прежнего размера.

rect.once('pointerup', () =>
{
    rect.scale = 2;
});

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

Как правильно изменить размер физического тела

Для изменения размеров тела в Matter.js необходимо создать новую геометрию (или масштабировать существующую) и применить ее к телу. Phaser предоставляет для этого метод setBody.

Предположим, мы хотим увеличить тело в два раза. Нам нужно: 1. Получить текущую конфигурацию тела. 2. Создать новое тело с измененными размерами. 3. Заменить старое тело новым.

rect.once('pointerup', () => {
    // Меняем визуальный масштаб
    rect.scale = 2;
    
    // Получаем текущие размеры оригинального спрайта (до scale)
    const width = rect.width;
    const height = rect.height;
    
    // Создаем новое тело - прямоугольник с увеличенными размерами
    const newBody = this.matter.bodies.rectangle(rect.x, rect.y, width * 2, height * 2);
    
    // Заменяем тело у спрайта
    rect.setBody(newBody);
});

Важно: rect.width и rect.height возвращают исходные размеры текстуры, а не масштабированные.

Копирование свойств физического тела

При создании нового тела важно перенести все физические свойства из старого тела, чтобы сохранить поведение объекта. Ключевые свойства, которые стоит скопировать:

rect.once('pointerup', () => {
    rect.scale = 2;
    const width = rect.width;
    const height = rect.height;
    
    // Создаем новое тело
    const newBody = this.matter.bodies.rectangle(rect.x, rect.y, width * 2, height * 2);
    
    // Копируем важные свойства из старого тела
    const oldBody = rect.body;
    newBody.isStatic = oldBody.isStatic;
    newBody.restitution = oldBody.restitution; // Аналог setBounce
    newBody.friction = oldBody.friction;
    newBody.frictionAir = oldBody.frictionAir; // Аналог setFriction(0,0,0)
    newBody.inverseInertia = oldBody.inverseInertia;
    
    // Переносим скорость и угловую скорость
    newBody.velocity = oldBody.velocity;
    newBody.angularVelocity = oldBody.angularVelocity;
    
    rect.setBody(newBody);
});

Этот подход гарантирует, что объект сохранит свою скорость, отскок (setBounce) и другие физические характеристики после изменения размера.

Альтернативный подход: изначальное планирование

Если вам заранее известны возможные размеры объекта, более эффективным решением будет создать несколько тел и переключаться между ними.

create() {
    // ... создание rect ...
    
    // Создаем два тела: обычное и увеличенное
    const normalBody = this.matter.bodies.rectangle(0, 0, rect.width, rect.height);
    const scaledBody = this.matter.bodies.rectangle(0, 0, rect.width * 2, rect.height * 2);
    
    // Сохраняем тела в объекте для повторного использования
    rect.normalBody = normalBody;
    rect.scaledBody = scaledBody;
    
    rect.once('pointerup', () => {
        rect.scale = 2;
        // Используем заранее созданное тело
        rect.setBody(rect.scaledBody);
    });
}

Этот подход особенно полезен, если изменение размера происходит часто, так как избегает постоянного создания новых тел во время выполнения игры.

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

Изменение свойства scale у объектов с физикой Matter.js влияет только на их отрисовку, но не на физическое тело. Для корректного изменения размеров коллайдера необходимо создавать новое тело с помощью this.matter.bodies и применять его через setBody. Не забудьте скопировать физические свойства из старого тела, чтобы сохранить поведение объекта. Для экспериментов попробуйте реализовать плавное изменение размера с промежуточными значениями или создать систему, которая автоматически синхронизирует визуальный scale с физическим телом.