Hi! I would to propose a solution for a problem which take me maybe 12 hours to resolve.
How to make an Arcade Sprite use stairs. It is a pretty dirty solution in my opinion but it works, so…
Maybe you would know a better way to do it, and maybe some people would be happy to find my snippet. So I am open to suggestion and, if I can help someone else, it is a win win
Here is my level. As you can see, My player will have 3 stairs to go through.
I tried moveToObject, things like that, but nothing to do, it seemed to be impossible to use any built-in easy Phaser stuff to resolve this kind of usecase.
So I’ve found inspiration by reading the source code of the Rexrainbow’s pathfollower plugin.
Above each of my stairs, I draw a line with a direction property. It can be LEFT or RIGHT, it is the direction of the stairs relative to the start of the level (my player goes from left to right).
For each stairs line, I will create two things:
An Object that I will insert into a Group
A curve which represent this stairs line
Each time my player overlaps a line, I will split the related curve in many points and I will update my player’s position according to each points
This will create the illusion of climbing up/down the stairs.
When the player goes to a specific direction and collides a stair, according to my current player direction, I check the stair direction and decide how to make my player cross the stair.
So here is my code:
First in my scene
// into create()
// below I get all the stairs line
const stairs = this.tilemap.filterObjects("stairs_detection", obj => obj.name === "stairs_line");
// I create my player sprite, passing the stairs objects
this.player = new Player(this, this.start.x, this.start.y, stairs);
// no matter how I get the x, y coordinates tho
Then in my Player class
export default class Player extends Phaser.Physics.Arcade.Sprite {
constructor(
scene: Phaser.Scene,
x: number,
y: number,
stairs: Phaser.Types.Tilemaps.TiledObject[],
) {
super(scene, x, y, 'player', 'idle-1');
// I will check overlaping between the player and the stairs
this.stairsGroup = this.scene.physics.add.staticGroup();
// By default, my player is not using the stairs
this.isClimbing = false;
// I will iterate on each stairs line in order to fill my stairsGroup
this.stairs = stairs;
}
create() {
// Check the overlap between this player and my stairs
this.scene.physics.add.overlap(
this,
this.stairsGroup,
(_, stair) => {
// if player was not climbing, soooo now he will!
if (!this.isClimbing) {
// this curve has been setted few lines below
const curve = stair.getData('curve');
// the direction of my stairs as setted in Tiled
this.climbingDirection = stair.getData('direction');
// splitting my curve in many points
this.currentStairsClimbing = curve.getSpacedPoints(curve.getLength() / 7);
this.isClimbing = true;
}
},
undefined,
this,
);
if (this.stairs.length > 0) {
// for each stairs line I got from the scene
for (const stair of this.stairs!) {
// stair object is built in a strange way,
// I have to make some stupid calculations in order to get the real line start and end coordinates
const start = { x: stair!.x, y: stair!.y };
const poly = stair!.polyline![1];
const end = { x: stair!.x!+poly.x!, y: stair!.y!+poly.y! };
// create my curve line
const curve = new Phaser.Curves.Line(
new Phaser.Math.Vector2(start.x, start.y),
new Phaser.Math.Vector2(end.x, end.y)
);
// adding in my stairsGroup an invisible sprite based on the curve coordinates
const oneStair = this.stairsGroup.create(curve.getBounds().x, curve.getBounds().y, undefined, undefined, false);
oneStair.setScale(curve.getBounds().width / 32, curve.getBounds().height / 32);
oneStair.setOrigin(0);
oneStair.setDataEnabled();
// below setting the data I will use in the overlap callback above
const direction = stair.properties.find((prop) => prop.name === 'direction');
oneStair.setData('direction', direction.value);
oneStair.setData('curve', curve);
oneStair.body.width = curve.getBounds().width;
oneStair.body.height = curve.getBounds().height;
// if I do not refresh the group, its content is not usable (weird)
this.stairsGroup.refresh();
}
}
}
update() {
// this loop helps me to define when I stop to overlap my stairs
// if I do not, I can't pass this.isClimbing at false...
this.stairsGroup.children.entries.forEach(() => {
if (this.isClimbing) {
if (this.body.touching.none) {
this.isClimbing = false;
// details for those two things later
this.currentStairsClimbing = [];
this.currentstairsClimbingIndex = 0;
}
}
});
// move player according to left or right down
this.movePlayer();
}
private movePlayer() {
if (this.isMovable) {
if (this.left.isDown) {
this.goLeft = true;
} else {
this.goLeft = false;
}
if (this.right.isDown) {
this.goRight = true;
} else {
this.goRight = false;
}
if (this.goLeft) {
// flip my player to the left and change his internal direction
this.flipX = true;
this.direction = 'left';
// if he is climbing, I will process the climbing algorythm
if (this.isClimbing) {
this.climbOnLeftDirection();
} else {
// if he is not climbing, so he just go ahead
this.setVelocityX(-this.speedX);
}
} else if (this.goRight) {
this.flipX = false;
this.direction = 'right';
if (this.isClimbing) {
this.climbOnRightDirection();
} else {
this.setVelocityX(this.speedX);
}
} else {
this.setVelocityX(0);
}
}
}
// so here, my player goes to the right direction
private climbOnRightDirection() {
// if he is overlaping a LEFT stairs
if (this.climbingDirection === 'LEFT') {
this.climbFromRightPositionToLeftStairs();
}
// if he is overlaping a RIGHT stairs
else {
this.climbFromRightPositionToRightStairs();
}
}
// so here, my player goes to the left direction
private climbOnLeftDirection() {
if (this.climbingDirection === 'LEFT') {
this.climbFromLeftPositionToLeftStairs();
} else {
this.climbFromLeftPositionToRightStairs();
}
}
// my player goes to the right direction and is overlaping a RIGHT stairs
// in this direction, I will use stairs from first point to last
private climbFromRightPositionToRightStairs() {
this.setVelocityX(this.speedX / 2);
// I check if I'm not out of bound for the current stairs
if (this.currentstairsClimbingIndex + 1 <= this.currentStairsClimbing?.length) {
// I take the current stairs point at current index (default 0, the first one)
const currentPosition = this.currentStairsClimbing[this.currentstairsClimbingIndex];
// increase index
this.currentstairsClimbingIndex++;
// set player position
this.setPosition(currentPosition.x, currentPosition.y);
}
}
// my player goes to the right direction and is overlaping a LEFT stairs
// in this direction too, I will use stairs from first point to last
private climbFromRightPositionToLeftStairs() {
this.setVelocityX(this.speedX / 2);
if (this.currentstairsClimbingIndex + 1 <= this.currentStairsClimbing?.length) {
const currentPosition = this.currentStairsClimbing[this.currentstairsClimbingIndex];
this.currentstairsClimbingIndex++;
this.setPosition(currentPosition.x, currentPosition.y);
}
}
// In the two directions below, I will use stairs from the other side, from last point to first
// my player goes to the left direction and is overlaping a RIGHT stairs
private climbFromLeftPositionToRightStairs() {
this.setVelocityX(-this.speedX / 2);
if (this.currentstairsClimbingIndex + 1 <= this.currentStairsClimbing?.length) {
const currentPosition = this.currentStairsClimbing[this.currentStairsClimbing.length - 1 - this.currentstairsClimbingIndex];
this.currentstairsClimbingIndex++;
this.setPosition(currentPosition.x, currentPosition.y);
}
}
// my player goes to the left direction and is overlaping a LEFT stairs
private climbFromLeftPositionToLeftStairs() {
this.setVelocityX(-this.speedX / 2);
if (this.currentstairsClimbingIndex + 1 <= this.currentStairsClimbing?.length) {
const currentPosition = this.currentStairsClimbing[this.currentStairsClimbing.length - 1 - this.currentstairsClimbingIndex];
this.currentstairsClimbingIndex++;
this.setPosition(currentPosition.x, currentPosition.y);
}
}
}
For more details on the climbFrom stuff, here one image for each case:
climbFromRightPositionToRightStairs
climbFromRightPositionToLeftStairs
climbFromLeftPositionToLeftStairs
climbFromLeftPositionToRightStairs
Open to any question, I think it is not very clear at all