Arcade physics, 2 axes movement, classes and collisions issues (jittery, stuck etc.)

Hi everyone,

these days I’ve been playing again with Phaser, and trying to implement a simple top-down game where the player would be controller with an analog controller (using a Xbox one, but assuming they would probably all pretty similar).

I have been having issues with movement when using two axes at the same time, especially down + left/right when there is a horizontal line of walls below (jumpiness), and up + left/right when there is a vertical line of walls on the side (getting stuck).

I am not sure that my description is super clear, so I made a quick capture:

YT link


The code

Player class:

class Player extends Phaser.Physics.Arcade.Sprite {

  constructor(scene, x, y, name) {
    super(scene, x, y, name);
    scene.add.existing(this);
    scene.physics.add.existing(this);

    // Set physics properties
    this.body.setSize(16, 10); // Set the body size
    this.body.setOffset(0, 6); 
    this.body.setCollideWorldBounds(true); // Prevent going outside the world bounds

    // Player physics-related properties
    this.deadzone = 0.15; // Deadzone threshold for input sensitivity
    this.speedFactor = 100; // Increased speed factor for faster movement
  };

  movePlayer(x, y) {
    const magnitude = Math.sqrt(x * x + y * y);
    // If the magnitude is below the deadzone, stop the player (set velocity to 0)
    if (magnitude < this.deadzone) {
      this.body.setVelocity(0, 0); // Reset velocity when no movement input
      return;
    }

    // Normalize diagonal movement (if both x and y are non-zero)
    if (x !== 0 && y !== 0) {
      x = x / Math.sqrt(2); // Normalize x-axis
      y = y / Math.sqrt(2); // Normalize y-axis
    }

    // Apply speed factor and set the velocity
    this.body.setVelocity(x * this.speedFactor, y * this.speedFactor);
  }
}

export { Player };

Wall class:

class Wall extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y, name) {
    super(scene, x, y, name);
    scene.add.existing(this);
    scene.physics.add.existing(this, true);
    this.body.immovable = true;
    this.zRatio = 100;
    this.body.setSize(16, 24);
    this.body.setOffset(0, 8);
    this.zOrder = y * this.zRatio;
    this.setDepth(this.zOrder);
  };
};
export { Wall };

And in the main game scene, I call these like this (minus imports etc.):

export class Game extends Scene {

  constructor () {
    super('Game');
  };

  create() {
    // Quick and dirty array to test with multiple walls
    // The final thing will probably use a json map from a server
    this.tilesGroup = this.physics.add.staticGroup();
    this.wallArray = [
      {x: 48, y:80, texture:'wall-001'},
      {x: 64, y:80, texture:'wall-001'},
      {x: 80, y:80, texture:'wall-001'},
      {x: 128, y:80, texture:'wall-001'},
      {x: 128, y:96, texture:'wall-001'},
      {x: 128, y:112, texture:'wall-001'}
    ];
    for(let i=0; i<this.wallArray.length; i++) {
      let tile = new Wall(this, this.wallArray[i].x, this.wallArray[i].y, this.wallArray[i].texture);
      this.tilesGroup.add(tile);
    };

    this.player = new Player(this, 50, 50, 'playerSprite');
    this.physics.add.collider(this.player, this.tilesGroup);

    this.hasGamepad = false;

    this.cameras.main.setBounds(0, 0, 800, 600);
    this.cameras.main.startFollow(this.player, true, 0.1, 0.1);
  };

  update() {
    if (this.input.gamepad.total === 0) {
      // No gamepad found
      return;
    };
    this.hasGamepad = true;
    const pad = this.input.gamepad.getPad(0);
    if (pad.axes.length) {
      this.player.movePlayer(pad.axes[0].getValue(), pad.axes[1].getValue());
      this.player.update();
    };
  }

  changeScene() {
    this.scene.start('GameOver');
  };

};

Not sure what is causing the issue… I tried to use dot product to modify the Player velocity but ended up messing things up even more :sweat_smile: … (also it seems to me that it is what the engine is proabably already doing under the hood?).
Thanks a lot!

If the wall bodies overlap or adjoin it will cause problems with collisions.

You would need to use, e.g, wall.body.checkCollision.down = false to remove the interior edges.

Thank you for your reply!

Any idea how I can do that? I tried something like the following.
I moved the collider code to the Player class, and added a callback, to have everything in one place.

scene.physics.add.collider(this, this.wallGroup, (player, wall) => {
      this.adjustWallCollision(wall);
    });

Then checking where is the Player blocked, on cases that cause me issues:

  adjustWallCollision(player, wall) {
    const isCollidingDown = player.body.blocked.down;
    const isCollidingLeft = player.body.blocked.left;
    const isCollidingRight = player.body.blocked.right;

    // If the player is colliding with the bottom (down), disable left/right collision on adjacent walls
    if (isCollidingDown) {
      this.disableLeftRightCollisions(wall);
    }

    // If the player is colliding with the left or right, disable up collision on the wall directly above
    if (isCollidingLeft || isCollidingRight) {
      this.disableUpCollisionAbove(wall);
    }
  }

Then trying to disable collisions for walls adjacent of the one already colliding, which I assumed might be the ones causing issues. First when the player is moving horizontally above a horizontal wall (left/right + down). For some reason its super smooth when the player is under the wall, it slides smoothly along it.

  disableLeftRightCollisions(wall) {
    this.wallGroup.getChildren().forEach(otherWall => {
      if (wall === otherWall) return; // Current actually colliding wall
      if (Math.abs(wall.y - otherWall.y) < 10) { // Small tolerance for matching y positions
        if (otherWall.x < wall.x) {
          // Wall to the left of the current wall
          otherWall.body.checkCollision.right = false;
        } else if (otherWall.x > wall.x) {
          // Wall to the right of the current wall
          otherWall.body.checkCollision.left = false;
        }
      }
    });
  }

then when the player is moving vertically, on the left/right side of a vertical wall (top + left/right). Here too, when the player is going down, no issue, only when it is going up.

  disableUpCollisionAbove(wall) {
    this.wallGroup.getChildren().forEach(otherWall => {
      if (wall === otherWall) return; // Current actually colliding wall
      if (Math.abs(wall.x - otherWall.x) < 10) { // Small tolerance for matching x positions
        if (otherWall.y < wall.y) {
          // Wall directly above the current wall
          otherWall.body.checkCollision.up = false;
        }
      }
    });
  }

…and it doesn’t work :sob:.
Not sure where to go from there…

You would set all the checkCollision values once, after creating the walls, not during the collisions. An alternative is using a tilemap instead.