Collider Process Callback Not Working As Expected

I posted this on Reddit, but I wasn’t sure how active that community is, so I figured I’d also try here.

I’m trying to understand how the process callback works in Arcade physics, and I think I’m just doing something dumb. Let’s say I have an image that’s actually a triangle… instead of using matter physics for this, I’m trying to use the process callback to determine if the player is actually colliding with the triangle (not its default square collider).

Here is my rudimentary fiddle… the player is the blue square, and you can use the up arrow key to move it up into the triangle… just move directly up, don’t hit any other keys. You’ll see that the console posts there is no collision, but the coordinates that get posted make it seem like there should be a collision, so maybe I just don’t understand what the contains method does.

I also tried messing around with Phaser.Geom.Intersects.RectangleToTriangle (fiddle), and I was able to detect that the player was colliding with the triangle properly, but the issue I’m seeing is that the player.body.blocked does not report any direction is being blocked, so I’m assuming some internal code isn’t preventing the update function from setting the velocity. I’m assuming a lot here because I’m unfamiliar with how all this works, so forgive me if I’m totally off.

Anyway, how do I fix this so that my player collides with the triangle (not the triangle’s square collision) and is prevented from moving further? I would rather not use matter physics, as the majority of my sprites in the game are square, and that’ll only add complexity.

const Velocity = 80;

class Example extends Phaser.Scene {
  constructor() {
    super();
  }

  preload() {
    this.load.image('tile', 'https://i.imgur.com/TttprwB.png');
    this.load.image('player', 'https://i.imgur.com/zJVFvQN.png')
  }

  create() {
    const tile = this.tile = this.physics.add.staticImage(128, 64, 'tile')
    const player = this.player = this.physics.add.image(128, 192, 'player');
    const triangle = new Phaser.Geom.Triangle(tile.getTopLeft().x, tile.getTopLeft().y, tile.getTopRight().x, tile.getTopRight().y, tile.getBottomRight().x, tile.getBottomRight().y);
    this.cursor = this.input.keyboard.createCursorKeys();
    this.physics.add.existing(player);
    this.physics.add.collider(tile, player, () => {}, (tile, player) => {
      const topLeft = triangle.contains(player.getTopLeft().x, player.getTopLeft().y);
      const topRight = triangle.contains(player.getTopRight().x, player.getTopRight().y);
      console.log(triangle, player.getTopRight(), topLeft, topRight);
      return topLeft || topRight;
    })
  }

  update() {
    const {
      cursor
    } = this;
    let velocityX = 0;
    let velocityY = 0;
    let animation;
    if (cursor.left.isDown) {
      velocityX = -Velocity;
    } else if (cursor.right.isDown) {
      velocityX = Velocity;
    }
    if (cursor.down.isDown) {
      velocityY = Velocity;
    } else if (cursor.up.isDown) {
      velocityY = -Velocity;
    }
    this.player.setVelocityX(velocityX);
    this.player.setVelocityY(velocityY);
  }
}

const config = {
  type: Phaser.AUTO,
  parent: 'phaser-example',
  width: '100%',
  height: '100%',
  pixelArt: true,
  physics: {
    default: 'arcade',
    arcade: {
      debug: true
    }
  },
  scene: [Example]
};
const game = new Phaser.Game(config);

For the contains method on the Phaser.Geom.Triangle class, this method only seems to check if the provided point is contained within the triangle and excludes if the point is on one of the triangle edges. I am not sure why this is the case, but from the docs, it links to this article with more information on the math that is used for this check: Point in triangle test.

Based on this, you could modify the logic in the processCallback function to include checks for the edges. A partial example:

if (triangle.x2 === triangle.x3 && triangle.x2 === player.getTopRight().x) {
  return true;
}
const topLeft = triangle.contains(player.getTopLeft().x, player.getTopLeft().y);
const topRight = triangle.contains(player.getTopRight().x, player.getTopRight().y);

If you add that, to your fiddle, then when you try to move the box directly up, the movement should be blocked.

demo

For doing a full collision with the triangle, this becomes a little more complicated since the collider is using the default box collider. What happens normally with the Arcade Physics collision is, when you provide a processCallback function, Phaser will run that function and if the return value is true, Phaser will try to separate the two Arcade Physics Bodies. In the example above, and in your 2nd fiddle, we see that this works when the two objects have an initial collision, and Phaser will push the object away from the triangle

In the cases were the processCallback function returns false, Phaser will not attempt to separate the two bodies, which is why the player is able to move over the part of the tile that is not the triangle. Once this happens, the original box collider of the tile and player is overlapping, and once this happens, Phaser is not able to separate the two Physics bodies, which is why the player can now move through the triangle shape.

demo2

To handle this, you could add custom logic to the collideCallback to separate the game objects, since the collideCallback will only be invoked when your processCallback function returns true.

A basic example:

 this.physics.add.collider(
      tile,
      player,
      () => {
        player.x -= 5;
        player.y += 5;
      },
      (tile, player) => {
        // your existing code for the processCallback function
      }
    );

demo3

Thank you very much for the in-depth response; it helped clear up a lot of my issues. It almost seems silly to not use Matter physics at this point, as the process callback will be more complex, and I really don’t know how much more efficient it is (or less efficient).

This is what I assumed was happening, so thank you for pointing this out!