javascriptphaser-frameworkmatter.js

Phaser.js: mismatch between coordinates for graphics and Matter.js body


I'm trying to mathematically create some shape graphics in Phaser.js, and to attach matching Matter.js physics bodies to them. I'm finding that the coordinate systems used for these two things don't line up exactly, in a way I can't quite describe.

I've cooked up a codepen example that demos the code below.

As near as I can tell, the Matter.js polygons are rendered appropriately. But the graphics polygons are offset, the 'centre' of the polygon is treated as its bottom left corner and the entire graphic is offset. I haven't been able to find any configuration in Phaser's GameObject or Shape classes that would let me configure this, setOrigin doesn't seem to have any effect.

Have I missed something? Is there some trick to generating coordinates which can be used with both a Phaser polygon and also a Matter polygon?

<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.87.0/phaser.min.js"></script>
<div id="phaser-container"></div>
class MyScene extends Phaser.Scene {
  constructor() {
    super('MyScene')
  }

  create() {
    const segments = 100
    const angleStep = (Math.PI * 2) / segments
    const radius = 200
    const x = 852 / 2
    const y = 640 / 2

    for (let i = 0; i < segments; i++) {
      const angle = i * angleStep
      const startAngle = angle + angleStep / 2
      const endAngle = angle - angleStep / 2

      const centreX = Math.cos(angle) * radius
      const centreY = Math.sin(angle) * radius
    

      const vertices = [
        {
          x: Math.cos(startAngle) * (radius - 10) - centreX,
          y: Math.sin(startAngle) * (radius - 10) - centreY
        },
        {
          x: Math.cos(startAngle) * (radius + 10) - centreX,
          y: Math.sin(startAngle) * (radius + 10) - centreY
        },
        {
          x: Math.cos(endAngle) * (radius + 10) - centreX,
          y: Math.sin(endAngle) * (radius + 10) - centreY
        },
        {
          x: Math.cos(endAngle) * (radius - 10) - centreX,
          y: Math.sin(endAngle) * (radius - 10) - centreY
        },
      ]

      const poly = this.add.polygon(x + centreX, y + centreY, vertices, 0x1d4ed8)

      this.matter.add.gameObject(poly, {
        shape: {
          type: 'fromVertices',
          verts: vertices,
        },
        isStatic: true,
      })

      this.add.circle(x + centreX, y + centreY, 1, 0xffff00)
    }
  }
};

const game = new Phaser.Game({
  type: Phaser.CANVAS,
  width: 852,
  height: 640, 
  scene: MyScene,
  parent: 'phaser-container',
  backgroundColor: 0x000033,
  physics: {
    default: 'matter',
    matter: {
      gravity: { x: 0, y: 0 },
      debug: {
        showBounds: false,
      },
    }
  },
});

Solution

  • This might have to do with the this infomation from the documentation (link to the documentation).

    ...By default the x and y coordinates of this Shape refer to the center of it. However, depending on the coordinates of the points provided, the final shape may be rendered offset from its origin.

    Note: The method getBounds will return incorrect bounds if any of the points in the Polygon are negative.

    Nevertheless a possible solution (depending on your usecase) is simply drawing the shapes on a graphics object, using Phaser.Geom.Polygon and creating the matter objects with the fromVertices function.

    Short Demo:
    (based on your demo)

    class DemoScene extends Phaser.Scene {
      create() {
        const segments = 100;
        const angleStep = (Math.PI * 2) / segments;
        const radius = 100;
        const x = config.width / 2;
        const y = config.height / 2;
    
        let graphics = this.add.graphics(x, y);
    
        for (let i = 0; i < segments; i++) {
          const angle = i * angleStep;
          const startAngle = angle + angleStep / 2;
          const endAngle = angle - angleStep / 2;
    
          const centreX = Math.cos(angle) * radius;
          const centreY = Math.sin(angle) * radius;
    
          const vertices = [
            {
              x: Math.cos(startAngle) * (radius - 10) - centreX,
              y: Math.sin(startAngle) * (radius - 10) - centreY,
            },
            {
              x: Math.cos(startAngle) * (radius + 10) - centreX,
              y: Math.sin(startAngle) * (radius + 10) - centreY,
            },
            {
              x: Math.cos(endAngle) * (radius + 10) - centreX,
              y: Math.sin(endAngle) * (radius + 10) - centreY,
            },
            {
              x: Math.cos(endAngle) * (radius - 10) - centreX,
              y: Math.sin(endAngle) * (radius - 10) - centreY,
            },
          ];
    
          // this could refeactored just for the demo, quick and dirty
          var shapePoly = new Phaser.Geom.Polygon(
            vertices.map((p) => {
              return { x: p.x + x + centreX, y: p.y + y + centreY };
            })
          );
    
          // Draw the Boxes
          graphics.lineStyle(1, 0xff00ff, 1.0);
          graphics.fillStyle(0xffffff, 1.0);
          graphics.fillPoints(shapePoly.points);
    
          // Create Physics Body
          this.matter.add.fromVertices(x + centreX, y + centreY, vertices, {
            isStatic: true,
          });
    
          this.add.circle(x + centreX, y + centreY, 1, 0xff0000);
        }
      }
    }
    
    var config = {
      width: 540,
      height: 180,
      physics: {
        default: 'matter',
        matter: {
          gravity: { x: 0, y: 0 },
          debug: {
            showBounds: false,
          },
        },
      },
      scene: DemoScene,
    };
    
    new Phaser.Game(config);
    
    console.clear();
    document.body.style = 'margin:0;';
    <script src="//cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>