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.

Starting point

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.

Press scan to import a 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.

Add a NavigationPolygon resource

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).

Pathfinding script

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[0])
		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[0]) * distance_to_walk
		else:
			# The player get to the next point
			position = path[0]
			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.

A path is an array of 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[0])), 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[0]) * 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[0]), 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:).

Final result

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:

Did you enjoy this article? Then consider buying me a coffee! Your support will help me cover site expenses and pay writers to create new blog content.


2 Comments

Avatar

Lawrie · February 1, 2020 at 10:35 am

Thank you for this! I was struggling to find details on non-Tilemap-based pathfinding, and this was SUPER straightforward. Only thing to note: in your `_process` function for the Player sprite, I think you missed a couple of characters off the end: `distance_to_walk -= distance_to_next_poi` should be `distance_to_walk -= distance_to_next_point`.

Going to read through some of your other posts now – this place is a goldmine!

    Avatar

    Davide Pesce · February 2, 2020 at 10:20 pm

    Thanks for the feedback Lawrie! You’re right, two characters were missing!

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close