The quicksand effect is somewhat common in platformers but you probably haven't found too many tutorials for it. 🤔
Unlike something simpler such as water or lava, quicksand might take a few days to figure out and implement.
In this article, we'll show you how to create a basic quicksand effect for a Phaser 3 game using Arcade Physics!
Example Overview
We will assume that you are making a platformer with player logic already implemented and using tilemaps but not necessarily from Tiled.
The code will be in modern JavaScript with ES6 classes and JSDoc annotations to help with type information.
If you are not familiar with using modern JavaScript with Phaser 3 then we have a free book to help you get started!
Learn to make an Infinite Jumper in Phaser 3 with modern JavaScript!
Drop your email into the box below to get this free 60+ page book and join our newsletter.
Scene set up code has been omitted so that we can focus directly on creating a Quicksand
class that can be used in a Scene you already have.
Creating the Quicksand Class
The Quicksand
class will create an object that is placed over the tiles representing quicksand.
We don't have any animations in our tiles but they can be added separately from the quicksand logic.
This is what the quicksand effect will look like 👇
Let's start with a basic skeleton:
|
|
Notice that we are not extending from an existing Phaser class. Instead, we are just going to create the visual elements we need and keep a reference to them.
In the constructor, we create an invisible Rectangle
on line 17 with an origin set to the bottom left corner. This is for ease of positioning in this example. You may prefer to keep it t at (0.5, 0.5)
or (0, 0)
.
Then we inject Arcade Physics components into the Rectangle
on line 20. We'll add a collider with the player instance in the Game Scene later.
Creating the Quicksand Effect
With a physics-enabled Rectangle
, the player will be able to stand on it but not sink into it. The sinking effect will be created by adjusting the size of the Rectangle
collision box.
We'll start making the Rectangle
smaller as soon as the player collides with it. Then if the player jumps or is no longer touching the collision box, we will expand the size of the Rectangle
.
Quicksand is also harder to move through so we will clamp how high the player can jump while in quicksand.
Let's start by adding a handleCollidePlayer()
method and a touchingPlayer
class property to store the player instance when it collides with the Rectangle
:
|
|
Note that this quicksand example assumes only 1 player character. There will be more work involved to have it support multiple players and is outside the scope of this article.
The handleCollidePlayer()
method will be set as the callback when we create a collider in the Game Scene.
Next, let's create a changeCollisionBoxBy(diff)
method that will adjust the size of the Rectangle
collision box. We'll use it in the update()
method to make the collision box bigger or smaller depending on what the player is doing.
|
|
Notice that we don't do anything if the collision box is as big as the display's original height or if it is already zero or less. These two checks are on lines 7 and 12.
Then on line 18, we set the offset so that the collision box changes height while being anchored to the bottom. Without this offset, you'll see that the height shrinks and expands from the middle.
Now, we can implement the core quicksand logic in update()
:
|
|
First, we early exit and do nothing if this.touchingPlayer
is not set. This means that the player is not currently in a quicksand area.
Then we do some logic to determine if the player is touching the collision box or within the bounds of the quicksand display.
We do this because the player can be surrounded by quicksand while jumping. This would result in the player not colliding with the collision box but still being affected by quicksand.
The collision box will continue to shrink as long as the player is touching it unless a jump occurs. Then the collision box will expand instead. This will allow the player to eventually jump out of the quicksand even though jump height is reduced.
That jump height reduction happens on line 26.
Finally, the last check on line 32 will determine when the player has jumped out of the quicksand area and should no longer be affected. We remove the reference to the player and immediately set the collision box to full size.
If the player lands on the quicksand again then the sinking effect will start over from the beginning.
Using the Quicksand Class in a Scene
The Quicksand
class can be easily used by creating a new instance in the create()
method like this:
|
|
We are assuming that the player instance is stored in a player
variable. Your actual game code may be different.
Notice that we use the handleCollidePlayer()
method from the Quicksand
class as the collider callback. We also pass in the quicksand
reference as the context
argument.
The position and size values that we are using to create a new Quicksand
instance are just for this example and your game code will probably be different.
If you are using an editor like Tiled then you will want to use objects on an Object Layer. That data can then be used to create a Quicksand
instance with the right position and size.
Lastly, we'll want to call the update()
method on the quicksand
instance in the Scene's update()
method. You may need to create a class property with the quicksand
reference to do this.
Be sure that the player's movement logic is run before the call to update()
on the Quicksand
instance. Otherwise, clamping the player's movement speed from the Quicksand
class won't work.
Multiple Quicksands
Having 1 Quicksand
object is a good example but not realistic for a game.
Odds are your level will have a few quicksand areas for the player to deal with.
The Quicksand
class we made easily supports multiple instances. All you need to do is to add a collider and call the update()
method for each created Quicksand
instance.
It'll look something like this:
|
|
Then in the Scene's update()
:
|
|
The main difference to note is that we created an Array
class property called quicksands
to store each created Quicksand
instance.
The end result should look something like this 👇
Next Steps
Quicksand is definitely more complicated than just adjusting physics for water or taking damage from landing in lava but not impossible!
We didn't go over how to determine when the player is so deep that they suffocate and we'll leave that to you.
Other physics values can be adjusted or clamped when the player is inside of quicksand as well. It may be similar to how we clamp the player's jump height.
Be sure to sign up for our newsletter so you don't miss any future Phaser 3 game development tips and techniques!
Drop your email into the box below.
Don't miss any Phaser 3 game development content
Subscribers get exclusive tips & techniques not found on the blog!
Join our newsletter. It's free. We don't spam. Spamming is for jerks.