using System;
using UnityEditor;
using UnityEditor.AI;
using UnityEngine;
using UnityEngine.AI;
namespace Unity.AI.Navigation.Editor
{
/// Class containing a set of utility functions meant for presenting information from the NavMeshComponents into the GUI.
public static class NavMeshComponentsGUIUtility
{
static readonly GUIContent s_TempContent = new();
static GUIContent TempContent(string text)
{
s_TempContent.image = default;
s_TempContent.text = text;
s_TempContent.tooltip = default;
return s_TempContent;
}
internal const string k_PackageEditorResourcesFolder = "Packages/com.unity.ai.navigation/EditorResources/";
static readonly string k_OpenAreaSettingsText = L10n.Tr("Open Area Settings...");
/// Displays a GUI element for selecting the area type used by a , , or .
/// The dropdown menu lists all of the area types defined in the Areas tab of the Navigation window.
/// The label for the field.
/// The serialized property that this GUI element displays and modifies. It represents a NavMesh area type and it needs to store values of type .
/// NavMeshSurface.defaultArea
/// NavMeshBuildSource.area
public static void AreaPopup(string labelName, SerializedProperty areaProperty) =>
AreaPopup(TempContent(labelName), areaProperty);
internal static void AreaPopup(GUIContent label, SerializedProperty areaProperty)
{
var areaIndex = -1;
var areaNames = GetNavMeshAreaNames();
for (var i = 0; i < areaNames.Length; i++)
{
var areaValue = GetNavMeshAreaFromName(areaNames[i]);
if (areaValue == areaProperty.intValue)
areaIndex = i;
}
ArrayUtility.Add(ref areaNames, "");
ArrayUtility.Add(ref areaNames, k_OpenAreaSettingsText);
var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(rect, GUIContent.none, areaProperty);
EditorGUI.BeginChangeCheck();
areaIndex = EditorGUI.Popup(rect, label, areaIndex, areaNames);
if (EditorGUI.EndChangeCheck())
{
if (areaIndex >= 0 && areaIndex < areaNames.Length - 2)
areaProperty.intValue = GetNavMeshAreaFromName(areaNames[areaIndex]);
else if (areaIndex == areaNames.Length - 1)
NavMeshEditorHelpers.OpenAreaSettings();
}
EditorGUI.EndProperty();
}
internal static readonly string k_OpenAgentSettingsText = L10n.Tr("Open Agent Settings...");
static readonly string k_AgentTypeInvalidText = L10n.Tr("Agent Type invalid.");
/// Displays a GUI element for selecting the agent type used by a or .
/// The dropdown menu lists all of the agent types defined in the Agents tab of the Navigation window.
/// The label for the field.
/// The serialized property that this GUI element displays and modifies. It stores an value that represents a NavMesh agent type ID.
/// The selected item is displayed as the name that corresponds to the stored ID.
/// NavMeshAgent.agentTypeID
/// NavMeshBuildSettings.agentTypeID
/// NavMesh.GetSettingsNameFromID
public static void AgentTypePopup(string labelName, SerializedProperty agentTypeID) =>
AgentTypePopup(TempContent(labelName), agentTypeID);
internal static void AgentTypePopup(GUIContent label, SerializedProperty agentTypeID)
{
var index = -1;
var count = NavMesh.GetSettingsCount();
var agentTypeNames = new string[count + 2];
for (var i = 0; i < count; i++)
{
var id = NavMesh.GetSettingsByIndex(i).agentTypeID;
var name = NavMesh.GetSettingsNameFromID(id);
agentTypeNames[i] = name;
if (id == agentTypeID.intValue)
index = i;
}
agentTypeNames[count] = "";
agentTypeNames[count + 1] = k_OpenAgentSettingsText;
bool validAgentType = index != -1;
if (!validAgentType)
{
EditorGUILayout.HelpBox(k_AgentTypeInvalidText, MessageType.Warning);
}
var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(rect, GUIContent.none, agentTypeID);
EditorGUI.BeginChangeCheck();
index = EditorGUI.Popup(rect, label, index, agentTypeNames);
if (EditorGUI.EndChangeCheck())
{
if (index >= 0 && index < count)
{
var id = NavMesh.GetSettingsByIndex(index).agentTypeID;
agentTypeID.intValue = id;
}
else if (index == count + 1)
{
NavMeshEditorHelpers.OpenAgentSettings(-1);
}
}
EditorGUI.EndProperty();
}
// Agent mask is a set (internally array/list) of agentTypeIDs.
// It is used to describe which agents modifiers apply to.
// There is a special case of "None" which is an empty array.
// There is a special case of "All" which is an array of length 1, and value of -1.
/// Displays a GUI element for selecting multiple agent types for which a or can influence the NavMesh.
/// The dropdown menu lists all of the agent types defined in the Agents tab of the Navigation window.
/// The label for the field.
/// The serialized property that holds the array of NavMesh agent type values that are selected from the items defined in the Agents tab of the Navigation window. The items are stored as ID values and are displayed as their corresponding names.
/// NavMesh.GetSettingsByIndex
/// NavMeshModifier.AffectsAgentType
/// NavMeshModifierVolume.AffectsAgentType
public static void AgentMaskPopup(string labelName, SerializedProperty agentMask) =>
AgentMaskPopup(TempContent(labelName), agentMask);
internal static void AgentMaskPopup(GUIContent label, SerializedProperty agentMask)
{
// Contents of the dropdown box.
string popupContent = "";
if (agentMask.hasMultipleDifferentValues)
popupContent = "\u2014";
else
popupContent = GetAgentMaskLabelName(agentMask);
var content = TempContent(popupContent);
var popupRect = GUILayoutUtility.GetRect(content, EditorStyles.popup);
EditorGUI.BeginProperty(popupRect, GUIContent.none, agentMask);
popupRect = EditorGUI.PrefixLabel(popupRect, 0, label);
bool pressed = GUI.Button(popupRect, content, EditorStyles.popup);
if (pressed)
{
var show = !agentMask.hasMultipleDifferentValues;
var showNone = show && agentMask.arraySize == 0;
var showAll = show && IsAll(agentMask);
var menu = new GenericMenu();
menu.AddItem(new GUIContent("None"), showNone, SetAgentMaskNone, agentMask);
menu.AddItem(new GUIContent("All"), showAll, SetAgentMaskAll, agentMask);
menu.AddSeparator("");
var count = NavMesh.GetSettingsCount();
for (var i = 0; i < count; i++)
{
var id = NavMesh.GetSettingsByIndex(i).agentTypeID;
var sname = NavMesh.GetSettingsNameFromID(id);
var showSelected = show && AgentMaskHasSelectedAgentTypeID(agentMask, id);
var userData = new object[] { agentMask, id, !showSelected };
menu.AddItem(new GUIContent(sname), showSelected, ToggleAgentMaskItem, userData);
}
menu.DropDown(popupRect);
}
EditorGUI.EndProperty();
}
/// Creates and selects a new GameObject as a child of another GameObject.
/// The name given to the created child GameObject. If necessary, this method modifies the name in order to distinguish it from the other children of the same parent object.
/// The GameObject to which the created GameObject is attached as a child object.
/// A new GameObject that is a child of the specified parent GameObject.
public static GameObject CreateAndSelectGameObject(string suggestedName, GameObject parent)
{
return CreateAndSelectGameObject(suggestedName, parent, Array.Empty());
}
internal static GameObject CreateAndSelectGameObject(string suggestedName, GameObject parent, params Type[] components)
{
var child = ObjectFactory.CreateGameObject(suggestedName, components);
GOCreationCommands.Place(child, parent);
return child;
}
/// Checks whether a serialized property has all the bits set when interpreted as a bitmask.
///
///
static bool IsAll(SerializedProperty agentMask)
{
return agentMask.arraySize == 1 && agentMask.GetArrayElementAtIndex(0).intValue == -1;
}
/// Marks one agent type as being selected or not.
///
static void ToggleAgentMaskItem(object userData)
{
var args = (object[])userData;
var agentMask = (SerializedProperty)args[0];
var agentTypeID = (int)args[1];
var value = (bool)args[2];
ToggleAgentMaskItem(agentMask, agentTypeID, value);
}
/// Marks one agent type as being selected or not.
///
///
///
static void ToggleAgentMaskItem(SerializedProperty agentMask, int agentTypeID, bool value)
{
if (agentMask.hasMultipleDifferentValues)
{
agentMask.ClearArray();
agentMask.serializedObject.ApplyModifiedProperties();
}
// Find which index this agent type is in the agentMask array.
int idx = -1;
for (var j = 0; j < agentMask.arraySize; j++)
{
var elem = agentMask.GetArrayElementAtIndex(j);
if (elem.intValue == agentTypeID)
idx = j;
}
// Handle "All" special case.
if (IsAll(agentMask))
{
agentMask.DeleteArrayElementAtIndex(0);
}
// Toggle value.
if (value)
{
if (idx == -1)
{
agentMask.InsertArrayElementAtIndex(agentMask.arraySize);
agentMask.GetArrayElementAtIndex(agentMask.arraySize - 1).intValue = agentTypeID;
}
}
else
{
if (idx != -1)
{
agentMask.DeleteArrayElementAtIndex(idx);
}
}
agentMask.serializedObject.ApplyModifiedProperties();
}
/// Marks all agent types as not being selected.
///
static void SetAgentMaskNone(object data)
{
var agentMask = (SerializedProperty)data;
agentMask.ClearArray();
agentMask.serializedObject.ApplyModifiedProperties();
}
/// Marks all agent types as being selected.
///
static void SetAgentMaskAll(object data)
{
var agentMask = (SerializedProperty)data;
agentMask.ClearArray();
agentMask.InsertArrayElementAtIndex(0);
agentMask.GetArrayElementAtIndex(0).intValue = -1;
agentMask.serializedObject.ApplyModifiedProperties();
}
static readonly string k_AgentMaskNoneText = L10n.Tr("None");
static readonly string k_AgentTypeAllText = L10n.Tr("All");
static readonly string k_AgentTypeMixedText = L10n.Tr("Mixed...");
/// Obtains one string that represents the current selection of agent types.
///
/// One string that represents the current selection of agent types.
static string GetAgentMaskLabelName(SerializedProperty agentMask)
{
if (agentMask.arraySize == 0)
return k_AgentMaskNoneText;
if (IsAll(agentMask))
return k_AgentTypeAllText;
if (agentMask.arraySize <= 3)
{
var labelName = "";
for (var j = 0; j < agentMask.arraySize; j++)
{
var elem = agentMask.GetArrayElementAtIndex(j);
var settingsName = NavMesh.GetSettingsNameFromID(elem.intValue);
if (string.IsNullOrEmpty(settingsName))
continue;
if (labelName.Length > 0)
labelName += ", ";
labelName += settingsName;
}
return labelName;
}
return k_AgentTypeMixedText;
}
/// Checks whether a certain agent type is selected.
///
///
///
static bool AgentMaskHasSelectedAgentTypeID(SerializedProperty agentMask, int agentTypeID)
{
for (var j = 0; j < agentMask.arraySize; j++)
{
var elem = agentMask.GetArrayElementAtIndex(j);
if (elem.intValue == agentTypeID)
return true;
}
return false;
}
static string[] GetNavMeshAreaNames()
{
#if EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED
return NavMesh.GetAreaNames();
#else
return GameObjectUtility.GetNavMeshAreaNames();
#endif
}
static int GetNavMeshAreaFromName(string name)
{
#if EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED
return NavMesh.GetAreaFromName(name);
#else
return GameObjectUtility.GetNavMeshAreaFromName(name);
#endif
}
}
}