О чем этот пример
При создании игр с нестандартной геометрией объектов часто возникает задача связать графический полигон с физическим телом. Пример из бага #6178 наглядно показывает две стратегии решения: прямую привязку и корректировку вершин. Понимание этих подходов избавит вас от "плывущих" или неправильно отображающихся фигур и сделает работу с физикой предсказуемой.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
this.matter.world.setBounds(0, 0, 800, 600);
this.matter.add.mouseSpring({ stiffness: 0.1 });
const verts = [
{ x: 50, y: 0 },
{ x: 100, y: 60 },
{ x: 0, y: 75 }
];
// This will work properly because the vertices are
// centered around 0x0
const body = this.matter.add.fromVertices(300, 300, verts);
const poly = this.add.polygon(body.position.x, body.position.y, verts, 0x8d8d8d);
// Tell the Game Object to use the existing body, don't create a new one
this.matter.add.gameObject(poly, body, false);
// In this case the verts are not centered, so we cannot
// use them to create the Polygon from. Instead we create
// the body first and use _those_ verts to create the Polygon
const verts2 = [
{ x: 0, y: -50 },
{ x: 50, y: 10 },
{ x: -50, y: 25 }
];
const body2 = this.matter.add.fromVertices(500, 300, verts2);
const polyVerts = [];
const bx = body2.position.x;
const by = body2.position.y;
const cx = body2.centerOffset.x;
const cy = body2.centerOffset.y;
body2.vertices.forEach(vert => {
polyVerts.push({ x: vert.x - bx + cx, y: vert.y - by + cy });
});
const poly2 = this.add.polygon(bx, by, polyVerts, 0x8d8d8d);
// Account for the fact that in this set of verts, the
// origin isn't the center
poly2.setDisplayOrigin(cx, cy);
this.matter.add.gameObject(poly2, body2, false);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example,
physics: {
default: 'matter',
matter: {
gravity: { y: 0 },
debug: {
showAngleIndicator: true,
angleColor: 0xe81153,
showVelocity: true,
velocityColor: 0x00aeef,
showCollisions: true,
collisionColor: 0xf5950c,
showBody: true,
showInternalEdges: true,
showPositions: true,
positionSize: 4,
positionColor: 0xe042da,
showJoint: true,
jointColor: 0xe0e042,
jointLineOpacity: 1,
jointLineThickness: 2,
pinSize: 4,
pinColor: 0x42e0e0,
springColor: 0xe042e0,
anchorColor: 0xefefef,
anchorSize: 4,
showConvexHulls: true,
hullColor: 0xd703d0
}
}
}
};
const game = new Phaser.Game(config);
Проблема центрирования вершин
Физический движок Matter.js ожидает, что вершины полигона (verts) заданы относительно его центра масс (точки 0,0). Если вы передадите в fromVertices() координаты "как есть", тело может быть создано, но его визуальное представление и физическая модель будут расходиться.
В примере первый полигон создается из вершин, уже центрированных вокруг нуля. Это идеальный случай.
const verts = [
{ x: 50, y: 0 },
{ x: 100, y: 60 },
{ x: 0, y: 75 }
];
Простой случай: готовые центрированные вершины
Когда вершины изначально заданы относительно центра, процесс становится тривиальным. Создаем физическое тело, затем графический полигон с теми же вершинами и связываем их. Ключевой момент — передача false в gameObject(), чтобы Phaser не создавал новое тело.
// Создаем тело из вершин
const body = this.matter.add.fromVertices(300, 300, verts);
// Создаем графический полигон с теми же вершинами
const poly = this.add.polygon(body.position.x, body.position.y, verts, 0x8d8d8d);
// Связываем объект с существующим телом
this.matter.add.gameObject(poly, body, false);
Сложный случай: вершины не центрированы
Часто вершины заданы в абсолютных или произвольных координатах. В примере verts2 заданы не вокруг нуля. Если использовать их напрямую для графики, полигон нарисуется не там, где находится тело.
const verts2 = [
{ x: 0, y: -50 },
{ x: 50, y: 10 },
{ x: -50, y: 25 }
];
Поэтому используется обходной путь: сначала создаем тело, а для графики используем уже обработанные движком вершины тела (body2.vertices).
Корректировка вершин для графики
Вершины, хранящиеся в созданном теле (body2.vertices), находятся в мировых координатах. Чтобы нарисовать полигон, нужно преобразовать их в локальные координаты относительно позиции тела, учитывая смещение центра (centerOffset).
const polyVerts = [];
const bx = body2.position.x;
const by = body2.position.y;
const cx = body2.centerOffset.x;
const cy = body2.centerOffset.y;
body2.vertices.forEach(vert => {
polyVerts.push({ x: vert.x - bx + cx, y: vert.y - by + cy });
});
Вычитание bx, by переводит мировые координаты в локальные. Добавление cx, cy компенсирует смещение центра тела, чтобы полигон отрисовался правильно.
Связывание и настройка отображения
После подготовки вершин создается графический полигон. Важный шаг — установить для него точку отображения (displayOrigin), совпадающую со смещением центра тела. Это гарантирует, что вращение и позиция графики будут синхронизированы с физикой.
const poly2 = this.add.polygon(bx, by, polyVerts, 0x8d8d8d);
poly2.setDisplayOrigin(cx, cy);
this.matter.add.gameObject(poly2, body2, false);
Метод setDisplayOrigin(cx, cy) смещает точку привязки спрайта/полигона, что особенно важно для корректного вращения несимметричных фигур.
Что попробовать дальше
Для надежной связи полигональной графики с Matter.js телом всегда проверяйте, центрированы ли исходные вершины. Если да — используйте простой путь. Если нет — создавайте тело первым, а для графики берите и преобразовывайте вершины из body.vertices. Экспериментируйте: попробуйте пропустить setDisplayOrigin и посмотрите на вращение объекта, или создайте сложный многоугольник и проверьте, как centerOffset влияет на его поведение.
