using UnityEditor; using UnityEngine; namespace Unity.AI.Navigation.Editor { [CanEditMultipleObjects] [CustomEditor(typeof(NavMeshLink))] class NavMeshLinkEditor : UnityEditor.Editor { SerializedProperty m_AgentTypeID; SerializedProperty m_Area; SerializedProperty m_CostModifier; SerializedProperty m_AutoUpdatePosition; SerializedProperty m_Bidirectional; SerializedProperty m_EndPoint; SerializedProperty m_StartPoint; #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK SerializedProperty m_EndTransform; SerializedProperty m_StartTransform; SerializedProperty m_Activated; #endif SerializedProperty m_Width; static int s_SelectedID; static int s_SelectedPoint = -1; static Color s_HandleColor = new Color(255f, 167f, 39f, 210f) / 255; static Color s_HandleColorDisabled = new Color(255f * 0.75f, 167f * 0.75f, 39f * 0.75f, 100f) / 255; void OnEnable() { m_AgentTypeID = serializedObject.FindProperty("m_AgentTypeID"); m_Area = serializedObject.FindProperty("m_Area"); m_CostModifier = serializedObject.FindProperty("m_CostModifier"); m_AutoUpdatePosition = serializedObject.FindProperty("m_AutoUpdatePosition"); m_Bidirectional = serializedObject.FindProperty("m_Bidirectional"); m_EndPoint = serializedObject.FindProperty("m_EndPoint"); m_StartPoint = serializedObject.FindProperty("m_StartPoint"); #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK m_EndTransform = serializedObject.FindProperty("m_EndTransform"); m_StartTransform = serializedObject.FindProperty("m_StartTransform"); m_Activated = serializedObject.FindProperty("m_Activated"); #endif m_Width = serializedObject.FindProperty("m_Width"); s_SelectedID = 0; s_SelectedPoint = -1; #if !UNITY_2022_2_OR_NEWER NavMeshVisualizationSettings.showNavigation++; #endif } #if !UNITY_2022_2_OR_NEWER void OnDisable() { NavMeshVisualizationSettings.showNavigation--; } #endif static Matrix4x4 UnscaledLocalToWorldMatrix(Transform t) { return Matrix4x4.TRS(t.position, t.rotation, Vector3.one); } static void AlignTransformToEndPoints(NavMeshLink navLink) { var mat = UnscaledLocalToWorldMatrix(navLink.transform); var worldStartPt = mat.MultiplyPoint(navLink.localStartPosition); var worldEndPt = mat.MultiplyPoint(navLink.localEndPosition); var forward = worldEndPt - worldStartPt; var up = navLink.transform.up; // Flatten forward -= Vector3.Dot(up, forward) * up; var transform = navLink.transform; transform.rotation = Quaternion.LookRotation(forward, up); transform.position = (worldEndPt + worldStartPt) * 0.5f; transform.localScale = Vector3.one; #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK if (navLink.startRelativeToThisGameObject) navLink.startPoint = transform.InverseTransformPoint(worldStartPt); if (navLink.endRelativeToThisGameObject) navLink.endPoint = transform.InverseTransformPoint(worldEndPt); #else navLink.startPoint = transform.InverseTransformPoint(worldStartPt); navLink.endPoint = transform.InverseTransformPoint(worldEndPt); #endif } public override void OnInspectorGUI() { serializedObject.Update(); NavMeshComponentsGUIUtility.AgentTypePopup("Agent Type", m_AgentTypeID); EditorGUILayout.Space(); #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK m_StartPoint.isExpanded = EditorGUILayout.Foldout(m_StartPoint.isExpanded, "Positions"); if (m_StartPoint.isExpanded) { EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_StartTransform); if (EditorGUI.EndChangeCheck()) m_StartPoint.vector3Value = Vector3.zero; EditorGUILayout.PropertyField(m_StartPoint); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_EndTransform); if (EditorGUI.EndChangeCheck()) m_EndPoint.vector3Value = Vector3.zero; EditorGUILayout.PropertyField(m_EndPoint); GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUIUtility.labelWidth); if (GUILayout.Button(new GUIContent("Swap", "Alter link direction by swapping the start and end"))) { Swap(targets); SceneView.RepaintAll(); } if (GUILayout.Button(new GUIContent("Re-Center Origin", "Place this GameObject at the middle point between the start and end of this link."))) { foreach (var nml in targets) { var navLink = (NavMeshLink)nml; Undo.RecordObject(navLink.transform, "Align Transform to End Points"); Undo.RecordObject(navLink, "Align Transform to End Points"); AlignTransformToEndPoints(navLink); } SceneView.RepaintAll(); } GUILayout.EndHorizontal(); EditorGUI.indentLevel--; } #else EditorGUILayout.PropertyField(m_StartPoint); EditorGUILayout.PropertyField(m_EndPoint); GUILayout.BeginHorizontal(); GUILayout.Space(EditorGUIUtility.labelWidth); if (GUILayout.Button("Swap")) { foreach (NavMeshLink navLink in targets) { var tmp = navLink.startPoint; navLink.startPoint = navLink.endPoint; navLink.endPoint = tmp; } SceneView.RepaintAll(); } if (GUILayout.Button("Align Transform")) { foreach (NavMeshLink navLink in targets) { Undo.RecordObject(navLink.transform, "Align Transform to End Points"); Undo.RecordObject(navLink, "Align Transform to End Points"); AlignTransformToEndPoints(navLink); } SceneView.RepaintAll(); } GUILayout.EndHorizontal(); #endif EditorGUILayout.Space(); EditorGUILayout.PropertyField(m_Width); EditorGUILayout.PropertyField(m_CostModifier); EditorGUILayout.PropertyField(m_AutoUpdatePosition); EditorGUILayout.PropertyField(m_Bidirectional); NavMeshComponentsGUIUtility.AreaPopup("Area Type", m_Area); #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK EditorGUILayout.PropertyField(m_Activated); #endif serializedObject.ApplyModifiedProperties(); EditorGUILayout.Space(); } #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK private static void Swap(Object[] targets) { foreach (NavMeshLink navLink in targets) { var tmpStartPoint = navLink.startPoint; var tmpStartTransform = navLink.startTransform; navLink.startTransform = navLink.endTransform; navLink.startPoint = navLink.endPoint; navLink.endTransform = tmpStartTransform; navLink.endPoint = tmpStartPoint; } } #endif static Vector3 CalcLinkRight(NavMeshLink navLink) { var dir = navLink.localEndPosition - navLink.localStartPosition; return (new Vector3(-dir.z, 0.0f, dir.x)).normalized; } static void DrawLink(NavMeshLink navLink) { var right = CalcLinkRight(navLink); var rad = navLink.width * 0.5f; var edgeRadius = right * rad; var startPos = navLink.localStartPosition; var endPos = navLink.localEndPosition; var corners = new Vector3[4] { startPos - edgeRadius, startPos + edgeRadius, endPos + edgeRadius, endPos - edgeRadius }; Gizmos.DrawLineStrip(corners, true); } #if !UNITY_2022_2_OR_NEWER [DrawGizmo(GizmoType.Selected | GizmoType.Active | GizmoType.Pickable)] #else [DrawGizmo(GizmoType.InSelectionHierarchy | GizmoType.Active | GizmoType.Pickable)] #endif static void RenderBoxGizmo(NavMeshLink navLink, GizmoType gizmoType) { if (!EditorApplication.isPlaying && navLink.isActiveAndEnabled && navLink.HaveTransformsChanged()) navLink.UpdateLink(); var color = s_HandleColor; if (!navLink.enabled) color = s_HandleColorDisabled; var oldColor = Gizmos.color; var oldMatrix = Gizmos.matrix; Gizmos.matrix = UnscaledLocalToWorldMatrix(navLink.transform); Gizmos.color = color; DrawLink(navLink); Gizmos.matrix = oldMatrix; Gizmos.color = oldColor; Gizmos.DrawIcon(navLink.transform.position, "NavMeshLink Icon", true); } [DrawGizmo(GizmoType.NotInSelectionHierarchy | GizmoType.Pickable)] static void RenderBoxGizmoNotSelected(NavMeshLink navLink, GizmoType gizmoType) { if (!EditorApplication.isPlaying && navLink.isActiveAndEnabled && navLink.HaveTransformsChanged()) navLink.UpdateLink(); #if !UNITY_2022_2_OR_NEWER if (NavMeshVisualizationSettings.showNavigation > 0) #endif { var color = s_HandleColor; if (!navLink.enabled) color = s_HandleColorDisabled; var oldColor = Gizmos.color; var oldMatrix = Gizmos.matrix; Gizmos.matrix = UnscaledLocalToWorldMatrix(navLink.transform); Gizmos.color = color; DrawLink(navLink); Gizmos.matrix = oldMatrix; Gizmos.color = oldColor; } Gizmos.DrawIcon(navLink.transform.position, "NavMeshLink Icon", true); } public void OnSceneGUI() { var navLink = (NavMeshLink)target; if (!navLink.enabled) return; var mat = UnscaledLocalToWorldMatrix(navLink.transform); var worldStartPt = mat.MultiplyPoint(navLink.localStartPosition); var worldEndPt = mat.MultiplyPoint(navLink.localEndPosition); var worldMidPt = Vector3.Lerp(worldStartPt, worldEndPt, 0.35f); var startSize = HandleUtility.GetHandleSize(worldStartPt); var endSize = HandleUtility.GetHandleSize(worldEndPt); var midSize = HandleUtility.GetHandleSize(worldMidPt); var zup = Quaternion.FromToRotation(Vector3.forward, Vector3.up); var right = mat.MultiplyVector(CalcLinkRight(navLink)); var oldColor = Handles.color; Handles.color = s_HandleColor; Vector3 newWorldPos; #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK var startRelativeToThisGameObject = navLink.startRelativeToThisGameObject; if (navLink.GetInstanceID() == s_SelectedID && s_SelectedPoint == 0) { EditorGUI.BeginChangeCheck(); if (startRelativeToThisGameObject) Handles.CubeHandleCap(0, worldStartPt, zup, 0.1f * startSize, Event.current.type); else Handles.SphereHandleCap(0, worldStartPt, zup, 0.1f * startSize, Event.current.type); newWorldPos = Handles.PositionHandle(worldStartPt, navLink.transform.rotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Move link start point"); if (startRelativeToThisGameObject) navLink.startPoint = mat.inverse.MultiplyPoint(newWorldPos); else navLink.startPoint = newWorldPos - navLink.startTransform.position; } } else { if (Handles.Button(worldStartPt, zup, 0.1f * startSize, 0.1f * startSize, startRelativeToThisGameObject ? Handles.CubeHandleCap : Handles.SphereHandleCap)) { s_SelectedPoint = 0; s_SelectedID = navLink.GetInstanceID(); } } #else if (navLink.GetInstanceID() == s_SelectedID && s_SelectedPoint == 0) { EditorGUI.BeginChangeCheck(); Handles.CubeHandleCap(0, worldStartPt, zup, 0.1f * startSize, Event.current.type); newWorldPos = Handles.PositionHandle(worldStartPt, navLink.transform.rotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Move link point"); navLink.startPoint = mat.inverse.MultiplyPoint(newWorldPos); } } else { if (Handles.Button(worldStartPt, zup, 0.1f * startSize, 0.1f * startSize, Handles.CubeHandleCap)) { s_SelectedPoint = 0; s_SelectedID = navLink.GetInstanceID(); } } #endif #if ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK var endRelativeToThisGameObject = navLink.endRelativeToThisGameObject; if (navLink.GetInstanceID() == s_SelectedID && s_SelectedPoint == 1) { EditorGUI.BeginChangeCheck(); if (endRelativeToThisGameObject) Handles.CubeHandleCap(0, worldEndPt, zup, 0.1f * endSize, Event.current.type); else Handles.SphereHandleCap(0, worldEndPt, zup, 0.1f * endSize, Event.current.type); newWorldPos = Handles.PositionHandle(worldEndPt, navLink.transform.rotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Move link end point"); if (endRelativeToThisGameObject) navLink.endPoint = mat.inverse.MultiplyPoint(newWorldPos); else navLink.endPoint = newWorldPos - navLink.endTransform.position; } } else { if (Handles.Button(worldEndPt, zup, 0.1f * endSize, 0.1f * endSize, endRelativeToThisGameObject ? Handles.CubeHandleCap : Handles.SphereHandleCap)) { s_SelectedPoint = 1; s_SelectedID = navLink.GetInstanceID(); } } #else if (navLink.GetInstanceID() == s_SelectedID && s_SelectedPoint == 1) { EditorGUI.BeginChangeCheck(); Handles.CubeHandleCap(0, worldEndPt, zup, 0.1f * endSize, Event.current.type); newWorldPos = Handles.PositionHandle(worldEndPt, navLink.transform.rotation); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Move link point"); navLink.endPoint = mat.inverse.MultiplyPoint(newWorldPos); } } else { if (Handles.Button(worldEndPt, zup, 0.1f * endSize, 0.1f * endSize, Handles.CubeHandleCap)) { s_SelectedPoint = 1; s_SelectedID = navLink.GetInstanceID(); } } #endif EditorGUI.BeginChangeCheck(); newWorldPos = Handles.Slider(worldMidPt + right * navLink.width * 0.5f, right, midSize * 0.03f, Handles.DotHandleCap, 0); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Adjust link width"); navLink.width = Mathf.Max(0.0f, 2.0f * Vector3.Dot(right, (newWorldPos - worldMidPt))); } EditorGUI.BeginChangeCheck(); newWorldPos = Handles.Slider(worldMidPt - right * navLink.width * 0.5f, -right, midSize * 0.03f, Handles.DotHandleCap, 0); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(navLink, "Adjust link width"); navLink.width = Mathf.Max(0.0f, 2.0f * Vector3.Dot(-right, (newWorldPos - worldMidPt))); } Handles.color = oldColor; } [MenuItem("GameObject/AI/NavMesh Link", false, 2002)] public static void CreateNavMeshLink(MenuCommand menuCommand) { var parent = menuCommand.context as GameObject; GameObject go = NavMeshComponentsGUIUtility.CreateAndSelectGameObject("NavMesh Link", parent); go.AddComponent(); var view = SceneView.lastActiveSceneView; if (view != null) view.MoveToView(go.transform); } } }