In this tutorial, we will take a look at Navigation2D, a node that can be used to find the shortest path between two points. It is mainly used to move characters with automatic obstacles avoidance.
First, go to this Github repository and download the project’s Zip file, so you will have a starting point to follow the tutorial.
Extract the contents of the Zip file. Then, open Godot and press Scan to import the project.
Browse to the path where you extracted the Zip file, select the Navigation2D-Starting-Point folder and press Select this Folder to add the project to the Godot Project Manager.
Now, you can open the project.
Let’s look at the node tree. We simply have a root node (Root), which contains two Sprite nodes, one for the background and one for the player’s character. Both Root and Background nodes already have a script attached that currently do nothing.
Creating Navigation2D node and navigation polygons
Add a Navigation2D node to Root. A Navigation2D node provides navigation and pathfinding within an area defined as a set of NavigationPolygon resources. By default, these polygons resources are automatically obtained from child NavigationPolygonInstance nodes.
So, to define the area in which the character will be able to move, we need to add a NavigationPolygonInstance node as a Navigation2D child. Once the node is created, you will see a warning sign next to its name, which tells us that we need to create a NavigationPolygon resource.
With NavigationPolygonInstance selected, go to the Inspector and, next to the Navpoly property, select new NavigationPolygon.
Now you can draw the polygon that represents the area accessible to the character. When NavigationPolygonInstance is selected, in the workspace toolbar we have 3 new buttons to create and edit the navigation polygons:
Select the Create points tool and click on the editor to add points to the navigation polygon. Draw the entire perimeter, going around the obstacles, until you close it by clicking on the first point created.
Since the character has a size, be sure to leave some margin around obstacles, so that the player’s sprite does not overlap them.
Once the perimeter is finished, there will only be one obstacle left to do, that is the hole in the upper right corner of the map. To add it, simply draw a path around it to create a hole in the polygon.
The final result will be something like this:
The last node we need to add is a Line2D one. We will use it to draw the path calculated by Navigation2D. In the Inspector, set its Width property to 1 and choose a color that stands out on the green background (I chose red).
Let’s start by working on the Root node script. This script will be the one that will take care of requesting the path to the Navigation2D node.
Let’s click on the script icon next to Root to open the script editor. For now, the script contains only this code:
extends Node2D func _unhandled_input(event): pass
We want the player to move whenever we click the left mouse button on the map, like in Point and Click games. To handle mouse input, we will use the _unhandled_input() function, which is already declared in the script but is currently empty (the keyword pass is used to declare that the function is empty). This function is the recommend one to manage user input, because Godot calls it only after handling the user interface input events.
Replace the function code with the following:
func _unhandled_input(event): if event is InputEventMouseButton: if event.button_index == BUTTON_LEFT and event.pressed: var path = $Navigation2D.get_simple_path($Player.position, event.position) $Line2D.points = path $Player.path = path
The first two lines of the function check that the detected input event is a mouse event, and that this event is the left click.
If the two conditions are true, we ask the Navigation2D node for the path using the get_simple_path() method. This method has two mandatory parameters:
- the starting point, which in our case is the current position of the player;
- the destination point, that for us is the point of the map clicked with the mouse.
The get_simple_path() function returns a PoolVector2Array which contains the list of the points forming the shortest path from the starting point to the destination point.
After getting the path, the function does two things. The first is to set the points property of Line2D, so that the path is drawn on the screen. The second is to assign the path to Player‘s path variable. This variable is declared in the player script. We will use it for moving the character sprite.
If you run the project now, you will see that when you click a point on the map, the path to reach that point is drawn on the screen.
If you try to click on a point where the player cannot move (such as one of the gorges), the path will stop at the accessible point nearest to where you clicked.
Player movement script
Let’s move on to the player’s movement script. If you open it, you’ll see that it already includes this code:
extends Sprite var speed = 50 var path : = PoolVector2Array() func _process(delta): pass
The script has a speed variable to store player movement speed, and there is the path property we used before in the Root.gd script. This variable is initially set to an empty PoolVector2Array object, to indicate that the character does not have a path to follow.
We’re going to use the _process() method, that run on every frame, to move the character along the path. So, replace its code with the following:
func _process(delta): # Calculate the movement distance for this frame var distance_to_walk = speed * delta # Move the player along the path until he has run out of movement or the path ends. while distance_to_walk > 0 and path.size() > 0: var distance_to_next_point = position.distance_to(path) if distance_to_walk <= distance_to_next_point: # The player does not have enough movement left to get to the next point. position += position.direction_to(path) * distance_to_walk else: # The player get to the next point position = path path.remove(0) # Update the distance to walk distance_to_walk -= distance_to_next_point
How does this code work?
As we mentioned earlier, a path is a list of points. The character will move along the segments that connect these points.
At each frame, the character can move a certain distance, calculated as the product of its speed and the time elapsed from the previous frame (var distance_to_walk = speed * delta).
Once the distance to the next point of the route has been calculated (var distance_to_next_point = position.distance_to(path)), two things can happen:
- the distance to be covered is less than the distance from the next point of the path: in this case, the player moves towards the next point covering the entire distance of movement (position += position.direction_to(path) * distance_to_walk).
- the distance to be covered is greater than the distance from the next point: in this case, the player is placed on the next point (position = path), which will then be removed from the path (path.remove(0)); then, the distance to walk is reduced by the movement already performed (distance_to_walk -= distance_to_next_point) and the procedure is repeated until the movement is completed (while distance_to_walk > 0 and path.size() > 0:).
Run the game and try to click anywhere: the character will move to the place you click on the map (or to the nearest point if you click outside the accessible area.
Click below if you want to try the code: