Phaser Coding Tips 4
Phaser Coding Tips is a free weekly email - subscribe here.
Welcome!
This week we carry on building on our set of platformer game tools. Last time we created platforms with specific friction, but this week we're creating platforms you can ride: commonly known as "cloud platforms".
Get the source
I'm only going to highlight the most important parts of the code here. So please always look at the source first. If you've questions about something I didn't cover then ask on the forum. Run / Edit the code on jsbin or codepen or clone the phaser-coding-tips git repo.
"Cloud" Platforms
A "Cloud platform" is a special kind of platform in your level. It has two distinct characteristics: First it follows its own fixed motion path. And secondly, the player can ride it. For our purposes here is our cloud platform:
It doesn't have to look like an actual cloud of course! In the Mario games they take on all kind of visual forms, but it helps get the message across for our needs.
CloudPlatform
Because we want to have more than just one of these types of platform they are a good candidate for a custom object: CloudPlatform
. This is a special object that extends Phaser.Sprite
. Here is its constructor:
CloudPlatform = function (game, x, y, key, group) {
if (typeof group === 'undefined') { group = game.world; }
Phaser.Sprite.call(this, game, x, y, key);
game.physics.arcade.enable(this);
this.anchor.x = 0.5;
this.body.customSeparateX = true;
this.body.customSeparateY = true;
this.body.allowGravity = false;
this.body.immovable = true;
this.playerLocked = false;
group.add(this);
};
You pass in the same parameters as if you were defining a normal Sprite. The difference is that it enables itself for physics, defines an anchor and sets some body properties up. As all CloudPlatforms need the same settings it saves some time to do this here. Here is how we create one:
var cloud1 = new CloudPlatform(this.game, 300, 450, 'cloud-platform', this.clouds);
So far, so good. But they're still just normal sprites. What we need now is to define their motion.
addMotionPath
The CloudPlatform
object has a special method called addMotionPath
. It expects an array filled with path data:
cloud1.addMotionPath([
{ x: "+200", xSpeed: 2000, xEase: "Linear", y: "-200", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "-200", xSpeed: 2000, xEase: "Linear", y: "-200", ySpeed: 2000, yEase: "Sine.easeOut" },
{ x: "-200", xSpeed: 2000, xEase: "Linear", y: "+200", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "+200", xSpeed: 2000, xEase: "Linear", y: "+200", ySpeed: 2000, yEase: "Sine.easeOut" }
]);
What it then does is build an internal set of tweens that moves the platform based on its starting coordinates and the data given above. The above code moves the platform in an almost circular path. The faded sprites showing the path the platform took: Note how the coordinates are given as relative values, i.e. "+200" - this means they are all based off the platforms starting position. If you need to tweak the position you don't have to redo all the motion values too. Here's an example of a vertically moving platform:
cloud2.addMotionPath([
{ x: "+0", xSpeed: 2000, xEase: "Linear", y: "+300", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "-0", xSpeed: 2000, xEase: "Linear", y: "-300", ySpeed: 2000, yEase: "Sine.easeOut" }
]);
By default the CloudPlatform
runs on a loop. So in the example above it moves down by 300 pixels (using Sine.easeIn) and then moves back up by 300 pixels as well.
Locking the Player
The cloud platforms are checked in the update
loop via a normal collide call:
this.physics.arcade.collide(this.player, this.clouds, this.customSep, null, this);
The difference is that we've got a collision callback handler customSep
which looks like this:
customSep: function (player, platform) {
if (!this.locked && player.body.velocity.y > 0)
{
this.locked = true;
this.lockedTo = platform;
platform.playerLocked = true;
player.body.velocity.y = 0;
}
},
We test the velocity. If the player is in a jump and falling down (> 0) and not currently locked to a platform then we tell the game the player is now locked. The platform he landed on is stored in lockedTo
and his velocity is reset. Why do we lock him? The reason is that the platform is being moved by a tween, not by any kind of velocity or physics operation. It's following a fixed path, so we need to ensure that the player is repositioned to match that path every frame. This is done in the preRender
function:
preRender: function () {
if (this.game.paused)
{
// Because preRender still runs even if your game pauses!
return;
}
if (this.locked || this.wasLocked)
{
this.player.x += this.lockedTo.deltaX;
this.player.y = this.lockedTo.y - 48;
if (this.player.body.velocity.x !== 0)
{
this.player.body.velocity.y = 0;
}
}
}
...
The deltaX
of the platform is added to the players x position. While its y position is adjusted to match the top of the platform (lockedTo.y
). The -48 is just the height of the player sprite. The player is still free to run around. But that all takes place as part of the update
and postUpdate
parts of the game loop. And in preRender
we adjust him based on the platform he's locked to.
checkLock and cancelLock
We need to free the player from the platform in 2 cases: 1) He jumps and 2) He walks off the edge:
checkLock: function () {
this.player.body.velocity.y = 0;
if (this.player.body.right < this.lockedTo.body.x || this.player.body.x > this.lockedTo.body.right)
{
this.cancelLock();
}
},
If the player walks off the side of the platform then we no longer consider them locked to it. Equally the jump function cancels the lock as well. This works well for specific case, but you may want to tailor this action for your needs.
Run the code to play this demo.
Parallax Background
Last week we used a single TileSprite
to create a nice scrolling effect in the background. This week we're using two :) In the create
method you'll see we make background
and trees
. These are two horizontally repeating images that we wish to scroll at different rates as the camera moves:
this.background.tilePosition.x = -(this.camera.x * 0.7);
this.trees.tilePosition.x = -(this.camera.x * 0.9);
With the above values the background
(i.e. the sky layer) will scroll slower than the trees
. But both scroll slower than the actual camera does, giving a nice 3-way parallax effect as you traverse the level.
Brain Food
Platforms like this are really useful, however here are some ideas to enhance this approach: * Rather than use the deltaX value you could drop an anchor when the player lands on the platform. Then adjust it as they move, but use its coordinate in preRender
to re-position the player. This will give for a smoother end result as you won't be dealing with floats and rounding. * Right now all the platforms start moving automatically, but they have a start
method. You could activate this the moment the player lands on the platform, i.e. it moves on contact. * Equally the platforms don't have to run on a loop. They could simply move from A to B and then stop. This requires careful level design, but is often seen in platform games.
Here is what's new in the Phaser world this week:
Phaser 3 Development Log 2 Bunnies, instances and text Speed-up Phaser Group vs. Group Collision With this 1D Sort Cómo Crear un Juego Móvil con HTML5 y Cordova (Spanish) Understanding hexagonal tiles By Emanuele Feronato Digicat the Thief Steal the diamonds in this cute open-source game Discover Phaser A French multi-part series Lava Run - open-source endless runner By OttoRobba Phaser + RequireJS and Underscore Template npm install and deploy * Making Games with Phaser From Philly Gameworks Got something you'd like featured? Get in touch.
If you've questions or comments about the code then please ask. Equally if there are things you'd like me to write about then tell me! Until next week, happy coding. Discuss: HTML5 Game Devs Forum Tweet @photonstorm (hashtag #phaserjs)
Phaser Coding Tips is a free weekly email - subscribe here.
Welcome!
This week we carry on building on our set of platformer game tools. Last time we created platforms with specific friction, but this week we're creating platforms you can ride: commonly known as "cloud platforms".
Get the source
I'm only going to highlight the most important parts of the code here. So please always look at the source first. If you've questions about something I didn't cover then ask on the forum. Run / Edit the code on jsbin or codepen or clone the phaser-coding-tips git repo.
"Cloud" Platforms
A "Cloud platform" is a special kind of platform in your level. It has two distinct characteristics: First it follows its own fixed motion path. And secondly, the player can ride it. For our purposes here is our cloud platform:
It doesn't have to look like an actual cloud of course! In the Mario games they take on all kind of visual forms, but it helps get the message across for our needs.
CloudPlatform
Because we want to have more than just one of these types of platform they are a good candidate for a custom object: CloudPlatform
. This is a special object that extends Phaser.Sprite
. Here is its constructor:
CloudPlatform = function (game, x, y, key, group) {
if (typeof group === 'undefined') { group = game.world; }
Phaser.Sprite.call(this, game, x, y, key);
game.physics.arcade.enable(this);
this.anchor.x = 0.5;
this.body.customSeparateX = true;
this.body.customSeparateY = true;
this.body.allowGravity = false;
this.body.immovable = true;
this.playerLocked = false;
group.add(this);
};
You pass in the same parameters as if you were defining a normal Sprite. The difference is that it enables itself for physics, defines an anchor and sets some body properties up. As all CloudPlatforms need the same settings it saves some time to do this here. Here is how we create one:
var cloud1 = new CloudPlatform(this.game, 300, 450, 'cloud-platform', this.clouds);
So far, so good. But they're still just normal sprites. What we need now is to define their motion.
addMotionPath
The CloudPlatform
object has a special method called addMotionPath
. It expects an array filled with path data:
cloud1.addMotionPath([
{ x: "+200", xSpeed: 2000, xEase: "Linear", y: "-200", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "-200", xSpeed: 2000, xEase: "Linear", y: "-200", ySpeed: 2000, yEase: "Sine.easeOut" },
{ x: "-200", xSpeed: 2000, xEase: "Linear", y: "+200", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "+200", xSpeed: 2000, xEase: "Linear", y: "+200", ySpeed: 2000, yEase: "Sine.easeOut" }
]);
What it then does is build an internal set of tweens that moves the platform based on its starting coordinates and the data given above. The above code moves the platform in an almost circular path. The faded sprites showing the path the platform took: Note how the coordinates are given as relative values, i.e. "+200" - this means they are all based off the platforms starting position. If you need to tweak the position you don't have to redo all the motion values too. Here's an example of a vertically moving platform:
cloud2.addMotionPath([
{ x: "+0", xSpeed: 2000, xEase: "Linear", y: "+300", ySpeed: 2000, yEase: "Sine.easeIn" },
{ x: "-0", xSpeed: 2000, xEase: "Linear", y: "-300", ySpeed: 2000, yEase: "Sine.easeOut" }
]);
By default the CloudPlatform
runs on a loop. So in the example above it moves down by 300 pixels (using Sine.easeIn) and then moves back up by 300 pixels as well.
Locking the Player
The cloud platforms are checked in the update
loop via a normal collide call:
this.physics.arcade.collide(this.player, this.clouds, this.customSep, null, this);
The difference is that we've got a collision callback handler customSep
which looks like this:
customSep: function (player, platform) {
if (!this.locked && player.body.velocity.y > 0)
{
this.locked = true;
this.lockedTo = platform;
platform.playerLocked = true;
player.body.velocity.y = 0;
}
},
We test the velocity. If the player is in a jump and falling down (> 0) and not currently locked to a platform then we tell the game the player is now locked. The platform he landed on is stored in lockedTo
and his velocity is reset. Why do we lock him? The reason is that the platform is being moved by a tween, not by any kind of velocity or physics operation. It's following a fixed path, so we need to ensure that the player is repositioned to match that path every frame. This is done in the preRender
function:
preRender: function () {
if (this.game.paused)
{
// Because preRender still runs even if your game pauses!
return;
}
if (this.locked || this.wasLocked)
{
this.player.x += this.lockedTo.deltaX;
this.player.y = this.lockedTo.y - 48;
if (this.player.body.velocity.x !== 0)
{
this.player.body.velocity.y = 0;
}
}
}
...
The deltaX
of the platform is added to the players x position. While its y position is adjusted to match the top of the platform (lockedTo.y
). The -48 is just the height of the player sprite. The player is still free to run around. But that all takes place as part of the update
and postUpdate
parts of the game loop. And in preRender
we adjust him based on the platform he's locked to.
checkLock and cancelLock
We need to free the player from the platform in 2 cases: 1) He jumps and 2) He walks off the edge:
checkLock: function () {
this.player.body.velocity.y = 0;
if (this.player.body.right < this.lockedTo.body.x || this.player.body.x > this.lockedTo.body.right)
{
this.cancelLock();
}
},
If the player walks off the side of the platform then we no longer consider them locked to it. Equally the jump function cancels the lock as well. This works well for specific case, but you may want to tailor this action for your needs.
Run the code to play this demo.
Parallax Background
Last week we used a single TileSprite
to create a nice scrolling effect in the background. This week we're using two :) In the create
method you'll see we make background
and trees
. These are two horizontally repeating images that we wish to scroll at different rates as the camera moves:
this.background.tilePosition.x = -(this.camera.x * 0.7);
this.trees.tilePosition.x = -(this.camera.x * 0.9);
With the above values the background
(i.e. the sky layer) will scroll slower than the trees
. But both scroll slower than the actual camera does, giving a nice 3-way parallax effect as you traverse the level.
Brain Food
Platforms like this are really useful, however here are some ideas to enhance this approach: * Rather than use the deltaX value you could drop an anchor when the player lands on the platform. Then adjust it as they move, but use its coordinate in preRender
to re-position the player. This will give for a smoother end result as you won't be dealing with floats and rounding. * Right now all the platforms start moving automatically, but they have a start
method. You could activate this the moment the player lands on the platform, i.e. it moves on contact. * Equally the platforms don't have to run on a loop. They could simply move from A to B and then stop. This requires careful level design, but is often seen in platform games.
Here is what's new in the Phaser world this week:
Phaser 3 Development Log 2 Bunnies, instances and text Speed-up Phaser Group vs. Group Collision With this 1D Sort Cómo Crear un Juego Móvil con HTML5 y Cordova (Spanish) Understanding hexagonal tiles By Emanuele Feronato Digicat the Thief Steal the diamonds in this cute open-source game Discover Phaser A French multi-part series Lava Run - open-source endless runner By OttoRobba Phaser + RequireJS and Underscore Template npm install and deploy * Making Games with Phaser From Philly Gameworks Got something you'd like featured? Get in touch.
If you've questions or comments about the code then please ask. Equally if there are things you'd like me to write about then tell me! Until next week, happy coding. Discuss: HTML5 Game Devs Forum Tweet @photonstorm (hashtag #phaserjs)