2025-05-03 14:35:35 +01:00
using UnityEditor ;
using UnityEditor.AI ;
using UnityEditor.Overlays ;
using UnityEditor.SceneManagement ;
using UnityEngine ;
using UnityEngine.UIElements ;
namespace Unity.AI.Navigation.Editor
{
[Overlay(typeof(SceneView), k_OverlayId, "AI Navigation", defaultDisplay = false)]
[Icon(NavMeshComponentsGUIUtility.k_PackageEditorResourcesFolder + "Overlay/NavigationOverlay.png")]
internal class NavigationOverlay : Overlay
{
const string k_OverlayId = "AINavigationOverlay" ;
static class Style
{
internal static readonly GUIContent SurfacesSectionTexts =
EditorGUIUtility . TrTextContent ( "Surfaces" ) ;
internal static readonly GUIContent SurfacesSelectedOnlyTexts =
EditorGUIUtility . TrTextContent ( "Show Only Selected" , "Check this to hide surfaces which are not part of the selection hierarchy" ) ;
internal static readonly GUIContent SurfacesNavMeshTexts =
EditorGUIUtility . TrTextContent ( "Show NavMesh" , "Display navigation mesh using the associated area's color" ) ;
internal static readonly GUIContent SurfacesHeightMeshTexts =
EditorGUIUtility . TrTextContent ( "Show HeightMesh" , "Display height mesh used for accurate vertical placement" ) ;
internal static readonly GUIContent AgentsSectionTexts =
EditorGUIUtility . TrTextContent ( "Agents" ) ;
internal static readonly GUIContent AgentsPathPolysTexts =
EditorGUIUtility . TrTextContent ( "Show Path Polygons" , "Shows the polygons leading to goal." ) ;
internal static readonly GUIContent AgentsPathNodesTexts =
EditorGUIUtility . TrTextContent ( "Show Path Query Nodes" , "Shows the nodes expanded during last path query." ) ;
internal static readonly GUIContent AgentsNeighboursTexts =
EditorGUIUtility . TrTextContent ( "Show Neighbours" , "Show the agent neighbours considered during simulation." ) ;
internal static readonly GUIContent AgentsWallsTexts =
EditorGUIUtility . TrTextContent ( "Show Walls" , "Shows the wall segments handled during simulation." ) ;
internal static readonly GUIContent AgentsAvoidanceTexts =
EditorGUIUtility . TrTextContent ( "Show Avoidance" , "Shows the processed avoidance geometry from simulation." ) ;
internal static readonly GUIContent AgentsAvoidancePendingDebugRequestTexts =
EditorGUIUtility . TrTextContent ( "Avoidance display is not valid until after next game update." , "Avoidance information will be computed on the next game update" ) ;
internal static readonly GUIContent AgentsAvoidanceDebugRequestsCountExceededTexts =
EditorGUIUtility . TrTextContent ( "" , "Avoidance information display is limited to a fixed number of agents" ) ; // This text is dynamic
internal static readonly GUIContent ObstaclesSectionTexts =
EditorGUIUtility . TrTextContent ( "Obstacles" ) ;
internal static readonly GUIContent ObstaclesCarveHullText =
EditorGUIUtility . TrTextContent ( "Show Carve Hull" , "Shows the hull used to carve the obstacle from the NavMesh." ) ;
internal static readonly GUIContent DeveloperModeSectionTexts =
EditorGUIUtility . TrTextContent ( "Developer Mode" ) ;
internal static readonly GUIContent SurfacesPortalsTexts =
EditorGUIUtility . TrTextContent ( "Show NavMesh Portals" ) ;
internal static readonly GUIContent SurfacesTileLinksTexts =
EditorGUIUtility . TrTextContent ( "Show NavMesh Tile Links" ) ;
internal static readonly GUIContent SurfacesHeightMeshBVTreeTexts =
EditorGUIUtility . TrTextContent ( "Show HeightMesh BV-Tree" ) ;
internal static readonly GUIContent SurfacesHeightMapsTexts =
EditorGUIUtility . TrTextContent ( "Show HeightMaps" , "Display terrain height maps used for accurate vertical placement" ) ;
internal static readonly GUIContent SurfacesProximityGridTexts =
EditorGUIUtility . TrTextContent ( "Show Proximity Grid" ) ;
internal static readonly GUIContent NavigationVisualizationDisabledTexts =
EditorGUIUtility . TrTextContent ( "Navigation visualization is not available in prefab edition." , "" ) ;
}
VisualElement m_RootPanel ;
VisualElement m_OptionsPanel ;
VisualElement m_AgentFoldOut ;
HelpBox m_VisualizationDisabledHelpBox ;
HelpBox m_AgentCountWarning ;
HelpBox m_AgentPendingRequestWarning ;
public override void OnCreated ( )
{
base . OnCreated ( ) ;
NavMeshEditorHelpers . agentRejectedDebugInfoRequestsCountChanged + = OnAgentRejectedDebugInfoRequestsCountChanged ;
NavMeshEditorHelpers . agentDebugRequestsPending + = DisplayAgentPendingRequestWarningBox ;
NavMeshEditorHelpers . agentDebugRequestsProcessed + = HideAgentPendingRequestWarningBox ;
PrefabStage . prefabStageOpened + = OnPrefabStageOpened ;
PrefabStage . prefabStageClosing + = OnPrefabStageClosing ;
}
public override void OnWillBeDestroyed ( )
{
NavMeshEditorHelpers . agentRejectedDebugInfoRequestsCountChanged - = OnAgentRejectedDebugInfoRequestsCountChanged ;
NavMeshEditorHelpers . agentDebugRequestsPending - = DisplayAgentPendingRequestWarningBox ;
NavMeshEditorHelpers . agentDebugRequestsProcessed - = HideAgentPendingRequestWarningBox ;
PrefabStage . prefabStageOpened - = OnPrefabStageOpened ;
PrefabStage . prefabStageClosing - = OnPrefabStageClosing ;
base . OnWillBeDestroyed ( ) ;
}
public override VisualElement CreatePanelContent ( )
{
m_RootPanel = new VisualElement ( ) ;
m_OptionsPanel = new VisualElement ( ) ;
m_RootPanel . Add ( m_OptionsPanel ) ;
m_VisualizationDisabledHelpBox = AddHelpBox ( HelpBoxMessageType . Info , Style . NavigationVisualizationDisabledTexts , 200 , 10 , false ) ;
var visualizationEnabled = PrefabStageUtility . GetCurrentPrefabStage ( ) = = null ;
SetVisualizationEnabled ( visualizationEnabled ) ;
// Surfaces
var surfacesFoldout = AddFoldout ( m_OptionsPanel , Style . SurfacesSectionTexts , 5 ) ;
AddToggle ( surfacesFoldout , Style . SurfacesSelectedOnlyTexts , NavMeshVisualizationSettings . showOnlySelectedSurfaces ,
( evt = > NavMeshVisualizationSettings . showOnlySelectedSurfaces = evt . newValue ) ) ;
AddToggle ( surfacesFoldout , Style . SurfacesNavMeshTexts , NavMeshVisualizationSettings . showNavMesh ,
evt = > NavMeshVisualizationSettings . showNavMesh = evt . newValue ) ;
AddToggle ( surfacesFoldout , Style . SurfacesHeightMeshTexts , NavMeshVisualizationSettings . showHeightMesh ,
evt = > NavMeshVisualizationSettings . showHeightMesh = evt . newValue ) ;
// Agents
m_AgentFoldOut = AddFoldout ( m_OptionsPanel , Style . AgentsSectionTexts , 5 ) ;
AddToggle ( m_AgentFoldOut , Style . AgentsPathPolysTexts , NavMeshVisualizationSettings . showAgentPath , evt = > NavMeshVisualizationSettings . showAgentPath = evt . newValue ) ;
AddToggle ( m_AgentFoldOut , Style . AgentsPathNodesTexts , NavMeshVisualizationSettings . showAgentPathInfo ,
evt = > NavMeshVisualizationSettings . showAgentPathInfo = evt . newValue ) ;
AddToggle ( m_AgentFoldOut , Style . AgentsNeighboursTexts , NavMeshVisualizationSettings . showAgentNeighbours ,
evt = > NavMeshVisualizationSettings . showAgentNeighbours = evt . newValue ) ;
AddToggle ( m_AgentFoldOut , Style . AgentsWallsTexts , NavMeshVisualizationSettings . showAgentWalls , evt = > NavMeshVisualizationSettings . showAgentWalls = evt . newValue ) ;
AddToggle ( m_AgentFoldOut , Style . AgentsAvoidanceTexts , NavMeshVisualizationSettings . showAgentAvoidance ,
evt = > NavMeshVisualizationSettings . showAgentAvoidance = evt . newValue ) ;
// Create avoidance requests count warning box and display it if needed
m_AgentCountWarning = AddHelpBox ( HelpBoxMessageType . Warning , Style . AgentsAvoidanceDebugRequestsCountExceededTexts , 180 , 5 , false ) ;
NavMeshEditorHelpers . GetAgentsDebugInfoRejectedRequestsCount ( out var rejected , out var allowed ) ;
if ( rejected > 0 )
{
DisplayAgentCountWarningBox ( rejected , allowed ) ;
}
// Create avoidance pending requests warning box and display it if needed
m_AgentPendingRequestWarning = AddHelpBox ( HelpBoxMessageType . Warning , Style . AgentsAvoidancePendingDebugRequestTexts , 180 , 5 , false ) ;
if ( NavMeshEditorHelpers . HasPendingAgentDebugInfoRequests ( ) )
{
DisplayAgentPendingRequestWarningBox ( ) ;
}
// Obstacles
var obstaclesFoldout = AddFoldout ( m_OptionsPanel , Style . ObstaclesSectionTexts , 5 ) ;
AddToggle ( obstaclesFoldout , Style . ObstaclesCarveHullText , NavMeshVisualizationSettings . showObstacleCarveHull ,
evt = > NavMeshVisualizationSettings . showObstacleCarveHull = evt . newValue ) ;
// Developer Mode only
if ( Unsupported . IsDeveloperMode ( ) )
{
var developerModeFoldout = AddFoldout ( m_OptionsPanel , Style . DeveloperModeSectionTexts , 5 ) ;
AddToggle ( developerModeFoldout , Style . SurfacesPortalsTexts , NavMeshVisualizationSettings . showNavMeshPortals ,
evt = > NavMeshVisualizationSettings . showNavMeshPortals = evt . newValue ) ;
AddToggle ( developerModeFoldout , Style . SurfacesTileLinksTexts , NavMeshVisualizationSettings . showNavMeshLinks ,
evt = > NavMeshVisualizationSettings . showNavMeshLinks = evt . newValue ) ;
AddToggle ( developerModeFoldout , Style . SurfacesHeightMeshBVTreeTexts , NavMeshVisualizationSettings . showHeightMeshBVTree ,
evt = > NavMeshVisualizationSettings . showHeightMeshBVTree = evt . newValue ) ;
AddToggle ( developerModeFoldout , Style . SurfacesHeightMapsTexts , NavMeshVisualizationSettings . showHeightMaps ,
evt = > NavMeshVisualizationSettings . showHeightMaps = evt . newValue ) ;
AddToggle ( developerModeFoldout , Style . SurfacesProximityGridTexts , NavMeshVisualizationSettings . showProximityGrid ,
evt = > NavMeshVisualizationSettings . showProximityGrid = evt . newValue ) ;
}
return m_RootPanel ;
}
void OnAgentRejectedDebugInfoRequestsCountChanged ( int rejected , int allowed )
{
if ( rejected = = 0 )
{
HideAgentCountWarningBox ( ) ;
}
else
{
DisplayAgentCountWarningBox ( rejected , allowed ) ;
}
}
void OnPrefabStageOpened ( PrefabStage obj )
{
SetVisualizationEnabled ( false ) ;
}
void OnPrefabStageClosing ( PrefabStage obj )
{
SetVisualizationEnabled ( true ) ;
}
static Foldout AddFoldout ( VisualElement parent , GUIContent text , int padding )
{
var prefName = $"AINavigationOverlay_Foldout_{text.text}" ;
var foldout = new Foldout
{
text = text . text ,
tooltip = text . tooltip ,
style =
{
paddingBottom = padding
} ,
value = EditorPrefs . GetBool ( prefName , true )
} ;
foldout . RegisterValueChangedCallback ( evt = >
{
EditorPrefs . SetBool ( prefName , evt . newValue ) ;
#if OVERLAY_REFRESH_API
var sceneView = EditorWindow . GetWindow < SceneView > ( ) ;
if ( sceneView . TryGetOverlay ( k_OverlayId , out var overlay ) )
overlay . RefreshPopup ( ) ;
#endif
} ) ;
parent . Add ( foldout ) ;
return foldout ;
}
static void AddToggle ( VisualElement parent , GUIContent text , bool parameter , EventCallback < ChangeEvent < bool > > callback )
{
// Create toggle element with the desired text content
var toggle = new Toggle
{
label = text . text ,
value = parameter ,
tooltip = text . tooltip ,
style =
{
marginBottom = 0 // To compact a bit the checkboxes list layout
}
} ;
// Add padding to guarantee a minimum separation between labels and checkboxes
toggle . labelElement . style . paddingRight = 20 ;
2025-06-06 18:04:48 +01:00
// Look for the checkbox container and make it align the checkbox to the right (so that all checkboxes are justified)
2025-05-03 14:35:35 +01:00
foreach ( VisualElement child in toggle . Children ( ) )
{
if ( child ! = toggle . labelElement )
{
child . style . justifyContent = Justify . FlexEnd ;
break ;
}
}
toggle . RegisterCallback ( callback ) ;
parent . Add ( toggle ) ;
}
static HelpBox AddHelpBox ( HelpBoxMessageType messageType , GUIContent text , int maxWidth , int verticalMargin , bool visible )
{
var helpBox = new HelpBox ( text . text , messageType )
{
tooltip = text . tooltip ,
style =
{
marginBottom = verticalMargin ,
marginTop = verticalMargin ,
maxWidth = maxWidth ,
alignSelf = Align . Center ,
} ,
visible = visible
} ;
return helpBox ;
}
void DisplayAgentCountWarningBox ( int rejected , int allowed )
{
m_AgentCountWarning . text = $"Avoidance visualization can be drawn for {allowed} agents ({allowed + rejected} selected)." ;
if ( ! m_AgentCountWarning . visible )
{
m_AgentCountWarning . visible = true ;
m_AgentFoldOut . Add ( m_AgentCountWarning ) ;
}
}
void HideAgentCountWarningBox ( )
{
if ( m_AgentCountWarning . visible )
{
m_AgentCountWarning . visible = false ;
m_AgentFoldOut . Remove ( m_AgentCountWarning ) ;
}
}
void DisplayAgentPendingRequestWarningBox ( )
{
if ( ! m_AgentPendingRequestWarning . visible )
{
m_AgentPendingRequestWarning . visible = true ;
m_AgentFoldOut . Add ( m_AgentPendingRequestWarning ) ;
}
}
void HideAgentPendingRequestWarningBox ( )
{
if ( m_AgentPendingRequestWarning . visible )
{
m_AgentPendingRequestWarning . visible = false ;
m_AgentFoldOut . Remove ( m_AgentPendingRequestWarning ) ;
}
}
void SetVisualizationEnabled ( bool enabled )
{
if ( m_VisualizationDisabledHelpBox = = null )
return ;
2025-06-06 18:04:48 +01:00
2025-05-03 14:35:35 +01:00
if ( enabled )
{
if ( m_VisualizationDisabledHelpBox . visible )
{
m_RootPanel . Remove ( m_VisualizationDisabledHelpBox ) ;
m_OptionsPanel . SetEnabled ( true ) ;
m_OptionsPanel . tooltip = "" ;
m_VisualizationDisabledHelpBox . visible = false ;
}
}
else
{
if ( ! m_VisualizationDisabledHelpBox . visible )
{
m_RootPanel . Insert ( 0 , m_VisualizationDisabledHelpBox ) ;
m_OptionsPanel . SetEnabled ( false ) ;
m_OptionsPanel . tooltip = Style . NavigationVisualizationDisabledTexts . tooltip ;
m_VisualizationDisabledHelpBox . visible = true ;
}
}
}
}
}