【Godot】Pathfinding with NavigationAgent2D - Enemy AI Pursuit and Patrol

Created: 2025-12-08Last updated: 2025-12-16

Learn how to implement enemy AI pursuit and patrol behavior while avoiding obstacles using Godot Engine's NavigationAgent2D, with practical code examples and performance optimization tips.

Introduction: Why Game AI Needs Pathfinding

In game development, enemy and NPC movement significantly affects player experience quality. When walls, obstacles, and complex maps exist, AI needs to find the optimal path to destinations. This technique of "finding optimal paths" is pathfinding.

Godot Engine provides the NavigationAgent2D node to easily and efficiently implement this complex pathfinding processing.


How NavigationAgent2D Works and Basic Setup

Godot's navigation system consists of three main components:

ElementNode NameRole
Navigation MapNavigationServer2DBackend server managing navigable area data for the entire scene.
Navigable AreaNavigationRegion2DDefines areas (navigation mesh) where AI can move on the map.
Pathfinding AgentNavigationAgent2DRequests path calculations and controls parent node (AI character) movement.
Dynamic ObstaclesNavigationObstacle2DDefines dynamic obstacles like moving doors or other characters for agents to avoid.

Basic Setup

To make NavigationAgent2D functional, you first need to define navigable areas on the map.

  1. Place NavigationRegion2D: Add a NavigationRegion2D node to the scene tree.
  2. Create NavigationPolygon: In the NavigationRegion2D inspector, create a new NavigationPolygon resource and edit it to cover the navigable parts of the map.
  3. Prepare AI Character: Add a NavigationAgent2D node as a child of the enemy AI's root node (e.g., CharacterBody2D).

Practice 1: Player Pursuit by Enemy AI

Enemy AI chasing the player is the most fundamental pathfinding use case. With NavigationAgent2D, you can easily create AI that follows the shortest path while avoiding obstacles.

# EnemyAI.gd (Attached to CharacterBody2D)
extends CharacterBody2D

@export var speed: float = 250.0

# Reference to player node (set from inspector)
@export var player: Node2D

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    # max_speed is a parameter for RVO (Reciprocal Velocity Obstacle) avoidance
    # Actual movement speed is controlled by the speed variable in velocity calculation
    navigation_agent.max_speed = speed
    # Do nothing if player doesn't exist
    if not is_instance_valid(player):
        set_physics_process(false)

func _physics_process(delta: float) -> void:
    # Do nothing more if destination is reached
    if navigation_agent.is_navigation_finished():
        velocity = Vector2.ZERO
        move_and_slide()
        return

    # Set player position as target
    # For performance, updating via Timer rather than every frame is preferable (see below)
    navigation_agent.target_position = player.global_position

    # Get the next point to move toward
    var next_path_position: Vector2 = navigation_agent.get_next_path_position()
    # Calculate direction to that point
    var direction: Vector2 = global_position.direction_to(next_path_position)

    # Move along calculated direction
    velocity = direction * speed
    move_and_slide()

The core of this code is its simplicity: set navigation_agent.target_position to the player's coordinates, then move_and_slide() toward the coordinates returned by navigation_agent.get_next_path_position(). All complex path calculation and obstacle avoidance logic is handled internally by NavigationAgent2D.


Practice 2: Guard AI Patrolling a Fixed Route

Patrol behavior where enemy AI cycles through multiple points is also easily implemented with NavigationAgent2D. The is_navigation_finished() function for determining arrival plays a key role.

# PatrolAI.gd (Attached to CharacterBody2D)
extends CharacterBody2D

@export var speed: float = 150.0
# Array of patrol points
@export var patrol_points: Array[Vector2] = [
    Vector2(100, 100), Vector2(800, 100), Vector2(800, 500), Vector2(100, 500)
]

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

var current_point_index: int = 0

func _ready() -> void:
    navigation_agent.max_speed = speed
    # Set first patrol point as target
    _set_next_patrol_point()

func _physics_process(delta: float) -> void:
    # is_navigation_finished() is a reliable way to determine target arrival
    if navigation_agent.is_navigation_finished():
        # Move to next point
        current_point_index = (current_point_index + 1) % patrol_points.size()
        _set_next_patrol_point()

    # Move toward next point, similar to pursuit logic
    var next_path_position: Vector2 = navigation_agent.get_next_path_position()
    var direction: Vector2 = global_position.direction_to(next_path_position)

    velocity = direction * speed
    move_and_slide()

func _set_next_patrol_point() -> void:
    if patrol_points.is_empty():
        return
    navigation_agent.target_position = patrol_points[current_point_index]

Common Mistakes and Best Practices

NavigationAgent2D is powerful, but misuse can cause unexpected behavior and performance degradation.

Common MistakeBest Practice
Updating target_position every frameUse a Timer node to update target position every 0.1–0.3 seconds. Or incorporate logic to only update when distance from target exceeds a threshold.
Using only is_navigation_finished() for arrival detectionAlso use navigation_agent.distance_to_target() to consider arrival when within a certain tolerance from the target for more reliable detection.
Setting velocity directlyUse velocity = velocity.lerp(direction * speed, weight) to smoothly interpolate from current to target velocity for natural acceleration/deceleration.
Manually creating complex shapesUse TileMap node's layer feature to create scripts that automatically generate navigation meshes from specific tiles.

Performance Considerations and Alternative Patterns

Performance Optimization

  • Path Recalculation Frequency: Updating target_position can become the biggest bottleneck. Set update frequency appropriately based on AI importance and count.
  • Dynamic Obstacles (NavigationObstacle2D): Using NavigationObstacle2D for moving floors or doors is convenient but costly. Keep usage to a minimum.
  • Number of Agents: Simplifying or pausing processing (set_physics_process(false)) for distant or off-screen AI is effective optimization.

Alternative Pattern: Comparison with AStar2D

FeatureNavigationAgent2DAStar2D
Abstraction LevelHigh-level (easy to use)Low-level (highly flexible)
SetupJust bake NavigationRegion2DMust manually define points and connections
Obstacle AvoidanceAutomatic (supports dynamic obstacles)Must implement yourself
Use CasesCharacter spatial movementGrid-based games, turn-based strategy

For typical cases where characters move around maps in real-time, NavigationAgent2D is convenient and efficient.


Summary

Godot Engine's NavigationAgent2D is a powerful tool that dramatically simplifies AI movement logic in 2D games.

  • Define navigable areas with NavigationRegion2D
  • Set target position on NavigationAgent2D
  • Move the character toward the next point obtained from get_next_path_position()

With this simple procedure, you can immediately add practical, natural AI to your game that intelligently avoids obstacles, pursues players, or patrols fixed routes.