NavigationMesh in Godot – Complete Guide

Navigating through digital worlds can often seem as intricate and complex as moving through the great labyrinthine structures of history. At the heart of this process lies the silent but crucial technology known as the Navigation Mesh, or NavMesh for short. In the realm of game development with Godot 4, mastering the NavigationMesh class not only elevates the realism of an artificial environment but allows developers to forge pathways for characters to traverse these digital spaces with intelligence and purpose.

Understanding Navigation Meshes

The concept of a Navigation Mesh is essential for modern game development, especially when it comes to AI pathfinding. A Navigation Mesh is a collection of polygons that define the traversable regions of a game world. It acts as a map for AI agents or characters, detailing the areas they can walk on, the obstacles they need to avoid, and the most efficient path to their destination.

The Significance of NavigationMesh in Godot 4

Godot 4 empowers developers with a built-in NavigationMesh class, which streamlines the process of generating these polygonal maps within your game environments. Understanding and using the NavigationMesh class will unleash the potential for you to create intricate, lifelike movement patterns for the non-player characters within your projects.

Why Invest Time in Learning About NavigationMesh?

Learning how to implement and utilize a NavigationMesh is not just about creating paths for characters. It is about shaping the very way players will interact with your game environment. An intelligent navigation system can transform a good game into a great one, allowing for scenarios and interactions that were previously not possible. Engaging with the mechanics of NavigationMesh will elevate the quality of your projects and equip you with vital skills in AI pathfinding—a staple in the game developer’s toolkit.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Setting Up Your Navigation Mesh

To begin utilizing the NavigationMesh class in Godot 4, you first need to set up your navigation mesh within the Godot editor. Here’s how you can start:

var nav_mesh_instance = NavigationMesh.new()

Next, add the NavigationMesh to a Navigation node. This node acts as the parent for the NavigationMeshInstance.

var nav_node = Navigation.new()
nav_node.navmesh = nav_mesh_instance
add_child(nav_node)

Now that you have a basic setup, let’s create actual geometry for the Navigation Mesh. Typically, this is done using a tool within the editor, but for our purposes, let’s define a simple mesh programmatically:

var mesh = MeshInstance.new()
mesh.mesh = some_mesh_resource  # Replace with your mesh resource
nav_mesh_instance.meshes = [mesh]

Remember to replace some_mesh_resource with an actual Mesh resource that forms the geometry of your navigable area.

Configuring your Navigation Mesh Properties

After setting up the mesh, you must configure the properties to define how characters will interact with the environment. For instance, you can set the cell size, which dictates the resolution of the Navigation Mesh:

nav_mesh_instance.cell_size = 1

Smaller cell sizes allow for more precise navigation but can increase computation time. Adjust accordingly based on your game’s needs.

Additionally, you’ll want to set up properties like the agent’s height and radius which outline the walkable areas considering the size of the character:

nav_mesh_instance.agent_height = 2
nav_mesh_instance.agent_radius = 0.5

These adjust the Navigation Mesh to accommodate for the vertical and horizontal space an agent needs to move without collision.

Implementing Pathfinding with NavigationMesh

With your navigation mesh configured, let’s dive into how to implement pathfinding. First, you’d want to attain the path using the get_simple_path method:

var start_position = Vector3(0,0,0)  # Start of the path
var end_position = Vector3(10,0,10)  # End of the path
var path = nav_node.get_simple_path(start_position, end_position, true)

The third parameter is a boolean that, when set to true, ensures the path is optimized.

To have an agent follow this path, we’ll implement a simple follow routine within the agent’s script:

var current_path = []
var current_target = 0

func _process(delta):
    if current_path.size() > 0:
        var direction = current_path[current_target] - global_transform.origin
        move_and_slide(direction.normalized() * speed, Vector3.UP)
        if direction.length() = current_path.size():
                current_path = []

In this snippet, target_reached_threshold should be a small value that allows the agent to get sufficiently close to a waypoint before considering it “reached.”

Updating the Navigation Mesh at Runtime

Dynamic environments require the ability to update the Navigation Mesh as the game world changes. To do this, we can add or remove meshes from the NavigationMesh:

# Add a new obstacle
var obstacle = MeshInstance.new()
obstacle.mesh = my_obstacle_mesh
nav_mesh_instance.meshes.append(obstacle)

# Remove the obstacle
nav_mesh_instance.meshes.erase(obstacle)

Always ensure you update the NavMesh data after making any changes:

nav_node.navmesh = null
nav_node.navmesh = nav_mesh_instance

This prompts the Navigation node to re-bake the mesh information, ensuring that the pathfinding algorithms have the most up-to-date data to work with.

With these code examples, you’ve seen how to set up a basic Navigation Mesh, adjust its properties, use it for pathfinding, and update it at runtime. These foundations are critical for creating dynamic and responsive AI in your Godot 4 games.

Once you have ingrained the basics of the NavigationMesh and pathfinding, we can explore more complex tasks, such as obstacle avoidance and dynamic path recalculations. Here are some more code examples to deepen your understanding and application of NavigationMesh in Godot 4:

Let’s start with avoiding dynamic obstacles. The AI character needs to constantly check for changes in the environment and adjust its path accordingly. Here’s an example of how this could be done:

func _process(delta):
    if current_path.size() > 0:
        if is_obstacle_ahead():
            recalculate_path()
        else:
            follow_path(delta)

func is_obstacle_ahead() -> bool:
    # Implement your obstacle detection logic here
    pass

func recalculate_path():
    var new_path = nav_node.get_simple_path(global_transform.origin, end_position, true)
    if new_path.size() > 0:
        current_path = new_path
        current_target = 0

Next, let’s consider an AI that needs to follow a player or another moving target. We need to continuously update the end position of the path:

func _process(delta):
    var player_position = player.global_transform.origin
    var path_to_player = nav_node.get_simple_path(global_transform.origin, player_position, true)
    follow_custom_path(delta, path_to_player)

func follow_custom_path(delta, path):
    current_path = path
    current_target = 0
    follow_path(delta)

func follow_path(delta):
    # Follow the current_path as previously described

For games with multiplayer or complex interactions, you might need to calculate multiple paths concurrently. This situation requires managing multiple Navigation nodes:

# Suppose you have an array of Navigation nodes for different agents
var nav_nodes = []

func calculate_paths_for_agents():
    for i in range(nav_nodes.size()):
        var start_position = agents[i].global_transform.origin
        var end_position = agents[i].target_position
        var path = nav_nodes[i].get_simple_path(start_position, end_position, true)
        agents[i].current_path = path

Additionally, when creating complex environments with multiple levels, you may want to connect different NavigationMeshes:

var bridge_navmesh_instance = NavigationMesh.new()

# Configure and add the bridge NavMesh between two main NavMeshes
nav_mesh_instance.create_navigation_mesh_links(bridge_navmesh_instance)

In scenarios where avoiding enemy line-of-sight is crucial, pathfinding can be combined with raycasting to ensure paths are calculated out of enemy view:

func get_stealth_path(beginning: Vector3, end: Vector3) -> PoolVector3Array:
    var path = nav_node.get_simple_path(beginning, end, true)
    for point in path:
        if enemy.can_see_point(point):
            path = reroute_around_enemy(beginning, end)
            break
    return path

func can_see_point(point: Vector3) -> bool:
    # Implement enemy line-of-sight logic with raycasting here
    pass
    
func reroute_around_enemy(beginning: Vector3, end: Vector3) -> PoolVector3Array:
    # Calculate a new path that avoids the enemy's line-of-sight
    pass

Finally, performance optimization is often needed for games with large or complex worlds. Here’s a simple pre-processing step that can be done to speed up pathfinding:

func _ready():
    nav_mesh_instance.create_from_mesh(mesh)
    nav_mesh_instance.set_cell_size(1)
    nav_mesh_instance.set_agent_height(2)
    # Other properties and configurations
    nav_node.navmesh_data = nav_mesh_instance

By pre-computing and setting the NavMeshData, you avoid unnecessary computations during gameplay, boosting performance when retrieving paths.

Through these code snippets and applications, we’ve explored more advanced concepts and scenarios you’ll likely encounter when creating AI using the NavigationMesh in Godot 4. Integrating these features will not only make your game world more dynamic and challenging but also enhance the overall player experience.

Delving further into the dynamic capabilities of NavigationMesh, there are several advanced uses we can explore. Below are more code examples depicting various scenarios:

One common situation in games is when the navigation mesh should be altered in real-time due to destructible environments. Here’s how you can update the mesh when an area becomes impassable:

# Suppose there’s an area that gets destroyed and is no longer navigable
func on_area_destroyed(area):
    nav_mesh_instance.remove_mesh(area.mesh_instance)
    update_navmesh()

func update_navmesh():
    nav_node.navmesh_data = null
    nav_node.navmesh_data = nav_mesh_instance

For efficiency, the navigation mesh shouldn’t be updated with every small change, but rather batched together and updated less frequently:

var update_queue = []

func queue_mesh_update(mesh_instance):
    update_queue.append(mesh_instance)
    if update_queue.size() >= batch_size:
        apply_queued_updates()

func apply_queued_updates():
    for mesh_instance in update_queue:
        nav_mesh_instance.remove_mesh(mesh_instance)
    update_queue.clear()
    update_navmesh()

Creating dynamic paths that adapt to the agent’s speed can enhance realism. Here’s how you might implement such a feature:

var speed_variability = 0.5
func get_dynamic_path(agent):
    var path = nav_node.get_simple_path(agent.global_transform.origin, agent.destination, true)
    var dynamic_path = []
    var accumulated_distance = 0.0
    for i in range(path.size() - 1):
        var segment_length = path[i].distance_to(path[i + 1])
        accumulated_distance += segment_length
        if accumulated_distance > agent.speed * speed_variability:
            dynamic_path.append(path[i])
            accumulated_distance = 0.0
    dynamic_path.append(path[-1]) # Ensure the final destination is included
    return dynamic_path

Path smoothing can prevent agents from making sharp turns, which sometimes may look unnatural. To achieve smoother paths, you can utilize a simple smoothing algorithm:

func smooth_path(raw_path):
    var smoothed_path = [raw_path[0]]
    var current_direction = (raw_path[1] - raw_path[0]).normalized()
    for i in range(1, raw_path.size() - 1):
        var next_direction = (raw_path[i + 1] - raw_path[i]).normalized()
        if next_direction.dot(current_direction) < 0.99: # Adjust the value for level of smoothing
            smoothed_path.append(raw_path[i])
            current_direction = next_direction
    smoothed_path.append(raw_path[-1])
    return smoothed_path

When it comes to optimizing pathfinding in larger worlds, you can implement spatial partitioning to only calculate paths within certain sections at a time:

# Assuming you have a method to determine which partition an agent is in
func get_partitioned_path(agent):
    var partition = get_partition_for_agent(agent)
    return partition.get_simple_path(agent.global_transform.origin, agent.destination, true)

func get_partition_for_agent(agent):
    # Determine the current partition based on the agent’s location
    pass

Lastly, let’s consider an example of multi-threaded pathfinding, which is particularly useful for games with many AI agents:

func calculate_path_in_background(agent):
    var thread = Thread.new()
    thread.start(self, "_calculate_path", agent)

func _calculate_path(agent):
    # It is safe to use Navigation servers in separate threads since Godot 4
    var path = nav_node.get_simple_path(agent.global_transform.origin, agent.destination, true)
    call_deferred("_on_path_calculated", agent, path)

func _on_path_calculated(agent, path):
    agent.current_path = path

In these examples, you’ve learned about updating the navigation mesh for destructible environments, batched updates for performance, dynamic path adjustments based on agent speed, smoothing sharp turns in paths, optimizing with spatial partitioning, and performing multi-threaded pathfinding. These features could significantly contribute to a more dynamic, realistic, and responsive game environment.

Continue Your Game Development Journey

Your exploration into the world of game development with Navigation Meshes and pathfinding in Godot 4 doesn’t have to end here. To further advance your skills and create even more immersive game experiences, we invite you to dive deeper into Godot with our comprehensive Godot Game Development Mini-Degree. We’ve tailored a set of courses to equip you with a broad range of skills and best practices in game design and development using this powerful engine.

The beautiful thing about learning is that it’s a journey without end, and there’s always something new on the horizon to discover. If you’re eager to expand your horizons beyond this tutorial, consider checking out our full range of Godot courses at Zenva Academy. From 2D platformers to complex 3D environments and everything in between, our courses are designed to support your growth from beginner to professional. Start creating your own games, enhance your existing projects, and step confidently toward building a robust portfolio that opens doors in the game development industry.

We at Zenva pride ourselves on empowering students with high-quality content that’s accessible and engaging. Whether you’re just starting out or looking to perfect your craft, we have something to offer that will help take your development skills to the next level. Continue your learning adventure with us, create captivating games, and stride towards a future filled with coding triumphs and pixel-perfect victories.

Conclusion

As you’ve journeyed through the intricacies of the Navigation Mesh in Godot 4, you’ve uncovered the potential to breathe life into your virtual worlds. You’ve seen how essential a tool it is in crafting the intelligence that guides your characters and the strategies your players will unravel. Remember, what you’ve learned here is just the tip of the iceberg; there are endless possibilities awaiting in the depths of game development with Godot 4.

We at Zenva believe that every step you take in learning and perfecting your skills is a leap towards creating something truly remarkable. To continue fueling your passion and expanding your toolkit, revisit our Godot Game Development Mini-Degree and join a community of learners turning their game development dreams into reality. Turn today’s knowledge into tomorrow’s masterpieces, and let your creativity run wild in the sandbox of game design.

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.