Vertically moving platforms
The previous post dealt with platforms that are moving horizontally. Now we come to deal with vertical moving platform - and frankly, I ended up spending many hours getting bogged down in trying to figure out the "friendliness" of the tween manager and the physics engine (they are not).
My personal conclusion from this experience is that one should avoid where possible from moving things around the screen by directly adjusting the x/y coordinates (as is the typical case in using tweens) and rely on the physics engine to do its job (or use Matter.js).
"Relying on the arcade physics engine as much as possible" approach
Let's start by creating a vertically platform which moves with physics body.velocity in the Y direction.
this.platforms = this.physics.add.group({
immovable: true,
allowGravity: false
});
this.platforms.create(600,400,'ground').setScale(0.3, 0.5).setVelocityY(-100);
this.platforms.create(300,100,'ground').setScale(0.3, 0.5).setVelocityY(100);
Then create a function that is called from the update loop to switch the direction of the platform when it reaches near the top and bottom.
updatePlatforms() {
this.platforms.children.each(elevator => {
if (elevator.y >= 500) {
elevator.body.setVelocityY(-100);
} else
if (elevator.y <= 100) {
elevator.body.setVelocityY(100);
}
})
If you now place Mr Dude on top of these moving platforms, it seems to work fine when the platform is moving upwards. However, Mr Dude "floats" when the platform switches direction at the top. And if the gravity setting isn't strong enough, he will float down slowly in the air when the platform is moving down.
This is not altogether surprising if you think about what the physics engine is doing. We need a way to "fix" the relative position of Mr Dude when he is on the platform.
Move Mr Dude's physics body by the platform's DeltaX and DeltaY
There are several different ways of doing this. I think the "cleanest" (but not necessarily the simplest) way to do this is to adjust the position of Mr Dude by the displacement of the platform, by reference to the DeltaX and DeltaY of the platform.
deltaX() and deltaY()
deltaY() method is described as follows:
The change in this Body's vertical position from the previous step. This value is set during the Body's update phase.
As a Body can update multiple times per step this may not hold the final delta value for the Body. In this case, please see the deltaYFinal method.
The above code includes a similar line for the X axis. In this particular example where the platform only moves vertically, this is not necessary.
I have tried the same code with deltaYFinal method, but I could not see any difference.
I carry out this "adjustment" within the Mr Dude class as follows (red highlighted code), which is executed when the isOnPlatform flag is set to true (which is executed upon collision with the platform). What it does it to shift Mr Dude's physic body by the displacement of the platform's physics body each frame.
class Dude extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y) {
super(scene, x, y, 'dude');
this.scene = scene;
scene.add.existing(this);
scene.physics.add.existing(this);
this.setCollideWorldBounds(true);
this.isOnPlatform = false;
this.currentPlatform = null;
this.cursors = this.scene.input.keyboard.createCursorKeys();
}
update() {
this.handleInput();
if (this.isOnPlatform && this.currentPlatform) {
this.body.position.x += this.currentPlatform.body.deltaX();
this.body.position.y += this.currentPlatform.body.deltaY();
}
if (this.isOnPlatform && this.fallenOff(this, this.currentPlatform)) {
this.isOnPlatform = false;
this.currentPlatform = null;
this.body.allowGravity = true;
}
}
fallenOff(body1, body2) {
return (
body1.body.right <= body2.body.position.x ||
body1.body.position.x >= body2.body.right
);
}
Don't forget to turn gravity off!
When you start adjusting the x/y coordinate of Mr Dude like this, you need to turn off the gravity - the physics engine seems to get confused if it has to deal with the impact of gravity as well as "somebody else" adjusting the y-coordinate of Mr Dude. If you leave gravity turned on, Mr Dude will fall through the platform, when the platform switches direction at the top of its path.
Turning off gravity can be carried out by creating a callback function of the collider, as follows.
this.physics.add.collider(this.player, this.platforms, this.ridePlatform);
In the callback function, in order to be able to tell whether Mr Dude is "riding" a platform, a flag "isOnPlatform" is set to true, and the platform on which Mr Dude is standing on is assigned to Mr Dude's property, currentPlatform, before turning off the effect of gravity.
ridePlatform(dude, platform) {
if (platform.body.touching.up && dude.body.touching.down) {
dude.isOnPlatform = true;
dude.currentPlatform = platform;
dude.body.setAllowGravity(false);
dude.body.setVelocityY(0);
}
Checking when Mr Dude is off the platform
Of course, with the above, you need some way to "un-fix" Mr Dude from the platform.
This you can do either when Mr Dude jumps, or (ii) when he walks off the platform (checked by fallenOff method).
It is interesting to note that once Mr Dude lands on a platform, the collision detection fires once, but then "blocked.none" and "touching.none" properties remain true, as long as Mr Dude remains on the platform - I guess - because his physics body is "fixed" at the relative "separated" position from the platform.
Unexpected change in Mr Dude's body.position behaviour from Post 1.0
As an aside, I noticed a bizarre phenomenon as I was playing around with the jumping speed of Mr Dude. As clearly seen in the picture at the top of this post, I noticed that, even as Mr Dude was "standing still" on the ground, his y-position was "jittering", and his body.facing property would be 12 (which signifies down) even after moving left or right. Having spent a considerable time scratching my head, I came to the conclusion that this happens when the gravity is relatively weak.
You can see what I mean, by changing the game gravity config to, say gravity: { y: 500} from the current 300.
Even more curiously, even at a gravity setting of 300, the phenomenon only occurs at specific y-position of the ground. For example, if I change
this.ground.create(400, 568, 'ground').setScale(2).refreshBody(); // this is the ground
to
this.ground.create(400, 568+32, 'ground').setScale(2).refreshBody(); // this is the ground
the problem disappears...very strange.
Using Tweens to move the platform
Things become altogether more difficult when you use tweens to move the platform around. The tween is directly changing the x/y position of the sprite, bypassing the physics engine, hence the deltaY values referenced in the above code example is not udpated. In fact, exactly this problem is discussed in Phaser Discourse here, where it has apparently been resolved in two different ways:
create your own deltaX and deltaY values within the onUpdate callback of the tween, and
using a path follower (which is also a tween under the hood - as we have seen) and referencing pathDelta.
Creating your own deltaX and deltaY in the tween onUpdate call back
This did not work as expected.
The manually maintained delta.y appears to be firing reasonable looking values, but Mr Dude moves slightly slower than the platform. Unless, I have made a typo somewhere in my code, I can only assume that the game loop is running at a slightly different pace from the tween update (by looking at the behaviour of Mr Dude relative to the platform, the game loop appears to be running slower than the tween loop).
Bizarrely, if I set the FPS to 120 (sticking in fps: 120 within the game config - in my Codepen example code, I have commented it out), Mr Dude appears to follow the platform!! There is still a slight problem though. When the platform is moving up, Mr Dude is standing flush against the platform. However, when the platform is moving down, Mr Dude is "floating" by a couple of pixels above the platform. I suspect this is because of the order in which Mr Dude's position is updated vs the update of the platform's delta - unfortunately, I have not been able to figure out a work around to correct this.
A "hackey" solution
Of course, the simplest way of fixing Mr Dude to the platform is simply to update Mr Dude's y-coordinate by reference to the y-coordinate of the platform.
this.body.y = this.lockedTo.body.y - this.body.height;
This is fine for vertical platforms. However, if you want to ride platforms that move also in the x-direction, there will be challenges allowing Mr Dude to move left and right.
Using Path Follower and Path Delta
As per the suggestion by Samme, I create the platform as a pathfollower object, like so.
this.path = new Phaser.Curves.Path(300,500).lineTo(300,100);
this.platform = this.add.follower(this.path, 300,500, 'ground');
this.physics.add.existing(this.platform);
this.platform.setScale(0.3,0.5);
this.platform.body.setAllowGravity(false);
this.platform.body.setImmovable(true);
const loop = this.game.loop;
this.platform.startFollow({
duration: 5000,
yoyo: true,
repeat: -1,
callbackScope: this,
onUpdate: () => {
// Scale `pathDelta` to a 1-second velocity vector, for correct collisions.
this.platform.body.velocity.copy(this.platform.pathDelta).scale(1000 / loop.delta);
}
})
This is in effect "force feeding" the physics engine with a velocity, by adjusting the pathDelta of the path follower object by prorotating by the time elapsed since last game loop update. This seems to kick the physics engine to update its various properties, including DeltaX() and DeltaY().
When you run the code, you will notice that the DeltaY values calculated by the physics engine is slightly "larger" than the PathDelta.y figure of the pathfollower, confirming the difference in the loop speeds.
By updating Mr Dude's position with the DeltaY, Mr Dude successfully rides the platform...nearly!
I say nearly, because as time goes by, Mr Dude seems to "float" above the platform, with the gap very very slowing expanding...I can only assume that this is due to rounding? in the conversion of pathDelta to physics.body velocity...and I have no idea how to fix it.
My own pathfollower
Of course, in my prior exploration of path followers (here), I ended up developing my own version of the path follower object. The motive for doing so was to be able to use arcade Physics engine to move the sprite around, as opposed to using tweens to set the x/y coordinate directly; in other words, arcade physics engine is in control.
Taking the previous example code and replacing the path follower object with my customised path follower object, it seems to work fine, as you would expect.
Here is an example using my "WayPoints" path follower, which moves the platform between 6 way points.
Key references
Platformer Ladders and gravity - Phaser 3 - Phaser (talks about the difference between Physics engine loop and the game update loop)
Comments