О чем этот пример
При создании игры с физикой часто возникает задача идеально совместить спрайт и его физическое тело. Встроенный движок Matter.js в Phaser позиционирует тела по их центру масс, что может привести к неожиданным смещениям относительно графики. Эта статья объясняет, как рассчитать точное смещение, используя границы тела (`body.bounds`) и его центр масс (`centerOfMass`), чтобы спрайт и его коллайдер занимали одинаковое место на экране. Этот навык критически важен для создания полигональных коллайдеров, аккуратной анимации появления объектов и точного выравнивания статических декораций.
Версия 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.atlas('sheet', 'assets/physics/fruit-sprites.png', 'assets/physics/fruit-sprites.json');
this.load.json('shapes', 'assets/physics/fruit-shapes.json');
}
create ()
{
const align = this.add.image(0, 200, 'sheet', 'banana').setOrigin(0);
const shapes = this.cache.json.get('shapes');
// If you position the shape at 0x0 it will be positioned based on its center of mass by default.
// This isn't always desirable, especially if trying to align with non-physics images.
const banana = this.matter.add.sprite(0, 0, 'sheet', 'banana', { shape: shapes.banana });
// To position precisely we need to work out the bounds and offsets.
// We can either use the shape bounds (which changes as the shape rotates)
const width = (banana.body.bounds.max.x - banana.body.bounds.min.x) / 2;
const height = (banana.body.bounds.max.y - banana.body.bounds.min.y) / 2;
// Or the display origin, if the shape will not rotate and is static:
// var width = banana.displayOriginX;
// var height = banana.displayOriginY;
// Adjust for the center of mass:
const xOffset = -(width - banana.centerOfMass.x) + width;
const yOffset = -(height - banana.centerOfMass.y) + height;
const x = 500;
const y = 200;
// Then apply them to the position we want the shape to appear at
banana.setPosition(x + xOffset, y + yOffset);
this.tweens.add({
targets: align,
x: 500,
duration: 3000,
ease: 'Sine.easeOut'
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example,
physics: {
default: 'matter',
matter: {
gravity: { y: 0 },
debug: true
}
}
};
const game = new Phaser.Game(config);
Проблема: центр масс против визуального центра
Когда вы создаете физическое тело с помощью this.matter.add.sprite, Matter.js по умолчанию использует его центр масс в качестве точки привязки для позиции (x, y). Центр масс вычисляется автоматически на основе формы (shape) и может не совпадать с геометрическим центром спрайта или его точкой отображения (origin).
В примере мы видим два объекта: обычное изображение (align) и физический спрайт (banana), созданный из того же фрейма атласа. Если установить им одинаковые координаты, они не совпадут, потому что физическое тело "банан" будет позиционироваться относительно своего центра масс, а не левого верхнего угла изображения.
Решение: вычисление границ и смещения
Чтобы решить эту проблему, нужно вручную вычислить, насколько нужно сдвинуть физическое тело относительно желаемой точки. Алгоритм состоит из трех шагов.
**1. Определение ширины и высоты тела**
Мы можем получить реальные границы тела после его создания. Они хранятся в body.bounds.
const width = (banana.body.bounds.max.x - banana.body.bounds.min.x) / 2;
const height = (banana.body.bounds.max.y - banana.body.bounds.min.y) / 2;
Здесь width и height — это половины ширины и высоты ограничивающего прямоугольника (AABB) тела. Делим на 2, потому что bounds задают мин/макс точки в системе координат тела, и разница дает полный размер.
**2. Учет центра масс**
Центр масс (centerOfMass) — это вектор смещения от геометрического центра тела (который мы только что нашли) до той точки, вокруг которой Matter.js фактически вращает и позиционирует тело.
const xOffset = -(width - banana.centerOfMass.x) + width;
const yOffset = -(height - banana.centerOfMass.y) + height;
Эта формула вычисляет, насколько нужно сдвинуть тело по X и Y, чтобы его визуальный центр (рассчитанный через bounds) оказался в заданных координатах. По сути, это компенсация смещения, вносимого центром масс.
**3. Применение смещения к целевой позиции**
Финал — установка исправленной позиции с помощью setPosition.
const x = 500;
const y = 200;
banana.setPosition(x + xOffset, y + yOffset);
Альтернатива для статических тел
Если ваше тело статическое (неподвижное) и не будет вращаться, расчет можно упростить. Вместо использования body.bounds можно взять точку отображения (display origin) спрайта. Это особенно полезно для декораций или платформ.
// var width = banana.displayOriginX;
// var height = banana.displayOriginY;
Оставшаяя часть формулы со смещением центра масс останется прежней. Этот способ проще, но работает только когда тело не подвержено вращению, так как displayOrigin не меняется при повороте физической формы.
Сборка сцены и конфигурация Matter
Важно правильно настроить физический плагин Matter.js в конфигурации игры. Ключевые параметры:
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example,
physics: {
default: 'matter', // Используем Matter.js
matter: {
gravity: { y: 0 }, // Отключаем гравитацию для наглядности
debug: true // Включаем отладку, чтобы видеть синие контуры тел
}
}
};
Параметр debug: true крайне полезен при отладке позиционирования, так как он рисует контур физического тела поверх спрайта.
В методе preload загружаются необходимые ресурсы: атлас спрайтов и JSON-файл с полигональными формами (shapes) для точных коллайдеров.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('sheet', 'assets/physics/fruit-sprites.png', 'assets/physics/fruit-sprites.json');
this.load.json('shapes', 'assets/physics/fruit-shapes.json');
}
Физический спрайт создается с указанием загруженной формы:
this.matter.add.sprite(0, 0, 'sheet', 'banana', { shape: shapes.banana }).
Что попробовать дальше
Точное позиционирование физических тел в Matter.js требует понимания разницы между центром масс и визуальными границами. Используя body.bounds и centerOfMass, вы можете вычислить корректирующее смещение для любого тела. Для экспериментов попробуйте
- Применить эту технику к составным телам
- Написать универсальную функцию-хелпер для позиционирования
- Сравнить результат с отключенным
debugдля проверки идеального совпадения спрайта и коллайдера
