Navigation mesh generation and pathfinding skill for game AI. Enables creation and configuration of navigation meshes, pathfinding queries, dynamic obstacles, and navigation agent setup across Unity, Unreal, and Godot engines.
Generates navigation meshes and implements pathfinding for AI agents across Unity, Unreal, and Godot game engines.
npx claudepluginhub a5c-ai/babysitterThis skill is limited to using the following tools:
README.mdComprehensive navigation mesh generation and pathfinding implementation for game AI systems across multiple engines.
This skill provides capabilities for creating, configuring, and utilizing navigation meshes for AI pathfinding. It covers navmesh generation, agent configuration, dynamic obstacles, off-mesh links, and runtime navigation queries.
// Built-in: Package Manager > AI Navigation
// Install: com.unity.ai.navigation
// Enable NavigationSystem module in Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"NavigationSystem",
"AIModule"
});
# Enable NavigationServer2D/3D in project settings
# Use NavigationRegion2D/3D and NavigationAgent2D/3D nodes
// NavMeshAgent configuration
using UnityEngine;
using UnityEngine.AI;
public class AINavigation : MonoBehaviour
{
[Header("Navigation Settings")]
[SerializeField] private float moveSpeed = 3.5f;
[SerializeField] private float angularSpeed = 120f;
[SerializeField] private float stoppingDistance = 0.5f;
private NavMeshAgent _agent;
private Transform _target;
private void Awake()
{
_agent = GetComponent<NavMeshAgent>();
ConfigureAgent();
}
private void ConfigureAgent()
{
_agent.speed = moveSpeed;
_agent.angularSpeed = angularSpeed;
_agent.stoppingDistance = stoppingDistance;
_agent.autoBraking = true;
}
public void SetDestination(Vector3 destination)
{
if (NavMesh.SamplePosition(destination, out NavMeshHit hit, 2f, NavMesh.AllAreas))
{
_agent.SetDestination(hit.position);
}
}
public void SetTarget(Transform target)
{
_target = target;
}
private void Update()
{
if (_target != null)
{
SetDestination(_target.position);
}
}
public bool HasReachedDestination()
{
if (!_agent.pathPending)
{
if (_agent.remainingDistance <= _agent.stoppingDistance)
{
if (!_agent.hasPath || _agent.velocity.sqrMagnitude == 0f)
{
return true;
}
}
}
return false;
}
public bool IsPathValid()
{
return _agent.hasPath && _agent.pathStatus == NavMeshPathStatus.PathComplete;
}
}
// Dynamic NavMesh Obstacle
using UnityEngine;
using UnityEngine.AI;
public class DynamicObstacle : MonoBehaviour
{
private NavMeshObstacle _obstacle;
private void Awake()
{
_obstacle = GetComponent<NavMeshObstacle>();
_obstacle.carving = true;
_obstacle.carvingMoveThreshold = 0.1f;
_obstacle.carvingTimeToStationary = 0.5f;
}
public void EnableCarving(bool enable)
{
_obstacle.carving = enable;
}
}
// Off-Mesh Link Setup
using UnityEngine;
using UnityEngine.AI;
public class JumpLink : MonoBehaviour
{
[SerializeField] private Transform startPoint;
[SerializeField] private Transform endPoint;
[SerializeField] private bool bidirectional = false;
private OffMeshLink _link;
private void Awake()
{
_link = gameObject.AddComponent<OffMeshLink>();
_link.startTransform = startPoint;
_link.endTransform = endPoint;
_link.biDirectional = bidirectional;
_link.autoUpdatePositions = true;
}
}
// NavMesh Surface Runtime Baking
using UnityEngine;
using Unity.AI.Navigation;
public class RuntimeNavMesh : MonoBehaviour
{
private NavMeshSurface _surface;
private void Awake()
{
_surface = GetComponent<NavMeshSurface>();
}
public void RebuildNavMesh()
{
_surface.BuildNavMesh();
}
public void UpdateNavMesh()
{
_surface.UpdateNavMesh(_surface.navMeshData);
}
}
// AINavigationComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "NavigationSystem.h"
#include "AINavigationComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UAINavigationComponent : public UActorComponent
{
GENERATED_BODY()
public:
UAINavigationComponent();
UFUNCTION(BlueprintCallable, Category = "Navigation")
bool MoveToLocation(FVector Destination);
UFUNCTION(BlueprintCallable, Category = "Navigation")
bool MoveToActor(AActor* TargetActor);
UFUNCTION(BlueprintCallable, Category = "Navigation")
void StopMovement();
UFUNCTION(BlueprintCallable, Category = "Navigation")
bool HasReachedDestination() const;
UFUNCTION(BlueprintCallable, Category = "Navigation")
FVector GetRandomReachablePoint(float Radius) const;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Navigation")
float AcceptanceRadius = 50.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Navigation")
bool bStopOnOverlap = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Navigation")
bool bUsePathfinding = true;
private:
class AAIController* AIController;
class UNavigationSystemV1* NavSystem;
};
// AINavigationComponent.cpp
#include "AINavigationComponent.h"
#include "AIController.h"
#include "NavigationSystem.h"
#include "NavFilters/NavigationQueryFilter.h"
UAINavigationComponent::UAINavigationComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UAINavigationComponent::BeginPlay()
{
Super::BeginPlay();
APawn* Pawn = Cast<APawn>(GetOwner());
if (Pawn)
{
AIController = Cast<AAIController>(Pawn->GetController());
}
NavSystem = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
}
bool UAINavigationComponent::MoveToLocation(FVector Destination)
{
if (!AIController) return false;
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalLocation(Destination);
MoveRequest.SetAcceptanceRadius(AcceptanceRadius);
MoveRequest.SetStopOnOverlap(bStopOnOverlap);
MoveRequest.SetUsePathfinding(bUsePathfinding);
FNavPathSharedPtr Path;
AIController->MoveTo(MoveRequest, &Path);
return Path.IsValid();
}
bool UAINavigationComponent::MoveToActor(AActor* TargetActor)
{
if (!AIController || !TargetActor) return false;
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalActor(TargetActor);
MoveRequest.SetAcceptanceRadius(AcceptanceRadius);
MoveRequest.SetStopOnOverlap(bStopOnOverlap);
MoveRequest.SetUsePathfinding(bUsePathfinding);
FNavPathSharedPtr Path;
AIController->MoveTo(MoveRequest, &Path);
return Path.IsValid();
}
void UAINavigationComponent::StopMovement()
{
if (AIController)
{
AIController->StopMovement();
}
}
bool UAINavigationComponent::HasReachedDestination() const
{
if (!AIController) return false;
return AIController->GetMoveStatus() == EPathFollowingStatus::Idle;
}
FVector UAINavigationComponent::GetRandomReachablePoint(float Radius) const
{
FNavLocation RandomPoint;
if (NavSystem && NavSystem->GetRandomReachablePointInRadius(GetOwner()->GetActorLocation(), Radius, RandomPoint))
{
return RandomPoint.Location;
}
return GetOwner()->GetActorLocation();
}
// NavMesh Modifier Volume (Blueprint-friendly)
// BTTask_MoveToLocation.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_MoveToLocation.generated.h"
UCLASS()
class MYGAME_API UBTTask_MoveToLocation : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_MoveToLocation();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
protected:
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetLocationKey;
UPROPERTY(EditAnywhere, Category = "Movement")
float AcceptableRadius = 50.0f;
};
# navigation_controller.gd
extends CharacterBody2D
class_name NavigationController
## Movement speed in pixels per second
@export var move_speed: float = 200.0
## Arrival distance threshold
@export var arrival_distance: float = 10.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
var _is_navigating: bool = false
signal destination_reached
signal path_changed
func _ready() -> void:
nav_agent.velocity_computed.connect(_on_velocity_computed)
nav_agent.path_changed.connect(_on_path_changed)
nav_agent.target_reached.connect(_on_target_reached)
# Configure agent
nav_agent.path_desired_distance = arrival_distance
nav_agent.target_desired_distance = arrival_distance
func _physics_process(delta: float) -> void:
if not _is_navigating:
return
if nav_agent.is_navigation_finished():
_is_navigating = false
destination_reached.emit()
return
var next_path_position := nav_agent.get_next_path_position()
var direction := global_position.direction_to(next_path_position)
var velocity := direction * move_speed
if nav_agent.avoidance_enabled:
nav_agent.velocity = velocity
else:
_move(velocity)
func set_target_position(target: Vector2) -> void:
nav_agent.target_position = target
_is_navigating = true
func set_target_node(target: Node2D) -> void:
set_target_position(target.global_position)
func stop_navigation() -> void:
_is_navigating = false
velocity = Vector2.ZERO
func is_navigating() -> bool:
return _is_navigating
func get_remaining_distance() -> float:
return nav_agent.distance_to_target()
func _move(vel: Vector2) -> void:
velocity = vel
move_and_slide()
func _on_velocity_computed(safe_velocity: Vector2) -> void:
_move(safe_velocity)
func _on_path_changed() -> void:
path_changed.emit()
func _on_target_reached() -> void:
_is_navigating = false
destination_reached.emit()
# navigation_region_setup.gd
@tool
extends NavigationRegion2D
@export var bake_on_ready: bool = true
@export var auto_rebake_interval: float = 0.0
var _rebake_timer: float = 0.0
func _ready() -> void:
if not Engine.is_editor_hint() and bake_on_ready:
call_deferred("bake_navigation_polygon")
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
if auto_rebake_interval > 0:
_rebake_timer += delta
if _rebake_timer >= auto_rebake_interval:
_rebake_timer = 0.0
bake_navigation_polygon()
func rebake() -> void:
bake_navigation_polygon()
# dynamic_obstacle.gd
extends Node2D
class_name DynamicNavObstacle
@export var obstacle_vertices: PackedVector2Array
@export var affect_navigation: bool = true
@onready var nav_obstacle: NavigationObstacle2D = $NavigationObstacle2D
func _ready() -> void:
if obstacle_vertices.size() > 0:
nav_obstacle.vertices = obstacle_vertices
nav_obstacle.avoidance_enabled = affect_navigation
func set_vertices(vertices: PackedVector2Array) -> void:
nav_obstacle.vertices = vertices
func enable_avoidance(enabled: bool) -> void:
nav_obstacle.avoidance_enabled = enabled
# navigation_link.gd
extends NavigationLink2D
@export var link_cost: float = 1.0
@export_enum("Bidirectional", "Start to End", "End to Start") var direction: int = 0
func _ready() -> void:
travel_cost = link_cost
bidirectional = (direction == 0)
if direction == 2:
# Swap start and end for "End to Start"
var temp := start_position
start_position = end_position
end_position = temp
bidirectional = false
const navmeshSetupTask = defineTask({
name: 'navmesh-setup',
description: 'Configure navigation mesh for AI pathfinding',
inputs: {
engine: { type: 'string', required: true }, // unity, unreal, godot
agentType: { type: 'string', required: true },
settings: { type: 'object', required: true },
outputPath: { type: 'string', required: true }
},
outputs: {
configPath: { type: 'string' },
componentFiles: { type: 'array' },
success: { type: 'boolean' }
},
async run(inputs, taskCtx) {
return {
kind: 'skill',
title: `Setup navmesh for ${inputs.agentType}`,
skill: {
name: 'navmesh',
context: {
operation: 'configure_navigation',
engine: inputs.engine,
agentType: inputs.agentType,
settings: inputs.settings,
outputPath: inputs.outputPath
}
},
io: {
inputJsonPath: `tasks/${taskCtx.effectId}/input.json`,
outputJsonPath: `tasks/${taskCtx.effectId}/result.json`
}
};
}
});
| Parameter | Description | Typical Values |
|---|---|---|
| Agent Radius | Collision radius | 0.3-0.6 meters |
| Agent Height | Full agent height | 1.5-2.0 meters |
| Max Slope | Walkable slope angle | 30-45 degrees |
| Step Height | Climbable step | 0.3-0.5 meters |
| Max Speed | Movement speed | 3-10 m/s |
| Acceleration | Speed change rate | 8-20 m/s^2 |
| Area Type | Cost | Use Case |
|---|---|---|
| Walkable | 1.0 | Default ground |
| Road | 0.5 | Preferred paths |
| Grass | 1.5 | Slower terrain |
| Water (shallow) | 2.0 | Passable but slow |
| Water (deep) | Infinity | Not passable |
| Danger | 3.0 | Avoid if possible |
| Optimization | Description |
|---|---|
| Hierarchical Pathfinding | Pre-compute region graph for long paths |
| Path Caching | Reuse paths when destination unchanged |
| Async Pathfinding | Don't block main thread |
| NavMesh Tiles | Enable incremental updates |
| Query Filters | Limit search scope |
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.