Skip to main content
Glama
RobotNavigator.cs10.4 kB
using UnityEngine; using UnityEngine.AI; using System.Collections.Generic; using System.Linq; /// <summary> /// Navigation controller for virtual robots in Unity. /// Handles path planning, waypoints, obstacle avoidance, and NavMesh navigation. /// Used by robotics-mcp robot_navigation tool via execute_unity_method. /// </summary> public class RobotNavigator : MonoBehaviour { private static Dictionary<string, RobotNavigator> instances = new Dictionary<string, RobotNavigator>(); private static Dictionary<string, PathData> paths = new Dictionary<string, PathData>(); [Header("Navigation Settings")] public float speed = 2f; public float stoppingDistance = 0.5f; public float avoidanceRadius = 1f; private NavMeshAgent agent; private List<Vector3> waypoints = new List<Vector3>(); private string currentPathId = ""; private bool isFollowingPath = false; [System.Serializable] public class PathData { public string pathId; public List<Vector3> waypoints; public string status; // "planned", "following", "completed", "failed" public float progress; // 0.0 to 1.0 } void Awake() { agent = GetComponent<NavMeshAgent>(); if (agent == null) { agent = gameObject.AddComponent<NavMeshAgent>(); } agent.speed = speed; agent.stoppingDistance = stoppingDistance; agent.radius = avoidanceRadius; } void Update() { if (isFollowingPath && waypoints.Count > 0) { if (!agent.pathPending && agent.remainingDistance < stoppingDistance) { waypoints.RemoveAt(0); if (waypoints.Count > 0) { agent.SetDestination(waypoints[0]); } else { isFollowingPath = false; if (paths.ContainsKey(currentPathId)) { paths[currentPathId].status = "completed"; paths[currentPathId].progress = 1.0f; } } } if (paths.ContainsKey(currentPathId)) { float totalDistance = 0f; float remainingDistance = agent.remainingDistance; for (int i = 0; i < waypoints.Count - 1; i++) { totalDistance += Vector3.Distance(waypoints[i], waypoints[i + 1]); } paths[currentPathId].progress = totalDistance > 0 ? 1.0f - (remainingDistance / totalDistance) : 1.0f; } } } /// <summary> /// Plan path from start to goal using NavMesh. /// </summary> public static Dictionary<string, object> PlanPath(string robotId, Dictionary<string, float> startPosition, Dictionary<string, float> goalPosition) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return null; return instance.PlanPathInstance(startPosition, goalPosition); } private Dictionary<string, object> PlanPathInstance(Dictionary<string, float> startPos, Dictionary<string, float> goalPos) { Vector3 start = new Vector3(startPos["x"], startPos["y"], startPos["z"]); Vector3 goal = new Vector3(goalPos["x"], goalPos["y"], goalPos["z"]); NavMeshPath path = new NavMeshPath(); bool pathFound = NavMesh.CalculatePath(start, goal, NavMesh.AllAreas, path); if (!pathFound) { return new Dictionary<string, object> { {"path_id", ""}, {"status", "failed"}, {"message", "Path not found - goal may be unreachable"} }; } string pathId = $"path_{System.Guid.NewGuid().ToString().Substring(0, 8)}"; List<Vector3> waypointList = path.corners.ToList(); PathData pathData = new PathData { pathId = pathId, waypoints = waypointList, status = "planned", progress = 0.0f }; paths[pathId] = pathData; return new Dictionary<string, object> { {"path_id", pathId}, {"status", "planned"}, {"waypoints", waypointList.Select(w => new Dictionary<string, float> { {"x", w.x}, {"y", w.y}, {"z", w.z} }).ToList()}, {"distance", CalculatePathDistance(waypointList)} }; } /// <summary> /// Follow a planned path. /// </summary> public static bool FollowPath(string robotId, string pathId) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return false; return instance.FollowPathInstance(pathId); } private bool FollowPathInstance(string pathId) { if (!paths.ContainsKey(pathId)) { Debug.LogError($"Path '{pathId}' not found."); return false; } PathData pathData = paths[pathId]; waypoints = new List<Vector3>(pathData.waypoints); currentPathId = pathId; pathData.status = "following"; isFollowingPath = true; if (waypoints.Count > 0) { agent.SetDestination(waypoints[0]); } return true; } /// <summary> /// Set a waypoint. /// </summary> public static bool SetWaypoint(string robotId, Dictionary<string, float> waypoint) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return false; instance.SetWaypointInstance(waypoint); return true; } private void SetWaypointInstance(Dictionary<string, float> waypoint) { Vector3 wp = new Vector3(waypoint["x"], waypoint["y"], waypoint["z"]); waypoints.Add(wp); Debug.Log($"{gameObject.name} waypoint added: {wp}"); } /// <summary> /// Clear all waypoints. /// </summary> public static bool ClearWaypoints(string robotId) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return false; instance.ClearWaypointsInstance(); return true; } private void ClearWaypointsInstance() { waypoints.Clear(); isFollowingPath = false; agent.ResetPath(); Debug.Log($"{gameObject.name} waypoints cleared"); } /// <summary> /// Get path execution status. /// </summary> public static Dictionary<string, object> GetPathStatus(string robotId, string pathId) { if (!paths.ContainsKey(pathId)) { return new Dictionary<string, object> { {"path_id", pathId}, {"status", "not_found"} }; } PathData pathData = paths[pathId]; return new Dictionary<string, object> { {"path_id", pathId}, {"status", pathData.status}, {"progress", pathData.progress}, {"waypoints_remaining", pathData.waypoints.Count} }; } /// <summary> /// Trigger obstacle avoidance. /// </summary> public static bool AvoidObstacle(string robotId, Dictionary<string, float> obstaclePosition) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return false; instance.AvoidObstacleInstance(obstaclePosition); return true; } private void AvoidObstacleInstance(Dictionary<string, float> obstaclePos) { Vector3 obstacle = new Vector3(obstaclePos["x"], obstaclePos["y"], obstaclePos["z"]); Vector3 direction = (transform.position - obstacle).normalized; Vector3 avoidanceTarget = transform.position + direction * avoidanceRadius * 2f; NavMeshHit hit; if (NavMesh.SamplePosition(avoidanceTarget, out hit, avoidanceRadius * 2f, NavMesh.AllAreas)) { agent.SetDestination(hit.position); Debug.Log($"{gameObject.name} avoiding obstacle at {obstacle}"); } } /// <summary> /// Get current path being followed. /// </summary> public static Dictionary<string, object> GetCurrentPath(string robotId) { RobotNavigator instance = GetInstance(robotId); if (instance == null) return null; return instance.GetCurrentPathInstance(); } private Dictionary<string, object> GetCurrentPathInstance() { if (string.IsNullOrEmpty(currentPathId) || !paths.ContainsKey(currentPathId)) { return new Dictionary<string, object> { {"path_id", ""}, {"status", "no_path"} }; } PathData pathData = paths[currentPathId]; return new Dictionary<string, object> { {"path_id", currentPathId}, {"status", pathData.status}, {"progress", pathData.progress}, {"waypoints", pathData.waypoints.Select(w => new Dictionary<string, float> { {"x", w.x}, {"y", w.y}, {"z", w.z} }).ToList()} }; } private float CalculatePathDistance(List<Vector3> waypoints) { float distance = 0f; for (int i = 0; i < waypoints.Count - 1; i++) { distance += Vector3.Distance(waypoints[i], waypoints[i + 1]); } return distance; } private static RobotNavigator GetInstance(string robotId) { if (instances.ContainsKey(robotId)) { return instances[robotId]; } GameObject robot = GameObject.Find(robotId); if (robot == null) { Debug.LogError($"Robot with ID '{robotId}' not found in scene."); return null; } RobotNavigator navigator = robot.GetComponent<RobotNavigator>(); if (navigator == null) { navigator = robot.AddComponent<RobotNavigator>(); } instances[robotId] = navigator; return navigator; } void OnDestroy() { instances.Remove(gameObject.name); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sandraschi/robotics-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server