Are you are making an action-RPG, RTS, dungeon crawler, or similar type of game using tilemaps in Phaser 3 and want characters to move to where the mouse was clicked?
Maybe you've tried adjusting the velocity or used a tween to move to the mouse position but found that it didn't really work? Especially when there's a wall in the way?
What you need is a pathfinding algorithm!
In this article, we'll show you how to add point & click movement using breadth-first search to discover a path and then have the character move along it.
Example Base Project
We'll be building on top of our Dungeon Crawler Starter Template that uses TypeScript and comes with an 8.5 part YouTube series going over how it was made.
If you are not familiar with TypeScript then we've got a free book to help you get started!
Learn to make an Infinite Runner in Phaser 3 with TypeScript!
Drop your email into the box below to get this free 90+ page book and join our newsletter.
The code in this article should work for any Phaser 3 project that uses tilemaps but you may have to refer to the Dungeon Crawler Starter Template for additional context.
Start with a Click
Let's start by listening for a
POINTER_UP event on the
Game Scene. The pointer data will be used to determine which tile was clicked.
This tile will be used as the target location for our character to move to.
worldY values from the
pointer represent where the player clicked. We use object destructuring on line 6 to get the values.
Note: if you get an error about
tslib then you probably need to install it with
npm install tslib --save-dev.
Then we use those values on line 9 to get the tile
y values from the
groundLayer which is a
We do the same thing with the player (
faune) on line 8 to get the starting position.
In breadth-first search, we need a start position to begin traversal and then a target position to know when we're done. The
targetVec variables give us this information.
They are suffixed with
Vec because they are of type
Find a Path with Breadth-First Search
Next, we will implement the breadth-first search algorithm to create a lookup table that can be used to construct a path. This path will be an array of
Phaser.Math.Vector2 values representing the world positions the player needs to move along.
Let's add a
findPath.ts file to the
utils folder like this:
Notice that we declared an interface called
TilePosition and created the
toKey(x, y) helper function.
These are to help make the implementation code in
findPath() easier to understand.
With breadth-first search, we will add the start tile to a queue. Then we will look at each item in the queue and add all its neighbors. As we do this we will construct a lookup table of parent tiles that we can use to generate a path from the target to where the player is.
We recommend taking a look at this video for an in-depth explanation and step-by-step implementation of breadth-first search.
wallsLayer is passed to this function so that we can check if the target is a valid ground tile for the player to walk to and if there are walls in the way.
Here's the breadth-first search implementation that builds a parent lookup table:
The bulk of the code is a basic breadth-first search implementation using just the 4-way north, south, east, and west neighbors. Each neighbor is added to the
parentForKey lookup table with the current tile as the parent.
Notice that we are not storing the actual
Tile reference but just its
Next, we can use the
parentForKey lookup to get all the tiles that make a path from the target location to the starting location.
The goal of this code is to find the lineage that takes us from the target position back to the start position.
We do this by adding the target's parent to the
path array and then doing the same for that parent's parent and so forth until we get to the start position.
Another way to look at it:
target -> parent -> grandparent -> great grandparent -> ... -> start
This lineage is what makes up the
path array. The position of each ancestor is added to this list and then the entire list reversed.
Moving the Player Along a Path
Now we can give the player the
path array and have it move to each position one after the other.
Start by adding these properties and methods to the
This new code should be pretty straight forward. We add the
moveToTarget properties that will hold the path of positions and the current position to move to.
! operator on line 18. This is the non-null assertion operator. This lets us tell TypeScript that the result of
shift() will return a valid value and not
We are going to replace the existing
update(cursors: Phaser.Types.Input.Keyboard.CursorKeys) method with a new one on line 27. The player's movement will be based on
moveToTarget instead of
CursorKeys so we no longer need
cursors to be passed in.
We'll use logic that mimics keys being pressed depending on where
moveToTarget is relative to the player's position. We've left this method relatively similar to the original so that you can more easily compare and see the differences.
dy to represent the difference in
moveToTarget and the player's position.
The player is considered to have arrived at the target if
dy are within
5 pixels of the target location on either side. This check happens on lines 18 and 22.
Then on line 31, a new
moveToTarget position is set by taking the next one from
this.movePath. If there are no more positions left then
moveToTarget is set to
undefined and the player will stop moving.
downDown variables that were used to denote arrow keys being pressed is now set based on whether
dy is positive or negative.
Lastly, the same animation and velocity logic is used to move the player each frame.
Putting it Together
We created a bunch of code, methods, and functions so let's use them together to achieve point & click movement!
Back in the
create() method of the
Game Scene we need to use
findPath() and call
You should end up with something like this that will find an appropriate path around walls and move the player along that path:
This was a meaty article full of code so take some time to digest it!
Try using the NE, SE, SW, and NW diagonal neighbors in the breadth-first search algorithm to see how that changes the path.
We've completely omitted the logic for collecting treasure chests for simplicity so feel free to see how you can add that back!
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.