first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.AI.Navigation.Editor.Tests")]
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b436bc9c47bb45bd98478a478a2608f8
|
||||
timeCreated: 1707900617
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d25d346918c1247368b4cb66a787b59e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.AI.Navigation.Updater")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a86108f91b07e4fe3b5cd8fedc9f1797
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure holding the information for each Item that needs to be Converted.
|
||||
/// Name = The Name of the asset that is being converted.
|
||||
/// Info = Information that can be used to store some data. This will also be shown in the UI.
|
||||
/// WarningMessage = If there are some issues with the converter that we already know about.
|
||||
/// Example: If we know it is a custom shader, we can not convert it so we add the information here.
|
||||
/// AdditionalData = Additional data that can be used to store some data. This will not be shown in the UI.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal struct ConverterItemDescriptor
|
||||
{
|
||||
/// <summary> Name of the asset being converted. This will be shown in the UI. </summary>
|
||||
public string name;
|
||||
/// <summary> Information that can be used to store some data. This will also be shown in the UI. </summary>
|
||||
public string info;
|
||||
/// <summary> If there are some issues with the converter that we already know about during init phase. This will be added as a tooltip on the warning icon. </summary>
|
||||
public string warningMessage;
|
||||
/// <summary> Additional data that can be used to store data related to the item to be converted.</summary>
|
||||
public string additionalData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f72db493d3cd4e03ae748a6cd15534d
|
||||
timeCreated: 1618229698
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure holding the information for each Item that needs to be Converted.
|
||||
/// Descriptor = The ConverterItemDescriptor this item contain.
|
||||
/// Index = The index for this item in the list of converter items.
|
||||
/// </summary>
|
||||
internal struct ConverterItemInfo
|
||||
{
|
||||
/// <summary> The ConverterItemDescriptor this item contain. </summary>
|
||||
public ConverterItemDescriptor descriptor { get; internal set; }
|
||||
|
||||
/// <summary> The index for this item in the list of converter items. </summary>
|
||||
public int index { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fac6f414adf2e4556865a180b1529cbb
|
||||
timeCreated: 1618230361
|
||||
@@ -0,0 +1,23 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
internal static class EditorStyles
|
||||
{
|
||||
public static Texture iconHelp;
|
||||
public static Texture2D iconPending;
|
||||
public static Texture2D iconWarn;
|
||||
public static Texture2D iconFail;
|
||||
public static Texture2D iconSuccess;
|
||||
|
||||
static EditorStyles()
|
||||
{
|
||||
iconFail = EditorGUIUtility.Load("icons/console.erroricon.png") as Texture2D;
|
||||
iconWarn = EditorGUIUtility.Load("icons/console.warnicon.png") as Texture2D;
|
||||
iconHelp = EditorGUIUtility.Load("icons/console.infoicon.png") as Texture2D;
|
||||
iconSuccess = EditorGUIUtility.FindTexture("TestPassed");
|
||||
iconPending = EditorGUIUtility.FindTexture("Toolbar Minus");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c0c4a4a1781441ff8c5f9585d5a3462
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure needed for the initialization step of the converter.
|
||||
/// Stores data to be visible in the UI.
|
||||
/// </summary>
|
||||
internal struct InitializeConverterContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the list of ConverterItemDescriptor that will be filled in during the initialization step.
|
||||
/// </summary>
|
||||
internal List<ConverterItemDescriptor> items;
|
||||
|
||||
/// <summary>
|
||||
/// Add to the list of assets to be converted.
|
||||
/// This will be used to display information to the user in the UI.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add to the list items to convert</param>
|
||||
internal void AddAssetToConvert(ConverterItemDescriptor item)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5cf3eb6bee954ca5953487784e68715
|
||||
timeCreated: 1618230672
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure needed for the conversion part of the converter. <br />
|
||||
/// This holds the items that are being converted. <br />
|
||||
/// All arrays are the same length and the index of each array corresponds to the same item.
|
||||
/// </summary>
|
||||
internal struct RunItemContext
|
||||
{
|
||||
/// <summary> The items that will go through the conversion code. </summary>
|
||||
public ConverterItemInfo[] items { get; }
|
||||
|
||||
/// <summary> A bool to set if these items failed to convert. </summary>
|
||||
public bool[] didFail { get; }
|
||||
|
||||
/// <summary> Info to store data to be shown in the UI. </summary>
|
||||
public string[] info { get; }
|
||||
|
||||
/// <summary> Constructor for the RunItemContext. </summary>
|
||||
public RunItemContext(ConverterItemInfo[] items)
|
||||
{
|
||||
this.items = items;
|
||||
didFail = new bool[this.items.Length];
|
||||
info = new string[this.items.Length];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a841ce8d078944bb8dc02dc43952c91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
internal abstract class SystemConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the converter.
|
||||
/// </summary>
|
||||
public abstract string name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The information when hovering over the converter.
|
||||
/// </summary>
|
||||
public abstract string info { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A check if the converter is enabled or not. Can be used to do a check if prerequisites are met to have it enabled or disabled.
|
||||
/// </summary>
|
||||
public virtual bool isEnabled => true;
|
||||
|
||||
/// <summary>
|
||||
/// A priority of the converter. The lower the number (can be negative), the earlier it will be executed. Can be used to make sure that a converter runs before another converter.
|
||||
/// </summary>
|
||||
public virtual int priority => 0;
|
||||
|
||||
/// <summary>
|
||||
/// A check to see if the converter needs to create the index.
|
||||
/// This will only need to be set to true if the converter is using search api, and search queries.
|
||||
/// If set to true the converter framework will create the indexer and remove it after all search queries are done.
|
||||
/// </summary>
|
||||
public virtual bool needsIndexing => false;
|
||||
|
||||
/// <summary>
|
||||
/// This method getting triggered when clicking the listview item in the UI.
|
||||
/// </summary>
|
||||
public virtual void OnClicked(int index)
|
||||
{
|
||||
}
|
||||
|
||||
// This is so that we can have different segment in our UI, example Unity converters, your custom converters etc..
|
||||
// This is not implemented yet
|
||||
public virtual string category { get; }
|
||||
|
||||
// This is in which drop down item the converter belongs to.
|
||||
// Not properly implemented yet
|
||||
public abstract Type container { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This runs when initializing the converter. To gather data for the UI and also for the converter if needed.
|
||||
/// </summary>
|
||||
/// <param name="context">The context that will be used to initialize data for the converter.</param>
|
||||
public abstract void OnInitialize(InitializeConverterContext context, Action callback);
|
||||
|
||||
/// <summary>
|
||||
/// The method that will be run before Run method if needed.
|
||||
/// </summary>
|
||||
public virtual void OnPreRun()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method that will be run when converting the assets.
|
||||
/// </summary>
|
||||
/// <param name="context">The context that will be used when executing converter.</param>
|
||||
public abstract void OnRun(ref RunItemContext context);
|
||||
|
||||
/// <summary>
|
||||
/// The method that will be run after the converters are done if needed.
|
||||
/// </summary>
|
||||
public virtual void OnPostRun()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c9fba05c2cea421a8771ca17afce238
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to contain converters. This is for a common set of converters.
|
||||
/// For example: Converters that is for Built-in to URP would have it's own container.
|
||||
/// </summary>
|
||||
public abstract class SystemConverterContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the Container. This will show up int the UI.
|
||||
/// </summary>
|
||||
public abstract string name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The information for this container.
|
||||
/// This will be shown in the UI to tell the user some information about the converters that this container are targeting.
|
||||
/// </summary>
|
||||
public abstract string info { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c78b1d765d234b9c9e3a1d2b0667c47
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,685 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Search;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor.Converter
|
||||
{
|
||||
// Status for each row item to say in which state they are in.
|
||||
// This will make sure they are showing the correct icon
|
||||
[Serializable]
|
||||
enum Status
|
||||
{
|
||||
Pending,
|
||||
Warning,
|
||||
Error,
|
||||
Success
|
||||
}
|
||||
|
||||
// This is the serialized class that stores the state of each item in the list of items to convert
|
||||
[Serializable]
|
||||
class ConverterItemState
|
||||
{
|
||||
public bool isActive;
|
||||
|
||||
// Message that will be displayed on the icon if warning or failed.
|
||||
public string message;
|
||||
|
||||
// Status of the converted item, Pending, Warning, Error or Success
|
||||
public Status status;
|
||||
|
||||
internal bool hasConverted = false;
|
||||
}
|
||||
|
||||
// Each converter uses the active bool
|
||||
// Each converter has a list of active items/assets
|
||||
// We do this so that we can use the binding system of the UI Elements
|
||||
[Serializable]
|
||||
class ConverterState
|
||||
{
|
||||
// This is the enabled state of the whole converter
|
||||
public bool isEnabled;
|
||||
public bool isActive;
|
||||
public bool isLoading; // to name
|
||||
public bool isInitialized;
|
||||
public List<ConverterItemState> items = new List<ConverterItemState>();
|
||||
|
||||
public int pending;
|
||||
public int warnings;
|
||||
public int errors;
|
||||
public int success;
|
||||
internal int index;
|
||||
|
||||
public bool isActiveAndEnabled => isEnabled && isActive;
|
||||
public bool requiresInitialization => !isInitialized && isActiveAndEnabled;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct ConverterItems
|
||||
{
|
||||
public List<ConverterItemDescriptor> itemDescriptors;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SystemConvertersEditor : EditorWindow
|
||||
{
|
||||
public VisualTreeAsset converterEditorAsset;
|
||||
public VisualTreeAsset converterListAsset;
|
||||
public VisualTreeAsset converterItem;
|
||||
|
||||
ScrollView m_ScrollView;
|
||||
|
||||
List<SystemConverter> m_CoreConvertersList = new List<SystemConverter>();
|
||||
|
||||
private bool convertButtonActive = false;
|
||||
|
||||
// This list needs to be as long as the amount of converters
|
||||
List<ConverterItems> m_ItemsToConvert = new List<ConverterItems>();
|
||||
SerializedObject m_SerializedObject;
|
||||
|
||||
List<string> m_ContainerChoices = new List<string>();
|
||||
List<SystemConverterContainer> m_Containers = new List<SystemConverterContainer>();
|
||||
int m_ContainerChoiceIndex = 0;
|
||||
|
||||
// This is a list of Converter States which holds a list of which converter items/assets are active
|
||||
// There is one for each Converter.
|
||||
[SerializeField] List<ConverterState> m_ConverterStates = new List<ConverterState>();
|
||||
|
||||
TypeCache.TypeCollection m_ConverterContainers;
|
||||
|
||||
// Name of the index file
|
||||
string m_ConverterIndex = "SystemConverterIndex";
|
||||
|
||||
public void DontSaveToLayout(EditorWindow wnd)
|
||||
{
|
||||
// Making sure that the window is not saved in layouts.
|
||||
Assembly assembly = typeof(EditorWindow).Assembly;
|
||||
var editorWindowType = typeof(EditorWindow);
|
||||
var hostViewType = assembly.GetType("UnityEditor.HostView");
|
||||
var containerWindowType = assembly.GetType("UnityEditor.ContainerWindow");
|
||||
var parentViewField = editorWindowType.GetField("m_Parent", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var parentViewValue = parentViewField.GetValue(wnd);
|
||||
// window should not be saved to layout
|
||||
var containerWindowProperty =
|
||||
hostViewType.GetProperty("window", BindingFlags.Instance | BindingFlags.Public);
|
||||
var parentContainerWindowValue = containerWindowProperty.GetValue(parentViewValue);
|
||||
var dontSaveToLayoutField =
|
||||
containerWindowType.GetField("m_DontSaveToLayout", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
dontSaveToLayoutField.SetValue(parentContainerWindowValue, true);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
InitIfNeeded();
|
||||
}
|
||||
|
||||
void InitIfNeeded()
|
||||
{
|
||||
if (m_CoreConvertersList.Any())
|
||||
return;
|
||||
m_CoreConvertersList = new List<SystemConverter>();
|
||||
|
||||
// This is the drop down choices.
|
||||
m_ConverterContainers = TypeCache.GetTypesDerivedFrom<SystemConverterContainer>();
|
||||
foreach (var containerType in m_ConverterContainers)
|
||||
{
|
||||
var container = (SystemConverterContainer)Activator.CreateInstance(containerType);
|
||||
m_Containers.Add(container);
|
||||
m_ContainerChoices.Add(container.name);
|
||||
}
|
||||
|
||||
if (m_ConverterContainers.Any())
|
||||
{
|
||||
GetConverters();
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearConverterStates();
|
||||
}
|
||||
}
|
||||
|
||||
void ClearConverterStates()
|
||||
{
|
||||
m_CoreConvertersList.Clear();
|
||||
m_ConverterStates.Clear();
|
||||
m_ItemsToConvert.Clear();
|
||||
}
|
||||
|
||||
void GetConverters()
|
||||
{
|
||||
ClearConverterStates();
|
||||
var converterList = TypeCache.GetTypesDerivedFrom<SystemConverter>();
|
||||
|
||||
for (var i = 0; i < converterList.Count; ++i)
|
||||
{
|
||||
// Iterate over the converters that are used by the current container
|
||||
var conv = (SystemConverter)Activator.CreateInstance(converterList[i]);
|
||||
if (conv.container == m_ConverterContainers[m_ContainerChoiceIndex])
|
||||
{
|
||||
m_CoreConvertersList.Add(conv);
|
||||
}
|
||||
}
|
||||
|
||||
// this need to be sorted by Priority property
|
||||
m_CoreConvertersList = m_CoreConvertersList
|
||||
.OrderBy(o => o.priority).ToList();
|
||||
|
||||
for (var i = 0; i < m_CoreConvertersList.Count; i++)
|
||||
{
|
||||
// Create a new ConvertState which holds the active state of the converter
|
||||
var converterState = new ConverterState
|
||||
{
|
||||
isEnabled = m_CoreConvertersList[i].isEnabled,
|
||||
isActive = true,
|
||||
isInitialized = false,
|
||||
items = new List<ConverterItemState>(),
|
||||
index = i,
|
||||
};
|
||||
m_ConverterStates.Add(converterState);
|
||||
|
||||
// This just creates empty entries in the m_ItemsToConvert.
|
||||
// This list need to have the same amount of entries as the converters
|
||||
var converterItemInfos = new List<ConverterItemDescriptor>();
|
||||
//m_ItemsToConvert.Add(converterItemInfos);
|
||||
m_ItemsToConvert.Add(new ConverterItems { itemDescriptors = converterItemInfos });
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
InitIfNeeded();
|
||||
if (m_ConverterContainers.Any())
|
||||
{
|
||||
m_SerializedObject = new SerializedObject(this);
|
||||
converterEditorAsset.CloneTree(rootVisualElement);
|
||||
|
||||
RecreateUI();
|
||||
|
||||
var button = rootVisualElement.Q<Button>("convertButton");
|
||||
button.RegisterCallback<ClickEvent>(Convert);
|
||||
button.SetEnabled(false);
|
||||
|
||||
var initButton = rootVisualElement.Q<Button>("initializeButton");
|
||||
initButton.RegisterCallback<ClickEvent>(InitializeAllActiveConverters);
|
||||
}
|
||||
}
|
||||
|
||||
void RecreateUI()
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
|
||||
var currentContainer = m_Containers[m_ContainerChoiceIndex];
|
||||
rootVisualElement.Q<Label>("conversionName").text = currentContainer.name;
|
||||
rootVisualElement.Q<TextElement>("conversionInfo").text = currentContainer.info;
|
||||
|
||||
rootVisualElement.Q<Image>("converterContainerHelpIcon").image = EditorStyles.iconHelp;
|
||||
|
||||
// Getting the scrollview where the converters should be added
|
||||
m_ScrollView = rootVisualElement.Q<ScrollView>("convertersScrollView");
|
||||
m_ScrollView.Clear();
|
||||
for (int i = 0; i < m_CoreConvertersList.Count; ++i)
|
||||
{
|
||||
// Making an item using the converterListAsset as a template.
|
||||
// Then adding the information needed for each converter
|
||||
VisualElement item = new VisualElement();
|
||||
converterListAsset.CloneTree(item);
|
||||
var conv = m_CoreConvertersList[i];
|
||||
item.SetEnabled(conv.isEnabled);
|
||||
item.Q<Label>("converterName").text = conv.name;
|
||||
item.Q<Label>("converterInfo").text = conv.info;
|
||||
|
||||
// setup the images
|
||||
item.Q<Image>("pendingImage").image = EditorStyles.iconPending;
|
||||
item.Q<Image>("pendingImage").tooltip = "Pending";
|
||||
var pendingLabel = item.Q<Label>("pendingLabel");
|
||||
item.Q<Image>("warningImage").image = EditorStyles.iconWarn;
|
||||
item.Q<Image>("warningImage").tooltip = "Warnings";
|
||||
var warningLabel = item.Q<Label>("warningLabel");
|
||||
item.Q<Image>("errorImage").image = EditorStyles.iconFail;
|
||||
item.Q<Image>("errorImage").tooltip = "Failed";
|
||||
var errorLabel = item.Q<Label>("errorLabel");
|
||||
item.Q<Image>("successImage").image = EditorStyles.iconSuccess;
|
||||
item.Q<Image>("successImage").tooltip = "Success";
|
||||
var successLabel = item.Q<Label>("successLabel");
|
||||
|
||||
var converterEnabledToggle = item.Q<Toggle>("converterEnabled");
|
||||
converterEnabledToggle.bindingPath =
|
||||
$"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.isActive)}";
|
||||
pendingLabel.bindingPath =
|
||||
$"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.pending)}";
|
||||
warningLabel.bindingPath =
|
||||
$"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.warnings)}";
|
||||
errorLabel.bindingPath =
|
||||
$"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.errors)}";
|
||||
successLabel.bindingPath =
|
||||
$"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.success)}";
|
||||
|
||||
VisualElement child = item;
|
||||
ListView listView = child.Q<ListView>("converterItems");
|
||||
|
||||
listView.showBoundCollectionSize = false;
|
||||
listView.bindingPath = $"{nameof(m_ConverterStates)}.Array.data[{i}].{nameof(ConverterState.items)}";
|
||||
|
||||
int id = i;
|
||||
listView.makeItem = () =>
|
||||
{
|
||||
var convertItem = converterItem.CloneTree();
|
||||
// Adding the contextual menu for each item
|
||||
convertItem.AddManipulator(new ContextualMenuManipulator(evt => AddToContextMenu(evt, id)));
|
||||
return convertItem;
|
||||
};
|
||||
|
||||
listView.bindItem = (element, index) =>
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
var property = m_SerializedObject.FindProperty($"{listView.bindingPath}.Array.data[{index}]");
|
||||
|
||||
// ListView doesn't bind the child elements for us properly, so we do that for it
|
||||
// In the UXML our root is a BindableElement, as we can't bind otherwise.
|
||||
var bindable = (BindableElement)element;
|
||||
bindable.BindProperty(property);
|
||||
|
||||
// Adding index here to userData so it can be retrieved later
|
||||
element.userData = index;
|
||||
|
||||
var status = (Status)property.FindPropertyRelative("status").enumValueIndex;
|
||||
var info = property.FindPropertyRelative("message").stringValue;
|
||||
|
||||
// Update the amount of things to convert
|
||||
child.Q<Label>("converterStats").text = $"{m_ItemsToConvert[id].itemDescriptors.Count} items";
|
||||
|
||||
var convItemDesc = m_ItemsToConvert[id].itemDescriptors[index];
|
||||
|
||||
element.Q<Label>("converterItemName").text = convItemDesc.name;
|
||||
element.Q<Label>("converterItemPath").text = convItemDesc.info;
|
||||
|
||||
// Changing the icon here depending on the status.
|
||||
Texture2D icon = null;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case Status.Pending:
|
||||
icon = EditorStyles.iconPending;
|
||||
break;
|
||||
case Status.Error:
|
||||
icon = EditorStyles.iconFail;
|
||||
break;
|
||||
case Status.Warning:
|
||||
icon = EditorStyles.iconWarn;
|
||||
break;
|
||||
case Status.Success:
|
||||
icon = EditorStyles.iconSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
element.Q<Image>("converterItemStatusIcon").image = icon;
|
||||
element.Q<Image>("converterItemStatusIcon").tooltip = info;
|
||||
};
|
||||
listView.selectionChanged += obj => { m_CoreConvertersList[id].OnClicked(listView.selectedIndex); };
|
||||
listView.unbindItem = (element, index) =>
|
||||
{
|
||||
var bindable = (BindableElement)element;
|
||||
bindable.Unbind();
|
||||
};
|
||||
|
||||
m_ScrollView.Add(item);
|
||||
}
|
||||
rootVisualElement.Bind(m_SerializedObject);
|
||||
var button = rootVisualElement.Q<Button>("convertButton");
|
||||
button.RegisterCallback<ClickEvent>(Convert);
|
||||
button.SetEnabled(convertButtonActive);
|
||||
|
||||
var initButton = rootVisualElement.Q<Button>("initializeButton");
|
||||
initButton.RegisterCallback<ClickEvent>(InitializeAllActiveConverters);
|
||||
}
|
||||
|
||||
void GetAndSetData(int i, Action onAllConvertersCompleted = null)
|
||||
{
|
||||
// This need to be in Init method
|
||||
// Need to get the assets that this converter is converting.
|
||||
// Need to return Name, Path, Initial info, Help link.
|
||||
// New empty list of ConverterItemInfos
|
||||
List<ConverterItemDescriptor> converterItemInfos = new List<ConverterItemDescriptor>();
|
||||
var initCtx = new InitializeConverterContext { items = converterItemInfos };
|
||||
|
||||
var conv = m_CoreConvertersList[i];
|
||||
|
||||
m_ConverterStates[i].isLoading = true;
|
||||
|
||||
// This should also go to the init method
|
||||
// This will fill out the converter item infos list
|
||||
int id = i;
|
||||
conv.OnInitialize(initCtx, OnConverterCompleteDataCollection);
|
||||
|
||||
void OnConverterCompleteDataCollection()
|
||||
{
|
||||
// Set the item infos list to to the right index
|
||||
m_ItemsToConvert[id] = new ConverterItems { itemDescriptors = converterItemInfos };
|
||||
m_ConverterStates[id].items = new List<ConverterItemState>(converterItemInfos.Count);
|
||||
|
||||
// Default all the entries to true
|
||||
for (var j = 0; j < converterItemInfos.Count; j++)
|
||||
{
|
||||
string message = string.Empty;
|
||||
Status status;
|
||||
bool active = true;
|
||||
// If this data hasn't been filled in from the init phase then we can assume that there are no issues / warnings
|
||||
if (string.IsNullOrEmpty(converterItemInfos[j].warningMessage))
|
||||
{
|
||||
status = Status.Pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = Status.Warning;
|
||||
message = converterItemInfos[j].warningMessage;
|
||||
active = false;
|
||||
m_ConverterStates[id].warnings++;
|
||||
}
|
||||
|
||||
m_ConverterStates[id].items.Add(new ConverterItemState
|
||||
{
|
||||
isActive = active,
|
||||
message = message,
|
||||
status = status,
|
||||
hasConverted = false,
|
||||
});
|
||||
}
|
||||
|
||||
m_ConverterStates[id].isLoading = false;
|
||||
m_ConverterStates[id].isInitialized = true;
|
||||
|
||||
// Making sure that the pending amount is set to the amount of items needs converting
|
||||
m_ConverterStates[id].pending = m_ConverterStates[id].items.Count;
|
||||
|
||||
EditorUtility.SetDirty(this);
|
||||
m_SerializedObject.ApplyModifiedProperties();
|
||||
|
||||
CheckAllConvertersCompleted();
|
||||
convertButtonActive = true;
|
||||
// Make sure that the Convert Button is turned back on
|
||||
var button = rootVisualElement.Q<Button>("convertButton");
|
||||
button.SetEnabled(convertButtonActive);
|
||||
}
|
||||
|
||||
void CheckAllConvertersCompleted()
|
||||
{
|
||||
int convertersToInitialize = 0;
|
||||
int convertersInitialized = 0;
|
||||
|
||||
for (var j = 0; j < m_ConverterStates.Count; j++)
|
||||
{
|
||||
var converter = m_ConverterStates[j];
|
||||
|
||||
// Skip inactive converters
|
||||
if (!converter.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
if (converter.isInitialized)
|
||||
convertersInitialized++;
|
||||
else
|
||||
convertersToInitialize++;
|
||||
}
|
||||
|
||||
var sum = convertersToInitialize + convertersInitialized;
|
||||
|
||||
Assert.IsFalse(sum == 0);
|
||||
|
||||
// Show our progress so far
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters ({convertersInitialized}/{sum})...", (float)convertersInitialized / sum);
|
||||
|
||||
// If all converters are initialized call the complete callback
|
||||
if (convertersToInitialize == 0)
|
||||
{
|
||||
onAllConvertersCompleted?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAllActiveConverters(ClickEvent evt)
|
||||
{
|
||||
// If we use search index, go async
|
||||
if (ShouldCreateSearchIndex())
|
||||
{
|
||||
CreateSearchIndex(m_ConverterIndex);
|
||||
}
|
||||
// Otherwise do everything directly
|
||||
else
|
||||
{
|
||||
ConverterCollectData(() => { EditorUtility.ClearProgressBar(); });
|
||||
}
|
||||
|
||||
void CreateSearchIndex(string name)
|
||||
{
|
||||
// Create <guid>.index in the project
|
||||
var title = $"Building {name} search index";
|
||||
EditorUtility.DisplayProgressBar(title, "Creating search index...", -1f);
|
||||
|
||||
// Private implementation of a file naming function which puts the file at the selected path.
|
||||
Type assetdatabase = typeof(AssetDatabase);
|
||||
var indexPath = (string)assetdatabase.GetMethod("GetUniquePathNameAtSelectedPath", BindingFlags.NonPublic | BindingFlags.Static).Invoke(assetdatabase, new object[] { $"Assets/{name}.index" });
|
||||
|
||||
// Write search index manifest
|
||||
System.IO.File.WriteAllText(indexPath,
|
||||
@"{
|
||||
""roots"": [""Assets""],
|
||||
""includes"": [],
|
||||
""excludes"": [],
|
||||
""options"": {
|
||||
""types"": true,
|
||||
""properties"": true,
|
||||
""extended"": true,
|
||||
""dependencies"": true
|
||||
},
|
||||
""baseScore"": 9999
|
||||
}");
|
||||
|
||||
|
||||
|
||||
// Import the search index
|
||||
AssetDatabase.ImportAsset(indexPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.DontDownloadFromCacheServer);
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
// Create dummy request to ensure indexing has finished
|
||||
var context = SearchService.CreateContext("asset", $"p: a=\"{name}\"");
|
||||
SearchService.Request(context, (_, items) =>
|
||||
{
|
||||
OnSearchIndexCreated(name, indexPath, () =>
|
||||
{
|
||||
DeleteSearchIndex(context, indexPath);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void OnSearchIndexCreated(string name, string path, Action onComplete)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
ConverterCollectData(onComplete);
|
||||
}
|
||||
|
||||
void ConverterCollectData(Action onConverterDataCollectionComplete)
|
||||
{
|
||||
EditorUtility.DisplayProgressBar($"Initializing converters", $"Initializing converters...", -1f);
|
||||
|
||||
var convertersToInitialize = 0;
|
||||
for (var i = 0; i < m_ConverterStates.Count; ++i)
|
||||
{
|
||||
if (m_ConverterStates[i].isEnabled)
|
||||
{
|
||||
GetAndSetData(i, onConverterDataCollectionComplete);
|
||||
convertersToInitialize++;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have any converters to initialize, call the complete callback directly.
|
||||
if (convertersToInitialize == 0)
|
||||
onConverterDataCollectionComplete?.Invoke();
|
||||
}
|
||||
|
||||
void DeleteSearchIndex(SearchContext context, string indexPath)
|
||||
{
|
||||
context?.Dispose();
|
||||
// Client code has finished with the created index. We can delete it.
|
||||
AssetDatabase.DeleteAsset(indexPath);
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldCreateSearchIndex()
|
||||
{
|
||||
for (int i = 0; i < m_ConverterStates.Count; ++i)
|
||||
{
|
||||
if (m_ConverterStates[i].requiresInitialization)
|
||||
{
|
||||
var converter = m_CoreConvertersList[i];
|
||||
if (converter.needsIndexing)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddToContextMenu(ContextualMenuPopulateEvent evt, int coreConverterIndex)
|
||||
{
|
||||
var ve = (VisualElement)evt.target;
|
||||
// Checking if this context menu should be enabled or not
|
||||
var isActive = m_ConverterStates[coreConverterIndex].items[(int)ve.userData].isActive &&
|
||||
!m_ConverterStates[coreConverterIndex].items[(int)ve.userData].hasConverted;
|
||||
|
||||
evt.menu.AppendAction("Run converter for this asset",
|
||||
e => { ConvertIndex(coreConverterIndex, (int)ve.userData); },
|
||||
isActive ? DropdownMenuAction.AlwaysEnabled : DropdownMenuAction.AlwaysDisabled);
|
||||
}
|
||||
|
||||
void Convert(ClickEvent evt)
|
||||
{
|
||||
var activeConverterStates = new List<ConverterState>();
|
||||
// Get the names of the converters
|
||||
// Get the amount of them
|
||||
// Make the string "name x/y"
|
||||
|
||||
// Getting all the active converters to use in the cancelable progressbar
|
||||
foreach (var state in m_ConverterStates)
|
||||
{
|
||||
if (state.isActive && state.isInitialized)
|
||||
activeConverterStates.Add(state);
|
||||
}
|
||||
|
||||
var converterCount = 0;
|
||||
var activeConvertersCount = activeConverterStates.Count;
|
||||
foreach (var activeConverterState in activeConverterStates)
|
||||
{
|
||||
if (activeConverterState.items.Count == 0)
|
||||
continue;
|
||||
var hasItemsToConvert = false;
|
||||
foreach (var item in activeConverterState.items)
|
||||
{
|
||||
if (item.isActive && !item.hasConverted)
|
||||
{
|
||||
hasItemsToConvert = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasItemsToConvert)
|
||||
continue;
|
||||
|
||||
var converterIndex = activeConverterState.index;
|
||||
m_CoreConvertersList[converterIndex].OnPreRun();
|
||||
|
||||
var converterName = m_CoreConvertersList[converterIndex].name;
|
||||
var progressTitle = $"{converterName} Converter : {converterCount++}/{activeConvertersCount}";
|
||||
BatchConvert(activeConverterState, progressTitle);
|
||||
|
||||
m_CoreConvertersList[converterIndex].OnPostRun();
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
void BatchConvert(ConverterState converterState, string progressBarTile)
|
||||
{
|
||||
var converterIndex = converterState.index;
|
||||
var itemsFound = converterState.items;
|
||||
var itemsToConvert = new List<ConverterItemInfo>(itemsFound.Count);
|
||||
for (var i = 0; i < itemsFound.Count; ++i)
|
||||
{
|
||||
if (itemsFound[i].isActive && !itemsFound[i].hasConverted)
|
||||
itemsToConvert.Add(new ConverterItemInfo()
|
||||
{
|
||||
index = i,
|
||||
descriptor = m_ItemsToConvert[converterIndex].itemDescriptors[i]
|
||||
});
|
||||
}
|
||||
|
||||
// Since this is a batched process, we don't have progress to show, so we stick it to 50%.
|
||||
EditorUtility.DisplayProgressBar(progressBarTile, $"Processing {itemsToConvert.Count} items.", 0.5f);
|
||||
|
||||
var ctx = new RunItemContext(itemsToConvert.ToArray());
|
||||
m_CoreConvertersList[converterIndex].OnRun(ref ctx);
|
||||
UpdateInfo(converterIndex, ctx);
|
||||
}
|
||||
|
||||
void ConvertIndex(int converterIndex, int index)
|
||||
{
|
||||
if (!m_ConverterStates[converterIndex].items[index].hasConverted)
|
||||
{
|
||||
m_ConverterStates[converterIndex].items[index].hasConverted = true;
|
||||
var item = new ConverterItemInfo()
|
||||
{
|
||||
index = index,
|
||||
descriptor = m_ItemsToConvert[converterIndex].itemDescriptors[index],
|
||||
};
|
||||
var ctx = new RunItemContext(new[] { item });
|
||||
m_CoreConvertersList[converterIndex].OnRun(ref ctx);
|
||||
UpdateInfo(converterIndex, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInfo(int converterIndex, RunItemContext ctx)
|
||||
{
|
||||
// Reset converter stats, so that they don't contain old data from previous runs.
|
||||
m_ConverterStates[converterIndex].warnings = 0;
|
||||
m_ConverterStates[converterIndex].errors = 0;
|
||||
m_ConverterStates[converterIndex].success = 0;
|
||||
|
||||
var items = ctx.items;
|
||||
for (var i = 0; i < items.Length; ++i)
|
||||
{
|
||||
var itemIndex = items[i].index;
|
||||
if (ctx.didFail[i])
|
||||
{
|
||||
m_ConverterStates[converterIndex].items[itemIndex].message = ctx.info[i];
|
||||
m_ConverterStates[converterIndex].items[itemIndex].status = Status.Error;
|
||||
m_ConverterStates[converterIndex].errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ConverterStates[converterIndex].items[itemIndex].status = Status.Success;
|
||||
m_ConverterStates[converterIndex].success++;
|
||||
m_ConverterStates[converterIndex].items[itemIndex].hasConverted = true;
|
||||
}
|
||||
|
||||
// If the item was converted, we deselect it in the conversion list.
|
||||
m_ConverterStates[converterIndex].items[itemIndex].isActive = false;
|
||||
}
|
||||
|
||||
if (m_ConverterStates[converterIndex].pending > 0)
|
||||
m_ConverterStates[converterIndex].pending--;
|
||||
|
||||
var child = m_ScrollView[converterIndex];
|
||||
child.Q<ListView>("converterItems").Rebuild();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 030df50ef7a3d40deaa58109d30100c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_ViewDataDictionary: {instanceID: 0}
|
||||
- converterEditorAsset: {fileID: 9197481963319205126, guid: cd1eb3c3c695c494d855ea678fe7395b,
|
||||
type: 3}
|
||||
- converterListAsset: {fileID: 9197481963319205126, guid: aaa3e510761864dac9b71f85526490d6,
|
||||
type: 3}
|
||||
- converterItem: {fileID: 9197481963319205126, guid: d6de7697d63d64fabbfb31425d93541e,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Unity.AI.Navigation.Editor.ConversionSystem",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c57630adab7a340ec94f10e4fcac12f1
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
.convertersListView {
|
||||
--unity-item-height: 16;
|
||||
height: 120px;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb99c80b411349188e1e5167887ac6c
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,19 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:VisualElement style="max-width: 620px; min-width: 620px; flex-grow: 1; padding-left: 19px; padding-right: 1px; padding-top: 15px;">
|
||||
<ui:VisualElement style="height: 25px; width: 604px; flex-shrink: 0; flex-direction: row;">
|
||||
<ui:Label name="conversionName" style="-unity-font-style: bold; font-size: 16px; width: 593px; padding-right: 16px; flex-grow: 1; flex-shrink: 1;" />
|
||||
<ui:Image name="converterContainerHelpIcon" />
|
||||
</ui:VisualElement>
|
||||
<ui:TextElement name="conversionInfo" style="height: 46px; width: 606px; flex-shrink: 0;" />
|
||||
<ui:HelpBox message-type="Warning" text="This process makes irreversible changes to the project. Back up your project before proceeding." />
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<ui:VisualElement style="flex-grow: 1;" />
|
||||
</ui:VisualElement>
|
||||
<ui:ScrollView scroll-deceleration-rate="0,135" elasticity="0,1" name="convertersScrollView" style="flex-grow: 1; flex-shrink: 1; max-width: none; width: 632px; padding-right: 0; padding-left: 4px;" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="flex-direction: row-reverse; flex-shrink: 0; padding-left: 15px; padding-right: 14px;">
|
||||
<ui:Button text=" Convert Assets" name="convertButton" style="flex-direction: column; margin-bottom: 15px;" />
|
||||
<ui:Label style="flex-grow: 1;" />
|
||||
<ui:Button text="Initialize Converters" name="initializeButton" tooltip="This will initialize all the converters that have been toggled on." style="flex-direction: column; margin-bottom: 15px;" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd1eb3c3c695c494d855ea678fe7395b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63cb84b97408b4d25a6925daccffcd22
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,24 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:VisualElement name="converterTopVisualElement" style="flex-grow: 1; height: 250px; width: 606px; flex-shrink: 0; border-bottom-width: 2px; border-top-width: 2px; border-bottom-color: rgb(0, 0, 0); margin-bottom: 20px; padding-bottom: 0; margin-top: 20px; padding-right: 0; border-left-width: 2px; border-right-width: 2px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0);">
|
||||
<ui:VisualElement style="width: 613px; flex-direction: row; flex-grow: 0; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0; flex-shrink: 0; padding-bottom: 4px; padding-left: 2px; border-top-width: 0; border-top-color: rgb(0, 0, 0); padding-top: 11px;">
|
||||
<ui:Toggle name="converterEnabled" value="false" tooltip="Enabling this will start the pre-processing of this converter." style="flex-grow: 0; height: 24px; margin-top: 0; margin-bottom: 0;" />
|
||||
<ui:Label name="converterName" text="Name Of The Converter" style="width: 143px; -unity-text-align: middle-left; flex-grow: 1; flex-direction: column; max-height: 20%; height: 20px; min-height: 20px; padding-top: 3px; -unity-font-style: bold; padding-left: 4px;" />
|
||||
<ui:Label name="converterStats" style="flex-grow: 0; -unity-text-align: middle-right; -unity-font-style: bold; padding-right: 20px;" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="width: 596px; flex-direction: row; flex-grow: 0; flex-shrink: 1; padding-right: 0; padding-left: 2px; padding-bottom: 4px; overflow: hidden;">
|
||||
<ui:Label text="info" name="converterInfo" style="-unity-text-align: middle-left; flex-grow: 1; flex-wrap: nowrap; overflow: visible; white-space: normal; padding-top: 0;" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="flex-direction: row; height: 19px; flex-grow: 0; margin-top: 0; margin-bottom: 0; padding-right: 14px; width: 608px; flex-shrink: 1;">
|
||||
<ui:Label text=" " style="flex-grow: 1;" />
|
||||
<ui:Image name="pendingImage" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px;" />
|
||||
<ui:Label name="pendingLabel" />
|
||||
<ui:Image name="warningImage" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px;" />
|
||||
<ui:Label name="warningLabel" />
|
||||
<ui:Image name="errorImage" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px;" />
|
||||
<ui:Label name="errorLabel" />
|
||||
<ui:Image name="successImage" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px;" />
|
||||
<ui:Label name="successLabel" style="flex-grow: 0; flex-shrink: 0;" />
|
||||
</ui:VisualElement>
|
||||
<ui:ListView focusable="true" name="converterItems" show-alternating-row-backgrounds="All" text="Info" style="flex-grow: 1; flex-shrink: 0; height: 100px; width: 602px; padding-bottom: 0; margin-bottom: 0; margin-top: 0;" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aaa3e510761864dac9b71f85526490d6
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,11 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:BindableElement style="flex-grow: 1; flex-direction: row; align-items: center; flex-shrink: 0; padding-right: 20px;">
|
||||
<ui:Toggle value="true" name="converterItemActive" binding-path="isActive" />
|
||||
<ui:Label name="converterItemName" text="name" style="flex-grow: 0; width: 200px; overflow: hidden; padding-left: 4px;" />
|
||||
<ui:Label name="converterItemInfo" style="visibility: hidden;" />
|
||||
<ui:Label name="converterItemPath" text="path..." style="flex-grow: 0; flex-wrap: nowrap; white-space: nowrap; width: 300px; overflow: hidden; padding-left: 21px;" />
|
||||
<ui:Label display-tooltip-when-elided="true" style="flex-grow: 1;" />
|
||||
<ui:Image name="converterItemStatusIcon" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px; justify-content: center; flex-grow: 0; width: 16px; height: 16px;" />
|
||||
<ui:Image name="converterItemHelpIcon" style="max-width: 16px; max-height: 16px; min-width: 16px; min-height: 16px; justify-content: center; flex-grow: 0;" />
|
||||
</ui:BindableElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6de7697d63d64fabbfb31425d93541e
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,364 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages assets and baking operation of the NavMesh
|
||||
/// </summary>
|
||||
public class NavMeshAssetManager : ScriptableSingleton<NavMeshAssetManager>
|
||||
{
|
||||
internal struct AsyncBakeOperation
|
||||
{
|
||||
internal NavMeshSurface surface;
|
||||
internal NavMeshData bakeData;
|
||||
internal AsyncOperation bakeOperation;
|
||||
internal int progressReportId;
|
||||
}
|
||||
|
||||
List<AsyncBakeOperation> m_BakeOperations = new List<AsyncBakeOperation>();
|
||||
internal List<AsyncBakeOperation> GetBakeOperations() { return m_BakeOperations; }
|
||||
|
||||
struct SavedPrefabNavMeshData
|
||||
{
|
||||
internal NavMeshSurface surface;
|
||||
internal NavMeshData navMeshData;
|
||||
}
|
||||
|
||||
List<SavedPrefabNavMeshData> m_PrefabNavMeshDataAssets = new List<SavedPrefabNavMeshData>();
|
||||
|
||||
static string GetAndEnsureTargetPath(NavMeshSurface surface)
|
||||
{
|
||||
// Create directory for the asset if it does not exist yet.
|
||||
var activeScenePath = surface.gameObject.scene.path;
|
||||
|
||||
var targetPath = "Assets";
|
||||
if (!string.IsNullOrEmpty(activeScenePath))
|
||||
{
|
||||
targetPath = Path.Combine(Path.GetDirectoryName(activeScenePath), Path.GetFileNameWithoutExtension(activeScenePath));
|
||||
}
|
||||
else if (surface.IsPartOfPrefab())
|
||||
{
|
||||
var prefabStage = PrefabStageUtility.GetPrefabStage(surface.gameObject);
|
||||
var assetPath = prefabStage.assetPath;
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
var prefabDirectoryName = Path.GetDirectoryName(assetPath);
|
||||
if (!string.IsNullOrEmpty(prefabDirectoryName))
|
||||
targetPath = prefabDirectoryName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(targetPath))
|
||||
Directory.CreateDirectory(targetPath);
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
static void CreateNavMeshAsset(NavMeshSurface surface)
|
||||
{
|
||||
var targetPath = GetAndEnsureTargetPath(surface);
|
||||
|
||||
var combinedAssetPath = Path.Combine(targetPath, "NavMesh-" + surface.name + ".asset");
|
||||
combinedAssetPath = AssetDatabase.GenerateUniqueAssetPath(combinedAssetPath);
|
||||
AssetDatabase.CreateAsset(surface.navMeshData, combinedAssetPath);
|
||||
}
|
||||
|
||||
NavMeshData GetNavMeshAssetToDelete(NavMeshSurface navSurface)
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(navSurface) && !PrefabUtility.IsPartOfModelPrefab(navSurface))
|
||||
{
|
||||
// Don't allow deleting the asset belonging to the prefab parent
|
||||
var parentSurface = PrefabUtility.GetCorrespondingObjectFromSource(navSurface) as NavMeshSurface;
|
||||
if (parentSurface && navSurface.navMeshData == parentSurface.navMeshData)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do not delete the NavMeshData asset referenced from a prefab until the prefab is saved
|
||||
if (navSurface.IsPartOfPrefab() && IsCurrentPrefabNavMeshDataStored(navSurface))
|
||||
return null;
|
||||
|
||||
return navSurface.navMeshData;
|
||||
}
|
||||
|
||||
void ClearSurface(NavMeshSurface navSurface)
|
||||
{
|
||||
var hasNavMeshData = navSurface.navMeshData != null;
|
||||
StoreNavMeshDataIfInPrefab(navSurface);
|
||||
|
||||
var assetToDelete = GetNavMeshAssetToDelete(navSurface);
|
||||
navSurface.RemoveData();
|
||||
|
||||
if (hasNavMeshData)
|
||||
{
|
||||
SetNavMeshData(navSurface, null);
|
||||
EditorSceneManager.MarkSceneDirty(navSurface.gameObject.scene);
|
||||
}
|
||||
|
||||
if (assetToDelete)
|
||||
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(assetToDelete));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start baking a list of NavMeshSurface
|
||||
/// </summary>
|
||||
/// <param name="surfaces">List of surfaces</param>
|
||||
public void StartBakingSurfaces(Object[] surfaces)
|
||||
{
|
||||
// Remove first to avoid double registration of the callback
|
||||
EditorApplication.update -= UpdateAsyncBuildOperations;
|
||||
EditorApplication.update += UpdateAsyncBuildOperations;
|
||||
|
||||
foreach (NavMeshSurface surf in surfaces)
|
||||
{
|
||||
StoreNavMeshDataIfInPrefab(surf);
|
||||
|
||||
var oper = new AsyncBakeOperation();
|
||||
|
||||
oper.bakeData = InitializeBakeData(surf);
|
||||
oper.bakeOperation = surf.UpdateNavMesh(oper.bakeData);
|
||||
oper.surface = surf;
|
||||
oper.progressReportId = Progress.Start(L10n.Tr("Baking a NavMesh"), string.Format(L10n.Tr("Surface held by {0} for agent type {1}"), surf.gameObject.name, NavMesh.GetSettingsNameFromID(surf.agentTypeID)));
|
||||
|
||||
Progress.RegisterCancelCallback(oper.progressReportId, () =>
|
||||
{
|
||||
NavMeshBuilder.Cancel(oper.bakeData);
|
||||
return true;
|
||||
});
|
||||
|
||||
m_BakeOperations.Add(oper);
|
||||
}
|
||||
}
|
||||
|
||||
static NavMeshData InitializeBakeData(NavMeshSurface surface)
|
||||
{
|
||||
var emptySources = new List<NavMeshBuildSource>();
|
||||
var emptyBounds = new Bounds();
|
||||
return UnityEngine.AI.NavMeshBuilder.BuildNavMeshData(surface.GetBuildSettings(), emptySources, emptyBounds
|
||||
, surface.transform.position, surface.transform.rotation);
|
||||
}
|
||||
|
||||
void UpdateAsyncBuildOperations()
|
||||
{
|
||||
foreach (var oper in m_BakeOperations)
|
||||
{
|
||||
if (oper.surface == null || oper.bakeOperation == null)
|
||||
continue;
|
||||
|
||||
if (oper.bakeOperation.isDone)
|
||||
{
|
||||
var surface = oper.surface;
|
||||
var delete = GetNavMeshAssetToDelete(surface);
|
||||
if (delete != null)
|
||||
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(delete));
|
||||
|
||||
surface.RemoveData();
|
||||
SetNavMeshData(surface, oper.bakeData);
|
||||
|
||||
if (surface.isActiveAndEnabled)
|
||||
surface.AddData();
|
||||
CreateNavMeshAsset(surface);
|
||||
|
||||
if (!EditorApplication.isPlaying || surface.IsPartOfPrefab())
|
||||
EditorSceneManager.MarkSceneDirty(surface.gameObject.scene);
|
||||
|
||||
Progress.Finish(oper.progressReportId);
|
||||
}
|
||||
|
||||
Progress.Report(oper.progressReportId, oper.bakeOperation.progress);
|
||||
}
|
||||
m_BakeOperations.RemoveAll(o => o.bakeOperation == null || o.bakeOperation.isDone);
|
||||
if (m_BakeOperations.Count == 0)
|
||||
EditorApplication.update -= UpdateAsyncBuildOperations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an operation of baking is in progress for a specified surface
|
||||
/// </summary>
|
||||
/// <param name="surface">A NavMesh surface</param>
|
||||
/// <returns>True if the specified surface is baking</returns>
|
||||
public bool IsSurfaceBaking(NavMeshSurface surface)
|
||||
{
|
||||
if (surface == null)
|
||||
return false;
|
||||
|
||||
foreach (var oper in m_BakeOperations)
|
||||
{
|
||||
if (oper.surface == null || oper.bakeOperation == null)
|
||||
continue;
|
||||
|
||||
if (oper.surface == surface)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear NavMesh surfaces
|
||||
/// </summary>
|
||||
/// <param name="surfaces">List of surfaces</param>
|
||||
public void ClearSurfaces(Object[] surfaces)
|
||||
{
|
||||
foreach (NavMeshSurface s in surfaces)
|
||||
ClearSurface(s);
|
||||
}
|
||||
|
||||
static void SetNavMeshData(NavMeshSurface navSurface, NavMeshData navMeshData)
|
||||
{
|
||||
var so = new SerializedObject(navSurface);
|
||||
var navMeshDataProperty = so.FindProperty("m_NavMeshData");
|
||||
navMeshDataProperty.objectReferenceValue = navMeshData;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
void StoreNavMeshDataIfInPrefab(NavMeshSurface surfaceToStore)
|
||||
{
|
||||
if (!surfaceToStore.IsPartOfPrefab())
|
||||
return;
|
||||
|
||||
// check if data has already been stored for this surface
|
||||
foreach (var storedAssetInfo in m_PrefabNavMeshDataAssets)
|
||||
if (storedAssetInfo.surface == surfaceToStore)
|
||||
return;
|
||||
|
||||
if (m_PrefabNavMeshDataAssets.Count == 0)
|
||||
{
|
||||
PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces;
|
||||
PrefabStage.prefabSaving += DeleteStoredNavMeshDataAssetsForOwnedSurfaces;
|
||||
|
||||
PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges;
|
||||
PrefabStage.prefabStageClosing += ForgetUnsavedNavMeshDataChanges;
|
||||
}
|
||||
|
||||
var isDataOwner = true;
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(surfaceToStore) && !PrefabUtility.IsPartOfModelPrefab(surfaceToStore))
|
||||
{
|
||||
var basePrefabSurface = PrefabUtility.GetCorrespondingObjectFromSource(surfaceToStore) as NavMeshSurface;
|
||||
isDataOwner = basePrefabSurface == null || surfaceToStore.navMeshData != basePrefabSurface.navMeshData;
|
||||
}
|
||||
m_PrefabNavMeshDataAssets.Add(new SavedPrefabNavMeshData { surface = surfaceToStore, navMeshData = isDataOwner ? surfaceToStore.navMeshData : null });
|
||||
}
|
||||
|
||||
bool IsCurrentPrefabNavMeshDataStored(NavMeshSurface surface)
|
||||
{
|
||||
if (surface == null)
|
||||
return false;
|
||||
|
||||
foreach (var storedAssetInfo in m_PrefabNavMeshDataAssets)
|
||||
{
|
||||
if (storedAssetInfo.surface == surface)
|
||||
return storedAssetInfo.navMeshData == surface.navMeshData;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeleteStoredNavMeshDataAssetsForOwnedSurfaces(GameObject gameObjectInPrefab)
|
||||
{
|
||||
// Debug.LogFormat("DeleteStoredNavMeshDataAsset() when saving prefab {0}", gameObjectInPrefab.name);
|
||||
|
||||
var surfaces = gameObjectInPrefab.GetComponentsInChildren<NavMeshSurface>(true);
|
||||
foreach (var surface in surfaces)
|
||||
DeleteStoredPrefabNavMeshDataAsset(surface);
|
||||
}
|
||||
|
||||
void DeleteStoredPrefabNavMeshDataAsset(NavMeshSurface surface)
|
||||
{
|
||||
for (var i = m_PrefabNavMeshDataAssets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var storedAssetInfo = m_PrefabNavMeshDataAssets[i];
|
||||
if (storedAssetInfo.surface == surface)
|
||||
{
|
||||
var storedNavMeshData = storedAssetInfo.navMeshData;
|
||||
if (storedNavMeshData != null && storedNavMeshData != surface.navMeshData)
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(storedNavMeshData);
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"The asset of the previous NavMesh data ({storedNavMeshData.name}), owned by " +
|
||||
$"the prefab that was saved just now ({storedAssetInfo.surface.gameObject.transform.root.gameObject.name}), " +
|
||||
"cannot be deleted automatically because it might still be used by " +
|
||||
"NavMeshSurface components in the running scenes. You can safely delete " +
|
||||
$"the \"{storedNavMeshData.name}.asset\" file after playmode ends.", storedNavMeshData);
|
||||
}
|
||||
}
|
||||
|
||||
m_PrefabNavMeshDataAssets.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_PrefabNavMeshDataAssets.Count == 0)
|
||||
{
|
||||
PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces;
|
||||
PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges;
|
||||
}
|
||||
}
|
||||
|
||||
void ForgetUnsavedNavMeshDataChanges(PrefabStage prefabStage)
|
||||
{
|
||||
// Debug.Log("On prefab closing - forget about this object's surfaces and stop caring about prefab saving");
|
||||
|
||||
if (prefabStage == null)
|
||||
return;
|
||||
|
||||
var allSurfacesInPrefab = prefabStage.prefabContentsRoot.GetComponentsInChildren<NavMeshSurface>(true);
|
||||
NavMeshSurface surfaceInPrefab = null;
|
||||
var index = 0;
|
||||
do
|
||||
{
|
||||
if (allSurfacesInPrefab.Length > 0)
|
||||
surfaceInPrefab = allSurfacesInPrefab[index];
|
||||
|
||||
for (var i = m_PrefabNavMeshDataAssets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var storedPrefabInfo = m_PrefabNavMeshDataAssets[i];
|
||||
if (storedPrefabInfo.surface == null)
|
||||
{
|
||||
// Debug.LogFormat("A surface from the prefab got deleted after it has baked a new NavMesh but it hasn't saved it. Now the unsaved asset gets deleted. ({0})", storedPrefabInfo.navMeshData);
|
||||
|
||||
// surface got deleted, thus delete its initial NavMeshData asset
|
||||
if (storedPrefabInfo.navMeshData != null)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(storedPrefabInfo.navMeshData);
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
}
|
||||
|
||||
m_PrefabNavMeshDataAssets.RemoveAt(i);
|
||||
}
|
||||
else if (surfaceInPrefab != null && storedPrefabInfo.surface == surfaceInPrefab)
|
||||
{
|
||||
//Debug.LogFormat("The surface {0} from the prefab was storing the original NavMesh data and now will be forgotten", surfaceInPrefab);
|
||||
|
||||
var baseSurface = PrefabUtility.GetCorrespondingObjectFromSource(surfaceInPrefab) as NavMeshSurface;
|
||||
if (baseSurface == null || surfaceInPrefab.navMeshData != baseSurface.navMeshData)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(surfaceInPrefab.navMeshData);
|
||||
AssetDatabase.DeleteAsset(assetPath);
|
||||
|
||||
//Debug.LogFormat("The surface {0} from the prefab has baked new NavMeshData but did not save this change so the asset has been now deleted. ({1})",
|
||||
// surfaceInPrefab, assetPath);
|
||||
}
|
||||
|
||||
m_PrefabNavMeshDataAssets.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
} while (++index < allSurfacesInPrefab.Length);
|
||||
|
||||
if (m_PrefabNavMeshDataAssets.Count == 0)
|
||||
{
|
||||
PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces;
|
||||
PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178d8366aa1616849b91b66285c51454
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.AI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
/// <summary> Class containing a set of utility functions meant for presenting information from the NavMeshComponents into the GUI. </summary>
|
||||
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...");
|
||||
|
||||
/// <summary> Displays a GUI element for selecting the area type used by a <see cref="NavMeshSurface"/>, <see cref="NavMeshLink"/>, <see cref="NavMeshModifier"/> or <see cref="NavMeshModifierVolume"/>.</summary>
|
||||
/// <remarks> The dropdown menu lists all of the area types defined in the <a href="../manual/NavigationWindow.html#areas-tab">Areas tab</a> of the Navigation window. </remarks>
|
||||
/// <param name="labelName">The label for the field.</param>
|
||||
/// <param name="areaProperty">The serialized property that this GUI element displays and modifies. It represents a NavMesh <see cref="NavMeshModifier.area">area type</see> and it needs to store values of type <see cref="SerializedPropertyType.Integer"/>.</param>
|
||||
/// <seealso cref="NavMeshSurface.defaultArea">NavMeshSurface.defaultArea</seealso>
|
||||
/// <seealso cref="NavMeshBuildSource.area">NavMeshBuildSource.area</seealso>
|
||||
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.");
|
||||
|
||||
/// <summary> Displays a GUI element for selecting the agent type used by a <see cref="NavMeshSurface"/> or <see cref="NavMeshLink"/>. </summary>
|
||||
/// <remarks> The dropdown menu lists all of the agent types defined in the <a href="../manual/NavigationWindow.html#agents-tab">Agents tab</a> of the Navigation window. </remarks>
|
||||
/// <param name="labelName">The label for the field.</param>
|
||||
/// <param name="agentTypeID">The serialized property that this GUI element displays and modifies. It stores an <see cref="SerializedPropertyType.Integer"/> value that represents a NavMesh <see cref="NavMeshSurface.agentTypeID">agent type ID</see>.<br/>
|
||||
/// The selected item is displayed as the <a href="https://docs.unity3d.com/ScriptReference/AI.NavMesh.GetSettingsNameFromID.html">name</a> that corresponds to the stored ID.</param>
|
||||
/// <seealso href="https://docs.unity3d.com/ScriptReference/AI.NavMeshAgent-agentTypeID.html">NavMeshAgent.agentTypeID</seealso>
|
||||
/// <seealso href="https://docs.unity3d.com/ScriptReference/AI.NavMeshBuildSettings-agentTypeID.html">NavMeshBuildSettings.agentTypeID</seealso>
|
||||
/// <seealso href="https://docs.unity3d.com/ScriptReference/AI.NavMesh.GetSettingsNameFromID.html">NavMesh.GetSettingsNameFromID</seealso>
|
||||
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.
|
||||
/// <summary> Displays a GUI element for selecting multiple agent types for which a <see cref="NavMeshModifier"/> or <see cref="NavMeshModifierVolume"/> can influence the NavMesh.</summary>
|
||||
/// <remarks> The dropdown menu lists all of the agent types defined in the <a href="../manual/NavigationWindow.html#agents-tab">Agents tab</a> of the Navigation window. </remarks>
|
||||
/// <param name="labelName">The label for the field.</param>
|
||||
/// <param name="agentMask">The serialized property that holds the <a href="https://docs.unity3d.com/ScriptReference/SerializedProperty-isArray.html">array</a> of NavMesh <see cref="NavMeshSurface.agentTypeID">agent type</see> values that are selected from the items defined in the <a href="../manual/NavigationWindow.html#agents-tab">Agents tab</a> of the Navigation window. The items are stored as <see cref="SerializedPropertyType.Integer"/> <see cref="NavMeshBuildSettings.agentTypeID">ID values</see> and are displayed as their corresponding <a href="https://docs.unity3d.com/ScriptReference/AI.NavMesh.GetSettingsNameFromID.html">names</a>.</param>
|
||||
/// <seealso href="https://docs.unity3d.com/ScriptReference/AI.NavMesh.GetSettingsByIndex.html">NavMesh.GetSettingsByIndex</seealso>
|
||||
/// <seealso href="Unity.AI.Navigation.NavMeshModifier.AffectsAgentType.html">NavMeshModifier.AffectsAgentType</seealso>
|
||||
/// <seealso href="Unity.AI.Navigation.NavMeshModifierVolume.AffectsAgentType.html">NavMeshModifierVolume.AffectsAgentType</seealso>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary> Creates and selects a new GameObject as a child of another GameObject. </summary>
|
||||
/// <param name="suggestedName">The name given to the created child GameObject. If necessary, this method <a href="https://docs.unity3d.com/ScriptReference/GameObjectUtility.GetUniqueNameForSibling.html">modifies</a> the name in order to distinguish it from the other children of the same parent object.</param>
|
||||
/// <param name="parent">The GameObject to which the created GameObject is attached as a child object.</param>
|
||||
/// <returns>A new GameObject that is a child of the specified parent GameObject.</returns>
|
||||
public static GameObject CreateAndSelectGameObject(string suggestedName, GameObject parent)
|
||||
{
|
||||
return CreateAndSelectGameObject(suggestedName, parent, Array.Empty<Type>());
|
||||
}
|
||||
|
||||
internal static GameObject CreateAndSelectGameObject(string suggestedName, GameObject parent, params Type[] components)
|
||||
{
|
||||
var child = ObjectFactory.CreateGameObject(suggestedName, components);
|
||||
GOCreationCommands.Place(child, parent);
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <summary> Checks whether a serialized property has all the bits set when interpreted as a bitmask. </summary>
|
||||
/// <param name="agentMask"></param>
|
||||
/// <returns></returns>
|
||||
static bool IsAll(SerializedProperty agentMask)
|
||||
{
|
||||
return agentMask.arraySize == 1 && agentMask.GetArrayElementAtIndex(0).intValue == -1;
|
||||
}
|
||||
|
||||
/// <summary> Marks one agent type as being selected or not. </summary>
|
||||
/// <param name="userData"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary> Marks one agent type as being selected or not. </summary>
|
||||
/// <param name="agentMask"></param>
|
||||
/// <param name="agentTypeID"></param>
|
||||
/// <param name="value"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary> Marks all agent types as not being selected. </summary>
|
||||
/// <param name="data"></param>
|
||||
static void SetAgentMaskNone(object data)
|
||||
{
|
||||
var agentMask = (SerializedProperty)data;
|
||||
agentMask.ClearArray();
|
||||
agentMask.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary> Marks all agent types as being selected. </summary>
|
||||
/// <param name="data"></param>
|
||||
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...");
|
||||
|
||||
/// <summary> Obtains one string that represents the current selection of agent types. </summary>
|
||||
/// <param name="agentMask"></param>
|
||||
/// <returns> One string that represents the current selection of agent types.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Checks whether a certain agent type is selected. </summary>
|
||||
/// <param name="agentMask"></param>
|
||||
/// <param name="agentTypeID"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77fba670b979046f18d52d751e0d4659
|
||||
timeCreated: 1480524815
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
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_IsOverridingCost;
|
||||
SerializedProperty m_CostModifier;
|
||||
SerializedProperty m_AutoUpdatePosition;
|
||||
SerializedProperty m_Bidirectional;
|
||||
SerializedProperty m_EndPoint;
|
||||
SerializedProperty m_StartPoint;
|
||||
SerializedProperty m_EndTransform;
|
||||
SerializedProperty m_StartTransform;
|
||||
SerializedProperty m_Activated;
|
||||
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_IsOverridingCost = serializedObject.FindProperty("m_IsOverridingCost");
|
||||
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");
|
||||
m_EndTransform = serializedObject.FindProperty("m_EndTransform");
|
||||
m_StartTransform = serializedObject.FindProperty("m_StartTransform");
|
||||
m_Activated = serializedObject.FindProperty("m_Activated");
|
||||
m_Width = serializedObject.FindProperty("m_Width");
|
||||
|
||||
s_SelectedID = 0;
|
||||
s_SelectedPoint = -1;
|
||||
}
|
||||
|
||||
internal static void AlignTransformToEndPoints(NavMeshLink navLink)
|
||||
{
|
||||
var toWorld = navLink.LocalToWorldUnscaled();
|
||||
|
||||
var allLinksOnGameObject = navLink.gameObject.GetComponents<NavMeshLink>();
|
||||
Span<(Vector3 start, Vector3 end)> serializedEndpointsWorld = stackalloc (Vector3, Vector3)[allLinksOnGameObject.Length];
|
||||
Span<(Vector3 start, Vector3 end)> worldEndpointsBeforeAlign = stackalloc (Vector3, Vector3)[allLinksOnGameObject.Length];
|
||||
var thisLink = -1;
|
||||
for (var i = 0; i < allLinksOnGameObject.Length; i++)
|
||||
{
|
||||
var link = allLinksOnGameObject[i];
|
||||
if (link == navLink)
|
||||
thisLink = i;
|
||||
|
||||
// Store the world positions of the serialized point values
|
||||
serializedEndpointsWorld[i].start = toWorld.MultiplyPoint3x4(link.startPoint);
|
||||
serializedEndpointsWorld[i].end = toWorld.MultiplyPoint3x4(link.endPoint);
|
||||
|
||||
// Store the world positions of the used endpoints
|
||||
link.GetWorldPositions(out var initialStartPos, out var initialEndPos);
|
||||
worldEndpointsBeforeAlign[i].start = initialStartPos;
|
||||
worldEndpointsBeforeAlign[i].end = initialEndPos;
|
||||
}
|
||||
|
||||
// Use overall world position of target points or transforms to determine midpoint
|
||||
var worldStartPt = worldEndpointsBeforeAlign[thisLink].start;
|
||||
var worldEndPt = worldEndpointsBeforeAlign[thisLink].end;
|
||||
var startToEnd = worldEndPt - worldStartPt;
|
||||
var up = navLink.transform.up;
|
||||
|
||||
// Flatten
|
||||
var forward = startToEnd - Vector3.Dot(up, startToEnd) * up;
|
||||
|
||||
Undo.RecordObject(navLink.transform, Content.UndoReCenterOrigin);
|
||||
var middlePos = (worldEndPt + worldStartPt) * 0.5f;
|
||||
var lookTowardsEndAndKeepUp = Quaternion.LookRotation(forward, up);
|
||||
navLink.transform.SetPositionAndRotation(middlePos, lookTowardsEndAndKeepUp);
|
||||
|
||||
// Transform points back to local space
|
||||
var toNewLocal = navLink.LocalToWorldUnscaled().inverse;
|
||||
for (var i = 0; i < allLinksOnGameObject.Length; i++)
|
||||
{
|
||||
var link = allLinksOnGameObject[i];
|
||||
Undo.RecordObject(link, Content.UndoReCenterOrigin);
|
||||
var startAtOwnChild = link.startTransform != null && link.startTransform.IsChildOf(link.transform);
|
||||
var endAtOwnChild = link.endTransform != null && link.endTransform.IsChildOf(link.transform);
|
||||
if (startAtOwnChild)
|
||||
Undo.RecordObject(link.startTransform, Content.UndoReCenterOrigin);
|
||||
if (endAtOwnChild)
|
||||
Undo.RecordObject(link.endTransform, Content.UndoReCenterOrigin);
|
||||
using (new DeferredLinkUpdateScope(link))
|
||||
{
|
||||
link.startPoint = toNewLocal.MultiplyPoint3x4(serializedEndpointsWorld[i].start);
|
||||
link.endPoint = toNewLocal.MultiplyPoint3x4(serializedEndpointsWorld[i].end);
|
||||
|
||||
// Ensure transform targets return to world positions, in case they are children of the NavMeshLink object
|
||||
if (startAtOwnChild)
|
||||
link.startTransform.position = worldEndpointsBeforeAlign[i].start;
|
||||
|
||||
if (endAtOwnChild)
|
||||
link.endTransform.position = worldEndpointsBeforeAlign[i].end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
NavMeshComponentsGUIUtility.AgentTypePopup(Content.AgentType, m_AgentTypeID);
|
||||
NavMeshComponentsGUIUtility.AreaPopup(Content.AreaType, m_Area);
|
||||
|
||||
EditorGUILayout.PropertyField(m_IsOverridingCost, Content.CostOverrideToggle);
|
||||
EditorGUI.BeginDisabled(!m_IsOverridingCost.boolValue);
|
||||
EditorGUILayout.PropertyField(m_CostModifier, Content.CostModifier);
|
||||
EditorGUI.EndDisabled();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_StartPoint.isExpanded = EditorGUILayout.Foldout(m_StartPoint.isExpanded, Content.Positions, true);
|
||||
if (m_StartPoint.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(m_StartTransform, Content.StartTransform);
|
||||
using (new EditorGUI.DisabledScope(m_StartTransform.objectReferenceValue != null))
|
||||
EditorGUILayout.PropertyField(m_StartPoint, Content.StartPoint);
|
||||
|
||||
EditorGUILayout.PropertyField(m_EndTransform, Content.EndTransform);
|
||||
using (new EditorGUI.DisabledScope(m_EndTransform.objectReferenceValue != null))
|
||||
EditorGUILayout.PropertyField(m_EndPoint, Content.EndPoint);
|
||||
|
||||
var buttonRect = EditorGUILayout.GetControlRect();
|
||||
buttonRect = EditorGUI.IndentedRect(buttonRect);
|
||||
buttonRect.width -= EditorGUIUtility.standardVerticalSpacing;
|
||||
buttonRect.width *= 0.5f;
|
||||
if (GUI.Button(buttonRect, Content.ReverseDirectionButton))
|
||||
{
|
||||
Undo.RecordObjects(targets, Content.UndoReverseDirection);
|
||||
foreach (var nml in targets)
|
||||
ReverseDirection((NavMeshLink)nml);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
buttonRect.x += buttonRect.width + EditorGUIUtility.standardVerticalSpacing;
|
||||
if (GUI.Button(buttonRect, Content.ReCenterButton))
|
||||
{
|
||||
Undo.RecordObjects(targets, Content.UndoReCenterOrigin);
|
||||
foreach (var nml in targets)
|
||||
AlignTransformToEndPoints((NavMeshLink)nml);
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(m_AutoUpdatePosition, Content.AutoUpdatePositions);
|
||||
|
||||
EditorGUILayout.PropertyField(m_Bidirectional, Content.Bidirectional);
|
||||
EditorGUILayout.PropertyField(m_Width, Content.Width);
|
||||
|
||||
EditorGUILayout.PropertyField(m_Activated, Content.Activated);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
internal static void ReverseDirection(NavMeshLink navLink)
|
||||
{
|
||||
using (new DeferredLinkUpdateScope(navLink))
|
||||
{
|
||||
(navLink.startPoint, navLink.endPoint) = (navLink.endPoint, navLink.startPoint);
|
||||
(navLink.startTransform, navLink.endTransform) = (navLink.endTransform, navLink.startTransform);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Vector3 GetLocalDirectionRight(NavMeshLink navLink, out Vector3 localStartPosition, out Vector3 localEndPosition)
|
||||
{
|
||||
navLink.GetLocalPositions(out localStartPosition, out localEndPosition);
|
||||
var dir = localEndPosition - localStartPosition;
|
||||
return new Vector3(dir.z, 0.0f, -dir.x).normalized;
|
||||
}
|
||||
|
||||
static void DrawLink(NavMeshLink navLink)
|
||||
{
|
||||
var right = GetLocalDirectionRight(navLink, out var startPos, out var endPos);
|
||||
var rad = navLink.width * 0.5f;
|
||||
var edgeRadius = right * rad;
|
||||
|
||||
ReadOnlySpan<Vector3> corners = stackalloc[]
|
||||
{
|
||||
startPos - edgeRadius,
|
||||
startPos + edgeRadius,
|
||||
endPos + edgeRadius,
|
||||
endPos - edgeRadius
|
||||
};
|
||||
Gizmos.DrawLineStrip(corners, true);
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.InSelectionHierarchy | GizmoType.Active | GizmoType.Pickable)]
|
||||
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 = navLink.LocalToWorldUnscaled();
|
||||
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();
|
||||
|
||||
var color = s_HandleColor;
|
||||
if (!navLink.enabled)
|
||||
color = s_HandleColorDisabled;
|
||||
|
||||
var oldColor = Gizmos.color;
|
||||
var oldMatrix = Gizmos.matrix;
|
||||
|
||||
Gizmos.matrix = navLink.LocalToWorldUnscaled();
|
||||
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 toWorld = navLink.LocalToWorldUnscaled();
|
||||
|
||||
navLink.GetWorldPositions(out var worldStartPt, out var worldEndPt);
|
||||
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 = toWorld.MultiplyVector(GetLocalDirectionRight(navLink, out _, out _));
|
||||
|
||||
var oldColor = Handles.color;
|
||||
Handles.color = s_HandleColor;
|
||||
|
||||
Vector3 newWorldPos;
|
||||
|
||||
var startIsLocal = navLink.startTransform == null;
|
||||
if (s_SelectedPoint == 0 && navLink.GetInstanceID() == s_SelectedID)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (startIsLocal)
|
||||
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())
|
||||
{
|
||||
if (startIsLocal)
|
||||
{
|
||||
Undo.RecordObject(navLink, Content.UndoMoveLinkStartPoint);
|
||||
navLink.startPoint = toWorld.inverse.MultiplyPoint3x4(newWorldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
Undo.RecordObject(navLink.startTransform, Content.UndoMoveLinkStartObject);
|
||||
navLink.startTransform.position = newWorldPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Handles.Button(worldStartPt, zup, 0.1f * startSize, 0.1f * startSize,
|
||||
startIsLocal ? Handles.CubeHandleCap : Handles.SphereHandleCap))
|
||||
{
|
||||
s_SelectedPoint = 0;
|
||||
s_SelectedID = navLink.GetInstanceID();
|
||||
}
|
||||
}
|
||||
|
||||
var endIsLocal = navLink.endTransform == null;
|
||||
if (s_SelectedPoint == 1 && navLink.GetInstanceID() == s_SelectedID)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (endIsLocal)
|
||||
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())
|
||||
{
|
||||
if (endIsLocal)
|
||||
{
|
||||
Undo.RecordObject(navLink, Content.UndoMoveLinkEndPoint);
|
||||
navLink.endPoint = toWorld.inverse.MultiplyPoint3x4(newWorldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
Undo.RecordObject(navLink.endTransform, Content.UndoMoveLinkEndObject);
|
||||
navLink.endTransform.position = newWorldPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Handles.Button(worldEndPt, zup, 0.1f * endSize, 0.1f * endSize,
|
||||
endIsLocal ? Handles.CubeHandleCap : Handles.SphereHandleCap))
|
||||
{
|
||||
s_SelectedPoint = 1;
|
||||
s_SelectedID = navLink.GetInstanceID();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
newWorldPos = Handles.Slider(worldMidPt + 0.5f * navLink.width * right, right, midSize * 0.03f, Handles.DotHandleCap, 0);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(navLink, Content.UndoModifyLinkWidth);
|
||||
navLink.width = Mathf.Max(0.0f, 2.0f * Vector3.Dot(right, (newWorldPos - worldMidPt)));
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
newWorldPos = Handles.Slider(worldMidPt - 0.5f * navLink.width * right, -right, midSize * 0.03f, Handles.DotHandleCap, 0);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(navLink, Content.UndoModifyLinkWidth);
|
||||
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;
|
||||
NavMeshComponentsGUIUtility.CreateAndSelectGameObject("NavMesh Link", parent, typeof(NavMeshLink));
|
||||
}
|
||||
|
||||
internal class DeferredLinkUpdateScope : IDisposable
|
||||
{
|
||||
readonly NavMeshLink m_NavLink;
|
||||
readonly bool m_WasEnabled;
|
||||
|
||||
public DeferredLinkUpdateScope(NavMeshLink link)
|
||||
{
|
||||
m_NavLink = link;
|
||||
m_WasEnabled = link.enabled;
|
||||
link.enabled = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_NavLink.enabled = m_WasEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent AgentType = EditorGUIUtility.TrTextContent("Agent Type", "Specifies the agent type that can use the link.");
|
||||
public static readonly GUIContent AreaType = EditorGUIUtility.TrTextContent("Area Type", "The area type of the NavMesh Link, which affects pathfinding costs.");
|
||||
public static readonly GUIContent CostOverrideToggle = EditorGUIUtility.TrTextContent("Cost Override", "If enabled, the value below will be used instead of the area cost defined in the Navigation window.");
|
||||
public static readonly GUIContent CostModifier = EditorGUIUtility.TrTextContent(" ", "If Cost Override is enabled, the link uses this cost instead of the area cost defined in the Navigation window.");
|
||||
public static readonly GUIContent Positions = EditorGUIUtility.TrTextContent("Positions", "Configure the ends of the link, each specified either as a Transform's position or as an unscaled offset in the space of this GameObject.");
|
||||
public static readonly GUIContent StartTransform = EditorGUIUtility.TrTextContent("Start Transform", "Transform whose world position specifies the link start point.");
|
||||
public static readonly GUIContent StartPoint = EditorGUIUtility.TrTextContent("Start Point", "A local position where the link starts. Used only if Start Transform does not reference any object.");
|
||||
public static readonly GUIContent EndTransform = EditorGUIUtility.TrTextContent("End Transform", "Transform whose world position specifies the link end point.");
|
||||
public static readonly GUIContent EndPoint = EditorGUIUtility.TrTextContent("End Point", "A local position where the link ends. Used only if End Transform does not reference any object.");
|
||||
public static readonly GUIContent ReverseDirectionButton = EditorGUIUtility.TrTextContent("Swap", "Reverse the direction of the link by swapping the start and end.");
|
||||
public static readonly GUIContent ReCenterButton = EditorGUIUtility.TrTextContent("Re-Center Origin", "Place this GameObject at the middle point between the start and end of this link, and rotate it to point forward from the link start towards the end.");
|
||||
public static readonly GUIContent Width = EditorGUIUtility.TrTextContent("Width", "World-space width of the segments making up the ends of the link.");
|
||||
public static readonly GUIContent AutoUpdatePositions = EditorGUIUtility.TrTextContent("Auto Update Positions", "If enabled, the link will automatically update when the Start Transform, End Transform, or this GameObject's Transform changes.");
|
||||
public static readonly GUIContent Bidirectional = EditorGUIUtility.TrTextContent("Bidirectional", "If enabled, agents can traverse the link in both directions.");
|
||||
public static readonly GUIContent Activated = EditorGUIUtility.TrTextContent("Activated", "If enabled, allows the agents to traverse the link.");
|
||||
public static readonly string UndoReCenterOrigin = L10n.Tr("Re-Center NavMesh Link origin");
|
||||
public static readonly string UndoReverseDirection = L10n.Tr("Swap NavMesh Link start and end");
|
||||
public static readonly string UndoMoveLinkStartPoint = L10n.Tr("Move NavMesh Link start point");
|
||||
public static readonly string UndoMoveLinkStartObject = L10n.Tr("Move NavMesh Link start object");
|
||||
public static readonly string UndoMoveLinkEndPoint = L10n.Tr("Move NavMesh Link end point");
|
||||
public static readonly string UndoMoveLinkEndObject = L10n.Tr("Move NavMesh Link end object");
|
||||
public static readonly string UndoModifyLinkWidth = L10n.Tr("Modify NavMesh Link width");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ece1e865d1ad84587872fe8580ab5a20
|
||||
timeCreated: 1477036743
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(NavMeshModifier))]
|
||||
class NavMeshModifierEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty m_AffectedAgents;
|
||||
SerializedProperty m_IgnoreFromBuild;
|
||||
SerializedProperty m_OverrideArea;
|
||||
SerializedProperty m_Area;
|
||||
SerializedProperty m_ApplyToChildren;
|
||||
SerializedProperty m_OverrideGenerateLinks;
|
||||
SerializedProperty m_GenerateLinks;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_AffectedAgents = serializedObject.FindProperty("m_AffectedAgents");
|
||||
m_IgnoreFromBuild = serializedObject.FindProperty("m_IgnoreFromBuild");
|
||||
m_OverrideArea = serializedObject.FindProperty("m_OverrideArea");
|
||||
m_Area = serializedObject.FindProperty("m_Area");
|
||||
m_ApplyToChildren = serializedObject.FindProperty("m_ApplyToChildren");
|
||||
m_OverrideGenerateLinks = serializedObject.FindProperty("m_OverrideGenerateLinks");
|
||||
m_GenerateLinks = serializedObject.FindProperty("m_GenerateLinks");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
int mode = m_IgnoreFromBuild.boolValue ? 1 : 0;
|
||||
|
||||
mode = EditorGUILayout.Popup(Content.Mode, mode, Content.ModeChoices);
|
||||
|
||||
m_IgnoreFromBuild.boolValue = mode == 1;
|
||||
|
||||
NavMeshComponentsGUIUtility.AgentMaskPopup(Content.AffectedAgents, m_AffectedAgents);
|
||||
|
||||
EditorGUILayout.PropertyField(m_ApplyToChildren, Content.ApplyToChildren);
|
||||
|
||||
if (!m_IgnoreFromBuild.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_OverrideArea, Content.OverrideArea);
|
||||
if (m_OverrideArea.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
NavMeshComponentsGUIUtility.AreaPopup(Content.AreaType, m_Area);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_OverrideGenerateLinks, Content.OverrideGenerateLinks);
|
||||
if (m_OverrideGenerateLinks.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(m_GenerateLinks, Content.GenerateLinks);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent Mode = EditorGUIUtility.TrTextContent("Mode", "Specifies whether to consider or ignore the affected GameObject(s).");
|
||||
public static readonly string[] ModeChoices = { L10n.Tr("Add or Modify Object"), L10n.Tr("Remove Object") };
|
||||
public static readonly GUIContent AffectedAgents = EditorGUIUtility.TrTextContent("Affected Agents", "Specifies which agents the NavMesh Modifier affects.");
|
||||
public static readonly GUIContent ApplyToChildren = EditorGUIUtility.TrTextContent("Apply To Children", "If enabled, applies the configuration to the child hierarchy of the GameObject until another NavMesh Modifier component is encountered.");
|
||||
public static readonly GUIContent OverrideArea = EditorGUIUtility.TrTextContent("Override Area", "If enabled, the area type of the NavMeshModifier will be overridden by the area type selected below.");
|
||||
public static readonly GUIContent AreaType = EditorGUIUtility.TrTextContent("Area Type", "The area type of the NavMeshModifier.");
|
||||
public static readonly GUIContent OverrideGenerateLinks = EditorGUIUtility.TrTextContent("Override Generate Links", "If enabled, forces the NavMesh bake process to either include or ignore the affected GameObject(s) when you generate links.");
|
||||
public static readonly GUIContent GenerateLinks = EditorGUIUtility.TrTextContent("Generate Links", "If enabled, specifies whether or not to include the affected GameObject(s) when you generate links.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fa04b4743e3947eba4d7b9e5832ea69
|
||||
timeCreated: 1477036742
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(NavMeshModifierVolume))]
|
||||
class NavMeshModifierVolumeEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty m_AffectedAgents;
|
||||
SerializedProperty m_Area;
|
||||
SerializedProperty m_Center;
|
||||
SerializedProperty m_Size;
|
||||
|
||||
static Color s_HandleColor = new Color(187f, 138f, 240f, 210f) / 255;
|
||||
static Color s_HandleColorDisabled = new Color(187f * 0.75f, 138f * 0.75f, 240f * 0.75f, 100f) / 255;
|
||||
|
||||
BoxBoundsHandle m_BoundsHandle = new BoxBoundsHandle();
|
||||
|
||||
bool editingCollider
|
||||
{
|
||||
get { return EditMode.editMode == EditMode.SceneViewEditMode.Collider && EditMode.IsOwner(this); }
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_AffectedAgents = serializedObject.FindProperty("m_AffectedAgents");
|
||||
m_Area = serializedObject.FindProperty("m_Area");
|
||||
m_Center = serializedObject.FindProperty("m_Center");
|
||||
m_Size = serializedObject.FindProperty("m_Size");
|
||||
}
|
||||
|
||||
Bounds GetBounds()
|
||||
{
|
||||
var navModifier = (NavMeshModifierVolume)target;
|
||||
return new Bounds(navModifier.transform.position, navModifier.size);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditMode.DoEditModeInspectorModeButton(EditMode.SceneViewEditMode.Collider, "Edit Volume",
|
||||
EditorGUIUtility.IconContent("EditCollider"), GetBounds, this);
|
||||
|
||||
EditorGUILayout.PropertyField(m_Size, Content.Size);
|
||||
EditorGUILayout.PropertyField(m_Center, Content.Center);
|
||||
|
||||
NavMeshComponentsGUIUtility.AreaPopup(Content.Area, m_Area);
|
||||
NavMeshComponentsGUIUtility.AgentMaskPopup(Content.AffectedAgents, m_AffectedAgents);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.InSelectionHierarchy | GizmoType.Active)]
|
||||
static void RenderBoxGizmo(NavMeshModifierVolume navModifier, GizmoType gizmoType)
|
||||
{
|
||||
var color = navModifier.enabled ? s_HandleColor : s_HandleColorDisabled;
|
||||
var colorTrans = new Color(color.r * 0.75f, color.g * 0.75f, color.b * 0.75f, color.a * 0.15f);
|
||||
|
||||
var oldColor = Gizmos.color;
|
||||
var oldMatrix = Gizmos.matrix;
|
||||
|
||||
Gizmos.matrix = navModifier.transform.localToWorldMatrix;
|
||||
|
||||
Gizmos.color = colorTrans;
|
||||
Gizmos.DrawCube(navModifier.center, navModifier.size);
|
||||
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawWireCube(navModifier.center, navModifier.size);
|
||||
|
||||
Gizmos.matrix = oldMatrix;
|
||||
Gizmos.color = oldColor;
|
||||
|
||||
Gizmos.DrawIcon(navModifier.transform.position, "NavMeshModifierVolume Icon", true);
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.NotInSelectionHierarchy | GizmoType.Pickable)]
|
||||
static void RenderBoxGizmoNotSelected(NavMeshModifierVolume navModifier, GizmoType gizmoType)
|
||||
{
|
||||
var color = navModifier.enabled ? s_HandleColor : s_HandleColorDisabled;
|
||||
var oldColor = Gizmos.color;
|
||||
var oldMatrix = Gizmos.matrix;
|
||||
|
||||
Gizmos.matrix = navModifier.transform.localToWorldMatrix;
|
||||
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawWireCube(navModifier.center, navModifier.size);
|
||||
|
||||
Gizmos.matrix = oldMatrix;
|
||||
Gizmos.color = oldColor;
|
||||
|
||||
Gizmos.DrawIcon(navModifier.transform.position, "NavMeshModifierVolume Icon", true);
|
||||
}
|
||||
|
||||
void OnSceneGUI()
|
||||
{
|
||||
if (!editingCollider)
|
||||
return;
|
||||
|
||||
var vol = (NavMeshModifierVolume)target;
|
||||
var color = vol.enabled ? s_HandleColor : s_HandleColorDisabled;
|
||||
using (new Handles.DrawingScope(color, vol.transform.localToWorldMatrix))
|
||||
{
|
||||
m_BoundsHandle.center = vol.center;
|
||||
m_BoundsHandle.size = vol.size;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_BoundsHandle.DrawHandle();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(vol, Content.UndoModifyVolume);
|
||||
Vector3 center = m_BoundsHandle.center;
|
||||
Vector3 size = m_BoundsHandle.size;
|
||||
vol.center = center;
|
||||
vol.size = size;
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/AI/NavMesh Modifier Volume", false, 2001)]
|
||||
public static void CreateNavMeshModifierVolume(MenuCommand menuCommand)
|
||||
{
|
||||
var parent = menuCommand.context as GameObject;
|
||||
NavMeshComponentsGUIUtility.CreateAndSelectGameObject("NavMesh Modifier Volume", parent, typeof(NavMeshModifierVolume));
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent Size = EditorGUIUtility.TrTextContent("Size", "Dimensions of the NavMesh Modifier Volume.");
|
||||
public static readonly GUIContent Center = EditorGUIUtility.TrTextContent("Center", "The center of the NavMesh Modifier Volume relative to the GameObject center.");
|
||||
public static readonly GUIContent Area = EditorGUIUtility.TrTextContent("Area Type", "Describes the area type to which the NavMesh Modifier Volume applies.");
|
||||
public static readonly GUIContent AffectedAgents = EditorGUIUtility.TrTextContent("Affected Agents", "A selection of agent types that the NavMesh Modifier Volume affects.");
|
||||
public static readonly string UndoModifyVolume = L10n.Tr("Modify NavMesh Modifier Volume");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0f3bef2a67ae4e139538afec3e59b03
|
||||
timeCreated: 1477036743
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,406 @@
|
||||
#define NAVMESHCOMPONENTS_SHOW_NAVMESHDATA_REF
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.AI;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine.AI;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(NavMeshSurface))]
|
||||
class NavMeshSurfaceEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty m_AgentTypeID;
|
||||
SerializedProperty m_BuildHeightMesh;
|
||||
SerializedProperty m_Center;
|
||||
SerializedProperty m_CollectObjects;
|
||||
SerializedProperty m_DefaultArea;
|
||||
SerializedProperty m_GenerateLinks;
|
||||
SerializedProperty m_LayerMask;
|
||||
SerializedProperty m_OverrideTileSize;
|
||||
SerializedProperty m_OverrideVoxelSize;
|
||||
SerializedProperty m_Size;
|
||||
SerializedProperty m_TileSize;
|
||||
SerializedProperty m_UseGeometry;
|
||||
SerializedProperty m_VoxelSize;
|
||||
SerializedProperty m_MinRegionArea;
|
||||
SerializedProperty m_LedgeDropHeight;
|
||||
SerializedProperty m_MaxJumpAcrossDistance;
|
||||
|
||||
#if NAVMESHCOMPONENTS_SHOW_NAVMESHDATA_REF
|
||||
SerializedProperty m_NavMeshData;
|
||||
#endif
|
||||
|
||||
static bool s_ShowDebugOptions;
|
||||
|
||||
static Color s_HandleColor = new Color(127f, 214f, 244f, 100f) / 255;
|
||||
static Color s_HandleColorSelected = new Color(127f, 214f, 244f, 210f) / 255;
|
||||
static Color s_HandleColorDisabled = new Color(127f * 0.75f, 214f * 0.75f, 244f * 0.75f, 100f) / 255;
|
||||
|
||||
BoxBoundsHandle m_BoundsHandle = new BoxBoundsHandle();
|
||||
|
||||
bool editingCollider
|
||||
{
|
||||
get { return EditMode.editMode == EditMode.SceneViewEditMode.Collider && EditMode.IsOwner(this); }
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_AgentTypeID = serializedObject.FindProperty("m_AgentTypeID");
|
||||
m_BuildHeightMesh = serializedObject.FindProperty("m_BuildHeightMesh");
|
||||
m_Center = serializedObject.FindProperty("m_Center");
|
||||
m_CollectObjects = serializedObject.FindProperty("m_CollectObjects");
|
||||
m_DefaultArea = serializedObject.FindProperty("m_DefaultArea");
|
||||
m_GenerateLinks = serializedObject.FindProperty("m_GenerateLinks");
|
||||
m_LayerMask = serializedObject.FindProperty("m_LayerMask");
|
||||
m_OverrideTileSize = serializedObject.FindProperty("m_OverrideTileSize");
|
||||
m_OverrideVoxelSize = serializedObject.FindProperty("m_OverrideVoxelSize");
|
||||
m_Size = serializedObject.FindProperty("m_Size");
|
||||
m_TileSize = serializedObject.FindProperty("m_TileSize");
|
||||
m_UseGeometry = serializedObject.FindProperty("m_UseGeometry");
|
||||
m_VoxelSize = serializedObject.FindProperty("m_VoxelSize");
|
||||
m_MinRegionArea = serializedObject.FindProperty("m_MinRegionArea");
|
||||
|
||||
#if NAVMESHCOMPONENTS_SHOW_NAVMESHDATA_REF
|
||||
m_NavMeshData = serializedObject.FindProperty("m_NavMeshData");
|
||||
#endif
|
||||
}
|
||||
|
||||
Bounds GetBounds()
|
||||
{
|
||||
var navSurface = (NavMeshSurface)target;
|
||||
return new Bounds(navSurface.transform.position, navSurface.size);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
var bs = NavMesh.GetSettingsByID(m_AgentTypeID.intValue);
|
||||
|
||||
if (bs.agentTypeID != -1)
|
||||
{
|
||||
// Draw image
|
||||
const float diagramHeight = 80.0f;
|
||||
Rect agentDiagramRect = EditorGUILayout.GetControlRect(false, diagramHeight);
|
||||
NavMeshEditorHelpers.DrawAgentDiagram(agentDiagramRect, bs.agentRadius, bs.agentHeight, bs.agentClimb, bs.agentSlope);
|
||||
}
|
||||
|
||||
NavMeshComponentsGUIUtility.AgentTypePopup(Content.AgentType, m_AgentTypeID);
|
||||
|
||||
NavMeshComponentsGUIUtility.AreaPopup(Content.DefaultArea, m_DefaultArea);
|
||||
|
||||
EditorGUILayout.PropertyField(m_GenerateLinks, Content.GenerateLinks);
|
||||
|
||||
EditorGUILayout.PropertyField(m_UseGeometry, Content.UseGeometry);
|
||||
|
||||
m_CollectObjects.isExpanded = EditorGUILayout.Foldout(m_CollectObjects.isExpanded, Content.ObjectCollectionHeader, true);
|
||||
|
||||
if (m_CollectObjects.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(m_CollectObjects, Content.CollectObjects);
|
||||
if ((CollectObjects)m_CollectObjects.enumValueIndex == CollectObjects.Volume)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditMode.DoEditModeInspectorModeButton(EditMode.SceneViewEditMode.Collider, "Edit Volume",
|
||||
EditorGUIUtility.IconContent("EditCollider"), GetBounds, this);
|
||||
EditorGUILayout.PropertyField(m_Size);
|
||||
EditorGUILayout.PropertyField(m_Center);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (editingCollider)
|
||||
EditMode.QuitEditMode();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_LayerMask, Content.IncludeLayers);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_OverrideVoxelSize.isExpanded = EditorGUILayout.Foldout(m_OverrideVoxelSize.isExpanded, Content.AdvancedHeader, true);
|
||||
if (m_OverrideVoxelSize.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// Override voxel size.
|
||||
EditorGUILayout.PropertyField(m_OverrideVoxelSize, Content.OverrideVoxelSize);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_OverrideVoxelSize.boolValue || m_OverrideVoxelSize.hasMultipleDifferentValues))
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(m_VoxelSize, Content.VoxelSize);
|
||||
|
||||
if (!m_OverrideVoxelSize.hasMultipleDifferentValues)
|
||||
{
|
||||
if (!m_AgentTypeID.hasMultipleDifferentValues)
|
||||
{
|
||||
float voxelsPerRadius = m_VoxelSize.floatValue > 0.0f ? (bs.agentRadius / m_VoxelSize.floatValue) : 0.0f;
|
||||
EditorGUILayout.LabelField(" ", string.Format(Content.VoxelSizeFormatString, voxelsPerRadius), EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
if (m_OverrideVoxelSize.boolValue)
|
||||
EditorGUILayout.HelpBox(Content.VoxelSizeHelpBox, MessageType.None);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// Override tile size
|
||||
EditorGUILayout.PropertyField(m_OverrideTileSize, Content.OverrideTileSize);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_OverrideTileSize.boolValue || m_OverrideTileSize.hasMultipleDifferentValues))
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(m_TileSize, Content.TileSize);
|
||||
|
||||
if (!m_TileSize.hasMultipleDifferentValues && !m_VoxelSize.hasMultipleDifferentValues)
|
||||
{
|
||||
float tileWorldSize = m_TileSize.intValue * m_VoxelSize.floatValue;
|
||||
EditorGUILayout.LabelField(" ", string.Format(Content.TileWorldSizeFormatString, tileWorldSize), EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
if (!m_OverrideTileSize.hasMultipleDifferentValues)
|
||||
{
|
||||
if (m_OverrideTileSize.boolValue)
|
||||
EditorGUILayout.HelpBox(Content.TileWorldSizeHelpBox, MessageType.None);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_MinRegionArea, Content.MinimumRegionArea);
|
||||
|
||||
// Height mesh
|
||||
EditorGUILayout.PropertyField(m_BuildHeightMesh, Content.BuildHeightMesh);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var hadError = false;
|
||||
var multipleTargets = targets.Length > 1;
|
||||
foreach (NavMeshSurface navSurface in targets)
|
||||
{
|
||||
var settings = navSurface.GetBuildSettings();
|
||||
|
||||
// Calculating bounds is potentially expensive when unbounded - so here we just use the center/size.
|
||||
// It means the validation is not checking vertical voxel limit correctly when the surface is set to something else than "in volume".
|
||||
var bounds = new Bounds(Vector3.zero, Vector3.zero);
|
||||
if (navSurface.collectObjects == CollectObjects.Volume)
|
||||
{
|
||||
bounds = new Bounds(navSurface.center, navSurface.size);
|
||||
}
|
||||
|
||||
var errors = settings.ValidationReport(bounds);
|
||||
if (errors.Length > 0)
|
||||
{
|
||||
if (multipleTargets)
|
||||
EditorGUILayout.LabelField(navSurface.name);
|
||||
foreach (var err in errors)
|
||||
{
|
||||
EditorGUILayout.HelpBox(err, MessageType.Warning);
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(EditorGUIUtility.labelWidth);
|
||||
if (GUILayout.Button(NavMeshComponentsGUIUtility.k_OpenAgentSettingsText, EditorStyles.miniButton))
|
||||
NavMeshEditorHelpers.OpenAgentSettings(navSurface.agentTypeID);
|
||||
GUILayout.EndHorizontal();
|
||||
hadError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hadError)
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#if NAVMESHCOMPONENTS_SHOW_NAVMESHDATA_REF
|
||||
var nmdRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.BeginProperty(nmdRect, GUIContent.none, m_NavMeshData);
|
||||
var rectLabel = EditorGUI.PrefixLabel(nmdRect, GUIUtility.GetControlID(FocusType.Passive), Content.NavMeshData);
|
||||
EditorGUI.EndProperty();
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.BeginProperty(nmdRect, GUIContent.none, m_NavMeshData);
|
||||
EditorGUI.ObjectField(rectLabel, m_NavMeshData, GUIContent.none);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
#endif
|
||||
using (new EditorGUI.DisabledScope(Application.isPlaying || m_AgentTypeID.intValue == -1))
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(EditorGUIUtility.labelWidth);
|
||||
|
||||
using (new EditorGUI.DisabledScope(targets.All(s => (s as NavMeshSurface)?.navMeshData == null)))
|
||||
{
|
||||
if (GUILayout.Button(Content.ClearButton))
|
||||
{
|
||||
NavMeshAssetManager.instance.ClearSurfaces(targets);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(Content.BakeButton))
|
||||
{
|
||||
NavMeshAssetManager.instance.StartBakingSurfaces(targets);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Inform when selected target is being baked
|
||||
var bakeOperations = NavMeshAssetManager.instance.GetBakeOperations();
|
||||
bool bakeInProgress = bakeOperations.Any(b =>
|
||||
{
|
||||
if (!targets.Contains(b.surface))
|
||||
return false;
|
||||
|
||||
return b.bakeOperation != null;
|
||||
});
|
||||
|
||||
if (bakeInProgress)
|
||||
{
|
||||
GUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
if (GUILayout.Button(Content.BakeInProgressButton, EditorStyles.linkLabel))
|
||||
Progress.ShowDetails(false);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.InSelectionHierarchy | GizmoType.Active | GizmoType.Pickable)]
|
||||
static void RenderGizmoSelected(NavMeshSurface navSurface, GizmoType gizmoType)
|
||||
{
|
||||
navSurface.navMeshDataInstance.FlagAsInSelectionHierarchy();
|
||||
DrawBoundingBoxGizmoAndIcon(navSurface, true);
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.NotInSelectionHierarchy | GizmoType.Pickable)]
|
||||
static void RenderGizmoNotSelected(NavMeshSurface navSurface, GizmoType gizmoType)
|
||||
{
|
||||
DrawBoundingBoxGizmoAndIcon(navSurface, false);
|
||||
}
|
||||
|
||||
static void DrawBoundingBoxGizmoAndIcon(NavMeshSurface navSurface, bool selected)
|
||||
{
|
||||
var color = selected ? s_HandleColorSelected : s_HandleColor;
|
||||
if (!navSurface.enabled)
|
||||
color = s_HandleColorDisabled;
|
||||
|
||||
var oldColor = Gizmos.color;
|
||||
var oldMatrix = Gizmos.matrix;
|
||||
|
||||
// Use the unscaled matrix for the NavMeshSurface
|
||||
var localToWorld = Matrix4x4.TRS(navSurface.transform.position, navSurface.transform.rotation, Vector3.one);
|
||||
Gizmos.matrix = localToWorld;
|
||||
|
||||
if (navSurface.collectObjects == CollectObjects.Volume)
|
||||
{
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawWireCube(navSurface.center, navSurface.size);
|
||||
|
||||
if (selected && navSurface.enabled)
|
||||
{
|
||||
var colorTrans = new Color(color.r * 0.75f, color.g * 0.75f, color.b * 0.75f, color.a * 0.15f);
|
||||
Gizmos.color = colorTrans;
|
||||
Gizmos.DrawCube(navSurface.center, navSurface.size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (navSurface.navMeshData != null)
|
||||
{
|
||||
var bounds = navSurface.navMeshData.sourceBounds;
|
||||
Gizmos.color = Color.grey;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
}
|
||||
}
|
||||
|
||||
Gizmos.matrix = oldMatrix;
|
||||
Gizmos.color = oldColor;
|
||||
|
||||
Gizmos.DrawIcon(navSurface.transform.position, "NavMeshSurface Icon", true);
|
||||
}
|
||||
|
||||
void OnSceneGUI()
|
||||
{
|
||||
if (!editingCollider)
|
||||
return;
|
||||
|
||||
var navSurface = (NavMeshSurface)target;
|
||||
var color = navSurface.enabled ? s_HandleColor : s_HandleColorDisabled;
|
||||
var localToWorld = Matrix4x4.TRS(navSurface.transform.position, navSurface.transform.rotation, Vector3.one);
|
||||
using (new Handles.DrawingScope(color, localToWorld))
|
||||
{
|
||||
m_BoundsHandle.center = navSurface.center;
|
||||
m_BoundsHandle.size = navSurface.size;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_BoundsHandle.DrawHandle();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(navSurface, Content.UndoModifyVolume);
|
||||
Vector3 center = m_BoundsHandle.center;
|
||||
Vector3 size = m_BoundsHandle.size;
|
||||
navSurface.center = center;
|
||||
navSurface.size = size;
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/AI/NavMesh Surface", false, 2000)]
|
||||
public static void CreateNavMeshSurface(MenuCommand menuCommand)
|
||||
{
|
||||
var parent = menuCommand.context as GameObject;
|
||||
NavMeshComponentsGUIUtility.CreateAndSelectGameObject("NavMesh Surface", parent, typeof(NavMeshSurface));
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent AgentType = EditorGUIUtility.TrTextContent("Agent Type", "The NavMesh Agent type that uses the NavMesh Surface.");
|
||||
public static readonly GUIContent DefaultArea = EditorGUIUtility.TrTextContent("Default Area", "The area type assumed for all the objects at the moment when Unity generates the NavMesh. Use the NavMesh Modifier component to override the area type of an object and its hierarchy.");
|
||||
public static readonly GUIContent GenerateLinks = EditorGUIUtility.TrTextContent("Generate Links", "If enabled, collected objects will generate unidirectional links according to the drop height and jump distance values in the agent settings. Use the NavMesh Modifier component to override this behavior for an object and its hierarchy.");
|
||||
public static readonly GUIContent UseGeometry = EditorGUIUtility.TrTextContent("Use Geometry", "The type of geometry to create the NavMesh from.");
|
||||
public static readonly GUIContent ObjectCollectionHeader = EditorGUIUtility.TrTextContent("Object Collection", "Parameters that define how to select objects from the scene.");
|
||||
public static readonly GUIContent CollectObjects = EditorGUIUtility.TrTextContent("Collect Objects", "Defines which GameObjects to use for baking.");
|
||||
public static readonly GUIContent IncludeLayers = EditorGUIUtility.TrTextContent("Include Layers", "Define the layers on which GameObjects are included in the bake process.");
|
||||
public static readonly GUIContent AdvancedHeader = EditorGUIUtility.TrTextContent("Advanced", "Parameters that control the level of detail and the structure of the navigation data during its creation.");
|
||||
public static readonly GUIContent OverrideVoxelSize = EditorGUIUtility.TrTextContent("Override Voxel Size", "If enabled, uses the value below to control how accurately Unity processes the scene geometry when it creates the NavMesh.");
|
||||
public static readonly GUIContent VoxelSize = EditorGUIUtility.TrTextContent("Voxel Size", "The width of cells in a grid used to sample the level geometry. The cell height is half of the width.");
|
||||
public static readonly string VoxelSizeFormatString = L10n.Tr("{0:0.00} voxels per agent radius");
|
||||
public static readonly string VoxelSizeHelpBox = L10n.Tr("Voxel size controls the accuracy with which Unity generates the NavMesh from the scene geometry. A good voxel size fits 2-4 voxels per agent radius. When you reduce the voxel size, both the accuracy and the bake duration increase.");
|
||||
public static readonly GUIContent OverrideTileSize = EditorGUIUtility.TrTextContent("Override Tile Size", "If enabled, the value below overrides the size of the tiles that partition the NavMesh.");
|
||||
public static readonly GUIContent TileSize = EditorGUIUtility.TrTextContent("Tile Size", "The number of voxels that determines the width of a square NavMesh tile. The created NavMesh is subdivided into a grid of tiles in order to make the bake process parallel and memory efficient. A value of 256 is a good balance between memory usage and NavMesh fragmentation.");
|
||||
public static readonly string TileWorldSizeFormatString = L10n.Tr("{0:0.00} world units");
|
||||
public static readonly string TileWorldSizeHelpBox = L10n.Tr("Tile size reduces the impact of scene changes to only a section of the NavMesh. Smaller tiles allow carving or rebuilding to produce localized changes but may generate more polygon data overall.");
|
||||
public static readonly GUIContent MinimumRegionArea = EditorGUIUtility.TrTextContent("Minimum Region Area", "Allows you to cull away the small regions disconnected from the larger NavMesh.");
|
||||
public static readonly GUIContent BuildHeightMesh = EditorGUIUtility.TrTextContent("Build Height Mesh", "Enables the creation of additional data used for determining more accurately the height at any position on the NavMesh.");
|
||||
public static readonly GUIContent NavMeshData = EditorGUIUtility.TrTextContent("NavMesh Data", "Locate the asset file where the NavMesh is stored.");
|
||||
public static readonly GUIContent ClearButton = EditorGUIUtility.TrTextContent("Clear", "Clear NavMesh data for this surface.");
|
||||
public static readonly GUIContent BakeButton = EditorGUIUtility.TrTextContent("Bake", "Create the NavMesh with the current settings.");
|
||||
public static readonly GUIContent BakeInProgressButton = EditorGUIUtility.TrTextContent("NavMesh baking is in progress.");
|
||||
public static readonly string UndoModifyVolume = L10n.Tr("Modify NavMesh Surface Volume");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c32167dbf3314852b6006a288eb449b
|
||||
timeCreated: 1476968447
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,349 @@
|
||||
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;
|
||||
|
||||
// Look for the checkbox container and make it align the checkbox to the right (so that all checkboxes are justified)
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c660403886dc4a449f55327665b10bbb
|
||||
timeCreated: 1629385811
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VisualizationSettings = UnityEditor.AI.NavMeshVisualizationSettings;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
class NavigationPreferencesProvider : SettingsProvider
|
||||
{
|
||||
class Styles
|
||||
{
|
||||
internal static readonly GUIContent NavMeshVisualizationSettingsLabel =
|
||||
EditorGUIUtility.TrTextContent("NavMesh Visualization Settings");
|
||||
|
||||
internal static readonly GUIContent SelectedSurfacesOpacityLabel =
|
||||
EditorGUIUtility.TrTextContent("Selected Surfaces Opacity", "Controls the mesh transparency for surfaces inside the selection hierarchy");
|
||||
|
||||
internal static readonly GUIContent UnselectedSurfacesOpacityLabel =
|
||||
EditorGUIUtility.TrTextContent("Unselected Surfaces Opacity", "Controls the mesh transparency for surfaces outside the selection hierarchy");
|
||||
|
||||
internal static readonly GUIContent HeightMeshColorLabel =
|
||||
EditorGUIUtility.TrTextContent("Height Mesh Color", "Color used to display height mesh information in the scene view");
|
||||
|
||||
internal static readonly GUIContent ResetVisualizationSettingsButtonLabel =
|
||||
EditorGUIUtility.TrTextContent("Reset to Defaults", "Revert visualization settings to their original values. Customized values will be lost");
|
||||
}
|
||||
|
||||
NavigationPreferencesProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null)
|
||||
: base(path, scopes, keywords)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnGUI(string searchContext)
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
EditorGUILayout.LabelField(Styles.NavMeshVisualizationSettingsLabel, EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Visualization settings
|
||||
VisualizationSettings.selectedSurfacesOpacity = EditorGUILayout.Slider(Styles.SelectedSurfacesOpacityLabel, VisualizationSettings.selectedSurfacesOpacity, 0, 1);
|
||||
VisualizationSettings.unselectedSurfacesOpacity = EditorGUILayout.Slider(Styles.UnselectedSurfacesOpacityLabel, VisualizationSettings.unselectedSurfacesOpacity, 0, 1);
|
||||
VisualizationSettings.heightMeshColor = EditorGUILayout.ColorField(Styles.HeightMeshColorLabel, VisualizationSettings.heightMeshColor);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
// Option to reset the visualization settings to their default values
|
||||
if (GUILayout.Button(Styles.ResetVisualizationSettingsButtonLabel, GUILayout.Width(160)))
|
||||
{
|
||||
VisualizationSettings.ResetSelectedSurfacesOpacity();
|
||||
VisualizationSettings.ResetUnselectedSurfacesOpacity();
|
||||
VisualizationSettings.ResetHeightMeshColor();
|
||||
}
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// Repaint the scene view when settings changed to update the visualization accordingly
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
internal static SettingsProvider CreateNavigationProjectSettingProvider()
|
||||
{
|
||||
var provider = new NavigationPreferencesProvider("Preferences/AI Navigation", SettingsScope.User, GetSearchKeywordsFromGUIContentProperties<Styles>());
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8c8febe35b734f4eb15f23d12e5faed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,414 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor.AI;
|
||||
using UnityEditorInternal;
|
||||
using Object = UnityEngine.Object;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.AI.Navigation.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
[EditorWindowTitle(title = "Navigation")]
|
||||
internal class NavigationWindow : EditorWindow
|
||||
{
|
||||
static NavigationWindow s_NavigationWindow;
|
||||
|
||||
// Scene based bake configuration
|
||||
SerializedObject m_SettingsObject;
|
||||
|
||||
// Project based configuration
|
||||
SerializedObject m_NavMeshProjectSettingsObject;
|
||||
SerializedProperty m_Areas;
|
||||
|
||||
SerializedProperty m_AgentTypes;
|
||||
SerializedProperty m_SettingNames;
|
||||
|
||||
Vector2 m_ScrollPos = Vector2.zero;
|
||||
bool m_Advanced;
|
||||
|
||||
ReorderableList m_AreasList;
|
||||
ReorderableList m_AgentTypeList;
|
||||
|
||||
enum Mode
|
||||
{
|
||||
AgentTypeSettings = 0,
|
||||
AreaSettings = 1,
|
||||
}
|
||||
|
||||
Mode m_Mode = Mode.AgentTypeSettings;
|
||||
|
||||
static class Styles
|
||||
{
|
||||
internal static readonly GUIContent k_AgentTypesHeader = EditorGUIUtility.TrTextContent("Agent Types");
|
||||
internal static readonly GUIContent k_NameLabel = EditorGUIUtility.TrTextContent("Name");
|
||||
internal static readonly GUIContent k_CostLabel = EditorGUIUtility.TrTextContent("Cost");
|
||||
internal static readonly GUIContent[] k_ModeToggles =
|
||||
{
|
||||
EditorGUIUtility.TrTextContent("Agents", "Navmesh agent settings."),
|
||||
EditorGUIUtility.TrTextContent("Areas", "Navmesh area settings."),
|
||||
};
|
||||
internal static readonly GUIStyle k_ContentMargins = new GUIStyle
|
||||
{
|
||||
padding = new RectOffset(4, 4, 4, 4)
|
||||
};
|
||||
};
|
||||
|
||||
static NavigationWindow()
|
||||
{
|
||||
NavMeshEditorHelpers.areaSettingsClicked += OpenAreaSettings;
|
||||
NavMeshEditorHelpers.agentTypeSettingsClicked += OpenAgentSettings;
|
||||
}
|
||||
|
||||
[MenuItem("Window/AI/Navigation", false, 1)]
|
||||
public static void SetupWindow()
|
||||
{
|
||||
var window = GetWindow<NavigationWindow>();
|
||||
window.minSize = new Vector2(300, 360);
|
||||
}
|
||||
|
||||
static void OpenAreaSettings()
|
||||
{
|
||||
SetupWindow();
|
||||
if (s_NavigationWindow == null)
|
||||
return;
|
||||
|
||||
s_NavigationWindow.m_Mode = Mode.AreaSettings;
|
||||
s_NavigationWindow.InitProjectSettings();
|
||||
s_NavigationWindow.InitAgentTypes();
|
||||
}
|
||||
|
||||
static void OpenAgentSettings(int agentTypeID)
|
||||
{
|
||||
SetupWindow();
|
||||
if (s_NavigationWindow == null)
|
||||
return;
|
||||
|
||||
s_NavigationWindow.m_Mode = Mode.AgentTypeSettings;
|
||||
s_NavigationWindow.InitProjectSettings();
|
||||
s_NavigationWindow.InitAgentTypes();
|
||||
|
||||
s_NavigationWindow.m_AgentTypeList.index = -1;
|
||||
for (int i = 0; i < s_NavigationWindow.m_AgentTypes.arraySize; i++)
|
||||
{
|
||||
SerializedProperty agentType = s_NavigationWindow.m_AgentTypes.GetArrayElementAtIndex(i);
|
||||
SerializedProperty idProp = agentType.FindPropertyRelative("agentTypeID");
|
||||
if (idProp.intValue == agentTypeID)
|
||||
{
|
||||
s_NavigationWindow.m_AgentTypeList.index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
var iconPath = $"{NavMeshComponentsGUIUtility.k_PackageEditorResourcesFolder}NavigationWindowIcon.asset";
|
||||
titleContent = EditorGUIUtility.TrTextContentWithIcon("Navigation", iconPath);
|
||||
s_NavigationWindow = this;
|
||||
EditorApplication.searchChanged += Repaint;
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void InitProjectSettings()
|
||||
{
|
||||
if (m_NavMeshProjectSettingsObject == null)
|
||||
{
|
||||
Object obj = Unsupported.GetSerializedAssetInterfaceSingleton("NavMeshProjectSettings");
|
||||
m_NavMeshProjectSettingsObject = new SerializedObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void InitAreas()
|
||||
{
|
||||
if (m_Areas == null)
|
||||
{
|
||||
m_Areas = m_NavMeshProjectSettingsObject.FindProperty("areas");
|
||||
}
|
||||
if (m_AreasList == null)
|
||||
{
|
||||
m_AreasList = new ReorderableList(m_NavMeshProjectSettingsObject, m_Areas, false, true, false, false);
|
||||
m_AreasList.drawElementCallback = DrawAreaListElement;
|
||||
m_AreasList.drawHeaderCallback = DrawAreaListHeader;
|
||||
m_AreasList.elementHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
void InitAgentTypes()
|
||||
{
|
||||
if (m_AgentTypes == null)
|
||||
{
|
||||
m_AgentTypes = m_NavMeshProjectSettingsObject.FindProperty("m_Settings");
|
||||
m_SettingNames = m_NavMeshProjectSettingsObject.FindProperty("m_SettingNames");
|
||||
}
|
||||
if (m_AgentTypeList == null)
|
||||
{
|
||||
m_AgentTypeList = new ReorderableList(m_NavMeshProjectSettingsObject, m_AgentTypes, false, true, true, true);
|
||||
m_AgentTypeList.drawElementCallback = DrawAgentTypeListElement;
|
||||
m_AgentTypeList.drawHeaderCallback = DrawAgentTypeListHeader;
|
||||
m_AgentTypeList.onAddCallback = AddAgentType;
|
||||
m_AgentTypeList.onRemoveCallback = RemoveAgentType;
|
||||
m_AgentTypeList.elementHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
// This code is replicated from the NavMesh debug draw code.
|
||||
int Bit(int a, int b)
|
||||
{
|
||||
return (a & (1 << b)) >> b;
|
||||
}
|
||||
|
||||
Color GetAreaColor(int i)
|
||||
{
|
||||
if (i == 0)
|
||||
return new Color(0, 0.75f, 1.0f, 0.5f);
|
||||
int r = (Bit(i, 4) + Bit(i, 1) * 2 + 1) * 63;
|
||||
int g = (Bit(i, 3) + Bit(i, 2) * 2 + 1) * 63;
|
||||
int b = (Bit(i, 5) + Bit(i, 0) * 2 + 1) * 63;
|
||||
return new Color(r / 255.0f, g / 255.0f, b / 255.0f, 0.5f);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
s_NavigationWindow = null;
|
||||
EditorApplication.searchChanged -= Repaint;
|
||||
}
|
||||
|
||||
|
||||
void OnSelectionChange()
|
||||
{
|
||||
m_ScrollPos = Vector2.zero;
|
||||
}
|
||||
|
||||
void ModeToggle()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
m_Mode = (Mode)GUILayout.Toolbar((int)m_Mode, Styles.k_ModeToggles, "LargeButton", GUI.ToolbarButtonSize.FitToContents);
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
static void GetAreaListRects(Rect rect, out Rect stripeRect, out Rect labelRect, out Rect nameRect, out Rect costRect)
|
||||
{
|
||||
float stripeWidth = EditorGUIUtility.singleLineHeight * 0.8f;
|
||||
float labelWidth = EditorGUIUtility.singleLineHeight * 5;
|
||||
float costWidth = EditorGUIUtility.singleLineHeight * 4;
|
||||
float nameWidth = rect.width - stripeWidth - labelWidth - costWidth;
|
||||
float x = rect.x;
|
||||
stripeRect = new Rect(x, rect.y, stripeWidth - 4, rect.height);
|
||||
x += stripeWidth;
|
||||
labelRect = new Rect(x, rect.y, labelWidth - 4, rect.height);
|
||||
x += labelWidth;
|
||||
nameRect = new Rect(x, rect.y, nameWidth - 4, rect.height);
|
||||
x += nameWidth;
|
||||
costRect = new Rect(x, rect.y, costWidth, rect.height);
|
||||
}
|
||||
|
||||
void DrawAreaListHeader(Rect rect)
|
||||
{
|
||||
GetAreaListRects(rect, out _, out _, out Rect nameRect, out Rect costRect);
|
||||
GUI.Label(nameRect, Styles.k_NameLabel);
|
||||
GUI.Label(costRect, Styles.k_CostLabel);
|
||||
}
|
||||
|
||||
void DrawAreaListElement(Rect rect, int index, bool selected, bool focused)
|
||||
{
|
||||
SerializedProperty areaProp = m_Areas.GetArrayElementAtIndex(index);
|
||||
if (areaProp == null)
|
||||
return;
|
||||
SerializedProperty nameProp = areaProp.FindPropertyRelative("name");
|
||||
SerializedProperty costProp = areaProp.FindPropertyRelative("cost");
|
||||
if (nameProp == null || costProp == null)
|
||||
return;
|
||||
|
||||
rect.height -= 2; // nicer looking with selected list row and a text field in it
|
||||
|
||||
bool builtInLayer;
|
||||
bool allowChangeName;
|
||||
bool allowChangeCost;
|
||||
switch (index)
|
||||
{
|
||||
case 0: // Default
|
||||
builtInLayer = true;
|
||||
allowChangeName = false;
|
||||
allowChangeCost = true;
|
||||
break;
|
||||
case 1: // NonWalkable
|
||||
builtInLayer = true;
|
||||
allowChangeName = false;
|
||||
allowChangeCost = false;
|
||||
break;
|
||||
case 2: // Jump
|
||||
builtInLayer = true;
|
||||
allowChangeName = false;
|
||||
allowChangeCost = true;
|
||||
break;
|
||||
default:
|
||||
builtInLayer = false;
|
||||
allowChangeName = true;
|
||||
allowChangeCost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
GetAreaListRects(rect, out Rect stripeRect, out Rect labelRect, out Rect nameRect, out Rect costRect);
|
||||
|
||||
bool oldEnabled = GUI.enabled;
|
||||
Color color = GetAreaColor(index);
|
||||
Color dimmed = new Color(color.r * 0.1f, color.g * 0.1f, color.b * 0.1f, 0.6f);
|
||||
EditorGUI.DrawRect(stripeRect, color);
|
||||
|
||||
EditorGUI.DrawRect(new Rect(stripeRect.x, stripeRect.y, 1, stripeRect.height), dimmed);
|
||||
EditorGUI.DrawRect(new Rect(stripeRect.x + stripeRect.width - 1, stripeRect.y, 1, stripeRect.height), dimmed);
|
||||
EditorGUI.DrawRect(new Rect(stripeRect.x + 1, stripeRect.y, stripeRect.width - 2, 1), dimmed);
|
||||
EditorGUI.DrawRect(new Rect(stripeRect.x + 1, stripeRect.y + stripeRect.height - 1, stripeRect.width - 2, 1), dimmed);
|
||||
|
||||
if (builtInLayer)
|
||||
GUI.Label(labelRect, EditorGUIUtility.TrTempContent("Built-in " + index));
|
||||
else
|
||||
GUI.Label(labelRect, EditorGUIUtility.TrTempContent("User " + index));
|
||||
|
||||
int oldIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
GUI.enabled = oldEnabled && allowChangeName;
|
||||
EditorGUI.PropertyField(nameRect, nameProp, GUIContent.none);
|
||||
|
||||
GUI.enabled = oldEnabled && allowChangeCost;
|
||||
EditorGUI.PropertyField(costRect, costProp, GUIContent.none);
|
||||
|
||||
GUI.enabled = oldEnabled;
|
||||
|
||||
EditorGUI.indentLevel = oldIndent;
|
||||
}
|
||||
|
||||
static void AddAgentType(ReorderableList list)
|
||||
{
|
||||
UnityEngine.AI.NavMesh.CreateSettings();
|
||||
list.index = UnityEngine.AI.NavMesh.GetSettingsCount() - 1;
|
||||
}
|
||||
|
||||
void RemoveAgentType(ReorderableList list)
|
||||
{
|
||||
SerializedProperty agentTypeProp = m_AgentTypes.GetArrayElementAtIndex(list.index);
|
||||
if (agentTypeProp == null)
|
||||
return;
|
||||
SerializedProperty idProp = agentTypeProp.FindPropertyRelative("agentTypeID");
|
||||
if (idProp == null)
|
||||
return;
|
||||
// Cannot delete default.
|
||||
if (idProp.intValue == 0)
|
||||
return;
|
||||
|
||||
m_SettingNames.DeleteArrayElementAtIndex(list.index);
|
||||
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
||||
}
|
||||
|
||||
static void DrawAgentTypeListHeader(Rect rect)
|
||||
{
|
||||
GUI.Label(rect, Styles.k_AgentTypesHeader);
|
||||
}
|
||||
|
||||
void DrawAgentTypeListElement(Rect rect, int index, bool selected, bool focused)
|
||||
{
|
||||
SerializedProperty agentProp = m_AgentTypes.GetArrayElementAtIndex(index);
|
||||
if (agentProp == null)
|
||||
return;
|
||||
SerializedProperty idProp = agentProp.FindPropertyRelative("agentTypeID");
|
||||
if (idProp == null)
|
||||
return;
|
||||
|
||||
rect.height -= 2; // nicer looking with selected list row and a text field in it
|
||||
|
||||
bool isDefault = idProp.intValue == 0;
|
||||
using (new EditorGUI.DisabledScope(isDefault))
|
||||
{
|
||||
var settingsName = UnityEngine.AI.NavMesh.GetSettingsNameFromID(idProp.intValue);
|
||||
GUI.Label(rect, EditorGUIUtility.TrTempContent(settingsName));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
ModeToggle();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
InitProjectSettings();
|
||||
|
||||
m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
|
||||
switch (m_Mode)
|
||||
{
|
||||
case Mode.AreaSettings:
|
||||
AreaSettings();
|
||||
break;
|
||||
case Mode.AgentTypeSettings:
|
||||
AgentTypeSettings();
|
||||
break;
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
void AreaSettings()
|
||||
{
|
||||
if (m_Areas == null)
|
||||
InitAreas();
|
||||
m_NavMeshProjectSettingsObject.Update();
|
||||
|
||||
using (new GUILayout.VerticalScope(Styles.k_ContentMargins))
|
||||
{
|
||||
m_AreasList.DoLayoutList();
|
||||
}
|
||||
|
||||
m_NavMeshProjectSettingsObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void AgentTypeSettings()
|
||||
{
|
||||
if (m_AgentTypes == null)
|
||||
InitAgentTypes();
|
||||
m_NavMeshProjectSettingsObject.Update();
|
||||
|
||||
if (m_AgentTypeList.index < 0)
|
||||
m_AgentTypeList.index = 0;
|
||||
|
||||
using (new GUILayout.VerticalScope(Styles.k_ContentMargins))
|
||||
{
|
||||
m_AgentTypeList.DoLayoutList();
|
||||
}
|
||||
|
||||
if (m_AgentTypeList.index >= 0 && m_AgentTypeList.index < m_AgentTypes.arraySize)
|
||||
{
|
||||
SerializedProperty nameProp = m_SettingNames.GetArrayElementAtIndex(m_AgentTypeList.index);
|
||||
SerializedProperty selectedAgentType = m_AgentTypes.GetArrayElementAtIndex(m_AgentTypeList.index);
|
||||
|
||||
SerializedProperty radiusProp = selectedAgentType.FindPropertyRelative("agentRadius");
|
||||
SerializedProperty heightProp = selectedAgentType.FindPropertyRelative("agentHeight");
|
||||
SerializedProperty stepHeightProp = selectedAgentType.FindPropertyRelative("agentClimb");
|
||||
SerializedProperty maxSlopeProp = selectedAgentType.FindPropertyRelative("agentSlope");
|
||||
SerializedProperty ledgeDropHeightProp = selectedAgentType.FindPropertyRelative("ledgeDropHeight");
|
||||
SerializedProperty jumpDistanceProp = selectedAgentType.FindPropertyRelative("maxJumpAcrossDistance");
|
||||
|
||||
const float kDiagramHeight = 120.0f;
|
||||
Rect agentDiagramRect = EditorGUILayout.GetControlRect(false, kDiagramHeight);
|
||||
NavMeshEditorHelpers.DrawAgentDiagram(agentDiagramRect, radiusProp.floatValue, heightProp.floatValue, stepHeightProp.floatValue, maxSlopeProp.floatValue);
|
||||
|
||||
EditorGUILayout.PropertyField(nameProp, EditorGUIUtility.TrTempContent("Name"));
|
||||
EditorGUILayout.PropertyField(radiusProp, EditorGUIUtility.TrTempContent("Radius"));
|
||||
EditorGUILayout.PropertyField(heightProp, EditorGUIUtility.TrTempContent("Height"));
|
||||
EditorGUILayout.PropertyField(stepHeightProp, EditorGUIUtility.TrTempContent("Step Height"));
|
||||
|
||||
const float kMaxSlopeAngle = 60.0f;
|
||||
EditorGUILayout.Slider(maxSlopeProp, 0.0f, kMaxSlopeAngle, EditorGUIUtility.TrTextContent("Max Slope"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(EditorGUIUtility.TrTempContent("Generated Links"), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(ledgeDropHeightProp, EditorGUIUtility.TrTempContent("Drop Height"));
|
||||
EditorGUILayout.PropertyField(jumpDistanceProp, EditorGUIUtility.TrTempContent("Jump Distance"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_NavMeshProjectSettingsObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e86a4b8c5b2b8425b9a3a007619c3a88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Unity.AI.Navigation.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:8c4dd21966739024fbd72155091d199e",
|
||||
"GUID:c57630adab7a340ec94f10e4fcac12f1"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "Unity",
|
||||
"expression": "6000.0.22",
|
||||
"define": "OVERLAY_REFRESH_API"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86c9d8e67265f41469be06142c397d17
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aaa4efef82a9346dba667d74ff3d5075
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.AI.Navigation.Editor.Tests")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebc7acf85aca94da2a3cc5c2f5236c45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Unity.AI.Navigation.Editor.Converter;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
internal class NavMeshSceneConverter : SystemConverter
|
||||
{
|
||||
public override string name => "NavMesh Scene Converter";
|
||||
public override string info => "Reassigns the legacy baked NavMesh to a NavMeshSurface on a game object named 'Navigation'.\nAdds a NavMeshModifier component to each game object marked as Navigation Static.";
|
||||
public override Type container => typeof(NavigationConverterContainer);
|
||||
|
||||
List<string> m_AssetsToConvert = new List<string>();
|
||||
|
||||
public override void OnInitialize(InitializeConverterContext context, Action callback)
|
||||
{
|
||||
string[] allAssetPaths = AssetDatabase.GetAllAssetPaths();
|
||||
|
||||
foreach (string path in allAssetPaths)
|
||||
{
|
||||
if (NavMeshUpdaterUtility.IsSceneReferencingLegacyNavMesh(path))
|
||||
{
|
||||
ConverterItemDescriptor desc = new ConverterItemDescriptor()
|
||||
{
|
||||
name = Path.GetFileNameWithoutExtension(path),
|
||||
info = path,
|
||||
warningMessage = String.Empty,
|
||||
};
|
||||
|
||||
m_AssetsToConvert.Add(path);
|
||||
context.AddAssetToConvert(desc);
|
||||
}
|
||||
}
|
||||
|
||||
callback.Invoke();
|
||||
}
|
||||
|
||||
public override void OnRun(ref RunItemContext context)
|
||||
{
|
||||
var items = context.items;
|
||||
for (var i = 0; i < items.Length; ++i)
|
||||
{
|
||||
var success = NavMeshUpdaterUtility.ConvertScene(items[i].descriptor.info);
|
||||
if (!success)
|
||||
{
|
||||
var index = items[i].index;
|
||||
context.didFail[index] = true;
|
||||
context.info[index] = "Failed to convert scene.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5747e6c564aaa4e468d457a54f2e508d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
internal static class NavMeshUpdaterUtility
|
||||
{
|
||||
const string k_NavMeshSettingsPropertyPath = "NavMeshSettings";
|
||||
const string k_NavMeshDataPropertyPath = "m_NavMeshData";
|
||||
const string k_EmptyFileIdSerialization = "{fileID: 0}";
|
||||
|
||||
const string k_DefaultNavMeshSurfaceName = "Navigation";
|
||||
|
||||
const int k_WalkableAreaId = 0;
|
||||
|
||||
public static bool ConvertScene(string path)
|
||||
{
|
||||
Scene previousActiveScene = SceneManager.GetActiveScene();
|
||||
|
||||
OpenAndSetActiveScene(path, out Scene convertedScene, out bool alreadyOpened);
|
||||
|
||||
// Retrieve the legacy NavMesh data from the active scene
|
||||
#pragma warning disable CS0618 // UnityEditor.AI.NavMeshBuilder is necessary in this implementation
|
||||
var settingObject = new SerializedObject(UnityEditor.AI.NavMeshBuilder.navMeshSettingsObject);
|
||||
#pragma warning restore CS0618
|
||||
var navMeshDataProperty = settingObject.FindProperty(k_NavMeshDataPropertyPath);
|
||||
var navMeshData = navMeshDataProperty.objectReferenceValue as NavMeshData;
|
||||
|
||||
if (navMeshData == null)
|
||||
{
|
||||
Debug.LogWarning("The NavMesh asset referenced in the scene is missing or corrupted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert static Navigation Flags with a NavMeshModifier
|
||||
GameObject[] rootObjects = convertedScene.GetRootGameObjects();
|
||||
var sceneObjects = SceneModeUtility.GetObjects(rootObjects, true);
|
||||
foreach (GameObject go in sceneObjects)
|
||||
ConvertStaticValues(go);
|
||||
|
||||
// Create a global NavMeshSurface and copy legacy NavMesh settings
|
||||
var surfaceObject = new GameObject(k_DefaultNavMeshSurfaceName, typeof(NavMeshSurface));
|
||||
var navMeshSurface = surfaceObject.GetComponent<NavMeshSurface>();
|
||||
|
||||
navMeshSurface.navMeshData = navMeshData;
|
||||
|
||||
NavMeshBuildSettings settings = navMeshData.buildSettings;
|
||||
navMeshSurface.collectObjects = CollectObjects.MarkedWithModifier;
|
||||
navMeshSurface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
|
||||
navMeshSurface.overrideVoxelSize = settings.overrideVoxelSize;
|
||||
navMeshSurface.voxelSize = settings.voxelSize;
|
||||
navMeshSurface.overrideTileSize = settings.overrideTileSize;
|
||||
navMeshSurface.tileSize = settings.tileSize;
|
||||
navMeshSurface.minRegionArea = settings.minRegionArea;
|
||||
navMeshSurface.buildHeightMesh = settings.buildHeightMesh;
|
||||
|
||||
// Remove NavMeshData reference from the scene
|
||||
navMeshDataProperty.objectReferenceValue = null;
|
||||
settingObject.ApplyModifiedProperties();
|
||||
|
||||
// Rename NavMesh asset
|
||||
var assetPath = AssetDatabase.GetAssetPath(navMeshData);
|
||||
AssetDatabase.RenameAsset(assetPath, "NavMesh-" + navMeshSurface.name + ".asset");
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
EditorSceneManager.SaveScene(convertedScene);
|
||||
|
||||
navMeshSurface.AddData();
|
||||
|
||||
if (!alreadyOpened)
|
||||
EditorSceneManager.CloseScene(convertedScene, true);
|
||||
|
||||
SceneManager.SetActiveScene(previousActiveScene);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void OpenAndSetActiveScene(string path, out Scene scene, out bool alreadyOpened)
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
scene = SceneManager.GetSceneAt(i);
|
||||
if (!scene.isLoaded)
|
||||
continue;
|
||||
|
||||
if (path == scene.path)
|
||||
{
|
||||
if (SceneManager.GetActiveScene() != scene)
|
||||
SceneManager.SetActiveScene(scene);
|
||||
|
||||
alreadyOpened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
|
||||
SceneManager.SetActiveScene(scene);
|
||||
alreadyOpened = false;
|
||||
}
|
||||
|
||||
private static void ConvertStaticValues(GameObject go)
|
||||
{
|
||||
// Disable CS0618 warning about StaticEditorFlags.NavigationStatic and StaticEditorFlags.OffMeshLinkGeneration being deprecated
|
||||
#pragma warning disable 618
|
||||
var staticFlags = GameObjectUtility.GetStaticEditorFlags(go);
|
||||
if ((staticFlags & StaticEditorFlags.NavigationStatic) != 0)
|
||||
{
|
||||
NavMeshModifier modifier = go.AddComponent<NavMeshModifier>();
|
||||
modifier.area = GameObjectUtility.GetNavMeshArea(go);
|
||||
if (modifier.area != k_WalkableAreaId)
|
||||
modifier.overrideArea = true;
|
||||
|
||||
if ((staticFlags & StaticEditorFlags.OffMeshLinkGeneration) != 0)
|
||||
{
|
||||
modifier.overrideGenerateLinks = true;
|
||||
modifier.generateLinks = true;
|
||||
}
|
||||
|
||||
staticFlags &= ~StaticEditorFlags.NavigationStatic;
|
||||
GameObjectUtility.SetStaticEditorFlags(go, staticFlags);
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
internal static bool IsSceneReferencingLegacyNavMesh(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
||||
if (path.StartsWith("Packages"))
|
||||
return false;
|
||||
|
||||
if (!path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
using (StreamReader file = File.OpenText(path))
|
||||
{
|
||||
string line;
|
||||
bool skipUntilSettings = true;
|
||||
while ((line = file.ReadLine()) != null)
|
||||
{
|
||||
if (skipUntilSettings)
|
||||
{
|
||||
if (line.Contains($"{k_NavMeshSettingsPropertyPath}:"))
|
||||
skipUntilSettings = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.Contains($"{k_NavMeshDataPropertyPath}:"))
|
||||
{
|
||||
return !line.Contains(k_EmptyFileIdSerialization);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7df3797ad5e64406fb77aae77b5d42dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using Unity.AI.Navigation.Editor.Converter;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
internal sealed class NavigationConverterContainer : SystemConverterContainer
|
||||
{
|
||||
public override string name => "Navigation Updater";
|
||||
public override string info => "* Converts scenes baked with the built-in NavMesh to the component-based version.\n" +
|
||||
"* Replaces Navigation Static flags with NavMeshModifier components.\n" +
|
||||
"* Turns OffMesh Link components into NavMesh Link components and preserves their properties.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14f573b87e1ac42568de3d97da3a0f03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using Unity.AI.Navigation.Editor.Converter;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
internal static class NavigationUpdaterEditor
|
||||
{
|
||||
[MenuItem("Window/AI/Navigation Updater", false, 50)]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var wnd = EditorWindow.GetWindow<SystemConvertersEditor>();
|
||||
wnd.titleContent = new GUIContent("Navigation Updater");
|
||||
wnd.DontSaveToLayout(wnd);
|
||||
wnd.maxSize = new Vector2(650f, 4000f);
|
||||
wnd.minSize = new Vector2(650f, 400f);
|
||||
wnd.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582e6909cfc304d93b58ee91d453d156
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Unity.AI.Navigation.Editor.Converter;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
internal sealed class OffMeshLinkConverter : SystemConverter
|
||||
{
|
||||
public override string name => "OffMesh Link Converter";
|
||||
public override string info => "Creates NavMesh Link components that match and replace existing OffMesh Link components. \n" +
|
||||
"Ensure the selected scene or prefab files are writable prior to running the Converter.";
|
||||
public override Type container => typeof(NavigationConverterContainer);
|
||||
|
||||
public override void OnInitialize(InitializeConverterContext context, Action callback)
|
||||
{
|
||||
var objectsToConvert = OffMeshLinkUpdaterUtility.FindObjectsToConvert();
|
||||
foreach (var guid in objectsToConvert)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var desc = new ConverterItemDescriptor()
|
||||
{
|
||||
name = Path.GetFileNameWithoutExtension(path),
|
||||
info = path,
|
||||
additionalData = guid
|
||||
};
|
||||
context.AddAssetToConvert(desc);
|
||||
}
|
||||
|
||||
callback.Invoke();
|
||||
}
|
||||
|
||||
public override void OnRun(ref RunItemContext context)
|
||||
{
|
||||
var convertList = new List<string>(context.items.Length);
|
||||
for (var i = 0; i < context.items.Length; ++i)
|
||||
{
|
||||
var guid = context.items[i].descriptor.additionalData;
|
||||
convertList.Add(guid);
|
||||
}
|
||||
OffMeshLinkUpdaterUtility.Convert(convertList, out var failedConversions);
|
||||
|
||||
foreach (var conversionItem in failedConversions)
|
||||
{
|
||||
var index = conversionItem.itemIndex;
|
||||
context.didFail[index] = true;
|
||||
context.info[index] = conversionItem.failureMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 202accd362464ac0b604bb6d336eb8aa
|
||||
timeCreated: 1710163840
|
||||
@@ -0,0 +1,523 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.AI.Navigation.Updater
|
||||
{
|
||||
#pragma warning disable 618
|
||||
internal static class OffMeshLinkUpdaterUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure holding the information of failed conversions.
|
||||
/// </summary>
|
||||
public struct FailedConversion
|
||||
{
|
||||
public int itemIndex;
|
||||
public string failureMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all prefabs and scenes that contain OffMeshLink components.
|
||||
/// This method also finds Prefab Variants which has removed OffMeshLink components.
|
||||
/// </summary>
|
||||
/// <param name="searchInFolders">Folders to search for prefabs and scenes. If null, the whole project is searched.</param>
|
||||
/// <returns>List of asset GUIDs to convert.</returns>
|
||||
public static List<string> FindObjectsToConvert(string[] searchInFolders = null)
|
||||
{
|
||||
var prefabGuids = AssetDatabase.FindAssets("t:Prefab", searchInFolders);
|
||||
var objectsToConvert = new HashSet<string>();
|
||||
foreach (var guid in prefabGuids)
|
||||
{
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
|
||||
if (prefab.GetComponentsInChildren<OffMeshLink>(true).Length > 0)
|
||||
objectsToConvert.Add(guid);
|
||||
var isPrefabVariant = PrefabUtility.IsPartOfVariantPrefab(prefab);
|
||||
if (isPrefabVariant)
|
||||
{
|
||||
var removedComponents = PrefabUtility.GetRemovedComponents(prefab);
|
||||
foreach (var removedComponent in removedComponents)
|
||||
{
|
||||
if (removedComponent.assetComponent is OffMeshLink)
|
||||
{
|
||||
objectsToConvert.Add(guid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sceneGuids = AssetDatabase.FindAssets("t:Scene", searchInFolders);
|
||||
foreach (var guid in sceneGuids)
|
||||
{
|
||||
var scenePath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
|
||||
var rootGameObjects = scene.GetRootGameObjects();
|
||||
foreach (var rootGameObject in rootGameObjects)
|
||||
{
|
||||
var offMeshLinksInHierarchy = rootGameObject.GetComponentsInChildren<OffMeshLink>(true);
|
||||
if (offMeshLinksInHierarchy.Length == 0)
|
||||
continue;
|
||||
|
||||
var offMeshLinks = FindAllOffMeshLinksInHierarchy(rootGameObject);
|
||||
if (offMeshLinks.Count > 0)
|
||||
{
|
||||
objectsToConvert.Add(guid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseScene(scene);
|
||||
}
|
||||
|
||||
var returnList = new List<string>(objectsToConvert);
|
||||
return returnList;
|
||||
}
|
||||
|
||||
static void CloseScene(Scene scene)
|
||||
{
|
||||
// EditorSceneManager does not support closing the last scene, so we open a new empty scene instead.
|
||||
if (EditorSceneManager.loadedSceneCount == 1)
|
||||
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
|
||||
else
|
||||
EditorSceneManager.CloseScene(scene, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert all objects in the list to use NavMeshLink instead of OffMeshLink.
|
||||
/// </summary>
|
||||
/// <param name="objectsToConvert">List of asset GUIDs to convert.</param>
|
||||
/// <param name="failedConversions">List of failed conversions.</param>
|
||||
public static void Convert(List<string> objectsToConvert, out List<FailedConversion> failedConversions)
|
||||
{
|
||||
failedConversions = new List<FailedConversion>();
|
||||
var offMeshLinkToNavMeshLink = new Dictionary<OffMeshLink, NavMeshLink>();
|
||||
var failedToConvert = new HashSet<string>();
|
||||
|
||||
// Initial sorting of objects to convert.
|
||||
// We want to deal with source prefabs first, then their variants, so that the NavMeshLink components are created in the correct order.
|
||||
SortAssetsToConvert(objectsToConvert);
|
||||
|
||||
// First convert, create NavMeshLink with OffMeshLink values
|
||||
foreach (var guid in objectsToConvert)
|
||||
{
|
||||
var pathToObject = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!DoesAssetExistOnDisk(pathToObject))
|
||||
{
|
||||
failedToConvert.Add(guid);
|
||||
failedConversions.Add(new FailedConversion
|
||||
{
|
||||
itemIndex = objectsToConvert.IndexOf(guid),
|
||||
failureMessage = $"Cannot find the asset at path {pathToObject}. Please make sure the file exists."
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (!CanWriteToAsset(pathToObject))
|
||||
{
|
||||
failedToConvert.Add(guid);
|
||||
failedConversions.Add(new FailedConversion
|
||||
{
|
||||
itemIndex = objectsToConvert.IndexOf(guid),
|
||||
failureMessage = $"Cannot write to the asset at path {pathToObject}. Please make sure the file is not read-only."
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryGetPrefabFromPath(pathToObject, out var prefab))
|
||||
ConvertPrefab(prefab, offMeshLinkToNavMeshLink);
|
||||
else if (TryGetSceneFromPath(pathToObject, out var scene))
|
||||
{
|
||||
ConvertScene(scene, ref offMeshLinkToNavMeshLink);
|
||||
CloseScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove failed conversions
|
||||
foreach (var guid in failedToConvert)
|
||||
objectsToConvert.Remove(guid);
|
||||
|
||||
// Second convert, apply prefab overrides to NavMeshLink
|
||||
foreach (var guid in objectsToConvert)
|
||||
{
|
||||
var pathToObject = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (TryGetPrefabFromPath(pathToObject, out var prefab))
|
||||
{
|
||||
ApplyOverrideDataToPrefab(prefab, offMeshLinkToNavMeshLink);
|
||||
SyncOverriddenRemovalsOfComponents(prefab, offMeshLinkToNavMeshLink);
|
||||
}
|
||||
else if (TryGetSceneFromPath(pathToObject, out var scene))
|
||||
{
|
||||
ApplyOverrideDataToScene(scene, offMeshLinkToNavMeshLink);
|
||||
CloseScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove OffMeshLinks from the objects
|
||||
foreach (var guid in objectsToConvert)
|
||||
{
|
||||
var pathToObject = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (TryGetPrefabFromPath(pathToObject, out var prefab))
|
||||
{
|
||||
var offMeshLinks = prefab.GetComponentsInChildren<OffMeshLink>(true);
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
Object.DestroyImmediate(offMeshLink, true);
|
||||
|
||||
// Since we are removing components in source prefabs, we have to "revert removed components"
|
||||
// on prefab instances, so that we don't store the removal data in the meta files.
|
||||
if (PrefabUtility.IsAnyPrefabInstanceRoot(prefab))
|
||||
RevertRemovedComponent(prefab);
|
||||
|
||||
PrefabUtility.SavePrefabAsset(prefab);
|
||||
}
|
||||
else if (TryGetSceneFromPath(pathToObject, out var scene))
|
||||
{
|
||||
var rootGameObjects = scene.GetRootGameObjects();
|
||||
foreach (var rootGameObject in rootGameObjects)
|
||||
{
|
||||
var offMeshLinks = rootGameObject.GetComponentsInChildren<OffMeshLink>(true);
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
Object.DestroyImmediate(offMeshLink, true);
|
||||
}
|
||||
EditorSceneManager.SaveScene(scene);
|
||||
CloseScene(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the list of objects to convert so that source prefabs are first.
|
||||
/// </summary>
|
||||
/// <param name="objectsToConvert">List of asset GUIDs to convert.</param>
|
||||
static void SortAssetsToConvert(List<string> objectsToConvert)
|
||||
{
|
||||
var sortedAssetGuids = new List<string>(objectsToConvert.Count);
|
||||
for (var i = 0; i < objectsToConvert.Count; i++)
|
||||
{
|
||||
var assetGuid = objectsToConvert[i];
|
||||
if (sortedAssetGuids.Contains(assetGuid))
|
||||
continue;
|
||||
|
||||
var prefabGameObject = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(assetGuid));
|
||||
if (prefabGameObject == null)
|
||||
sortedAssetGuids.Add(assetGuid);
|
||||
else
|
||||
SortPrefabsToConvert(sortedAssetGuids, prefabGameObject, assetGuid);
|
||||
}
|
||||
|
||||
objectsToConvert.Clear();
|
||||
objectsToConvert.AddRange(sortedAssetGuids);
|
||||
}
|
||||
|
||||
static void SortPrefabsToConvert(List<string> objectsToConvert, GameObject prefabRoot, string prefabRootGuid)
|
||||
{
|
||||
if (objectsToConvert.Contains(prefabRootGuid))
|
||||
return;
|
||||
|
||||
var source = PrefabUtility.GetCorrespondingObjectFromSource(prefabRoot);
|
||||
if (source == null)
|
||||
{
|
||||
objectsToConvert.Add(prefabRootGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(source));
|
||||
SortPrefabsToConvert(objectsToConvert, source, sourceGuid);
|
||||
objectsToConvert.Add(prefabRootGuid);
|
||||
}
|
||||
|
||||
static void RevertRemovedComponent(GameObject prefab)
|
||||
{
|
||||
var removedComponents = PrefabUtility.GetRemovedComponents(prefab);
|
||||
if (removedComponents == null)
|
||||
return;
|
||||
|
||||
foreach (var removedComponent in removedComponents)
|
||||
{
|
||||
if (removedComponent.assetComponent is OffMeshLink)
|
||||
PrefabUtility.RevertRemovedComponent(prefab, removedComponent.assetComponent, InteractionMode.AutomatedAction);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryGetPrefabFromPath(string path, out GameObject prefab)
|
||||
{
|
||||
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
return prefab != null && PrefabUtility.IsPartOfPrefabAsset(prefab);
|
||||
}
|
||||
|
||||
static bool TryGetSceneFromPath(string path, out Scene scene)
|
||||
{
|
||||
var sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
|
||||
if (sceneAsset != null)
|
||||
{
|
||||
scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
|
||||
return true;
|
||||
}
|
||||
scene = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static List<OffMeshLink> FindAllOffMeshLinksInHierarchy(GameObject rootGameObject)
|
||||
{
|
||||
var arr = rootGameObject.GetComponentsInChildren<OffMeshLink>(true);
|
||||
var offMeshLinks = new List<OffMeshLink>(arr);
|
||||
for (var i = offMeshLinks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var gameObject = offMeshLinks[i].gameObject;
|
||||
if (PrefabUtility.IsAnyPrefabInstanceRoot(gameObject) && gameObject != rootGameObject)
|
||||
offMeshLinks.RemoveAt(i);
|
||||
}
|
||||
return offMeshLinks;
|
||||
}
|
||||
|
||||
static void ConvertPrefab(GameObject prefabRoot, Dictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var offMeshLinks = FindAllOffMeshLinksInHierarchy(prefabRoot);
|
||||
var updatedPrefab = false;
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
{
|
||||
var gameObject = offMeshLink.gameObject;
|
||||
var isPrefabVariant = PrefabUtility.IsPartOfVariantPrefab(gameObject);
|
||||
// If the prefab is a variant and the OffMeshLink is not an override,
|
||||
// only store the variant's OffMeshLink and NavMeshLink pair, and skip the rest of the conversion.
|
||||
if (isPrefabVariant && !PrefabUtility.IsAddedComponentOverride(offMeshLink))
|
||||
{
|
||||
var sourceOml = PrefabUtility.GetCorrespondingObjectFromSource(offMeshLink);
|
||||
var sourceNML = offMeshLinkToNavMeshLink[sourceOml];
|
||||
var variantNML = GetCorrespondingVariantComponent(sourceNML, prefabRoot);
|
||||
offMeshLinkToNavMeshLink.Add(offMeshLink, variantNML);
|
||||
continue;
|
||||
}
|
||||
|
||||
var navMeshLink = gameObject.AddComponent<NavMeshLink>();
|
||||
offMeshLinkToNavMeshLink.Add(offMeshLink, navMeshLink);
|
||||
|
||||
// If the OffMeshLink is an override (created by a Prefab Variant), record the new NavMeshLink as an override.
|
||||
if (isPrefabVariant)
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
|
||||
|
||||
CopyValues(offMeshLink, navMeshLink);
|
||||
updatedPrefab = true;
|
||||
}
|
||||
|
||||
if (updatedPrefab)
|
||||
PrefabUtility.SavePrefabAsset(prefabRoot);
|
||||
}
|
||||
|
||||
static void ConvertScene(Scene scene, ref Dictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var rootGameObjects = scene.GetRootGameObjects();
|
||||
foreach (var rootGameObject in rootGameObjects)
|
||||
{
|
||||
// Skip prefab instances, they are taken care of in the ConvertPrefab method.
|
||||
if (PrefabUtility.IsAnyPrefabInstanceRoot(rootGameObject))
|
||||
continue;
|
||||
|
||||
var offMeshLinks = FindAllOffMeshLinksInHierarchy(rootGameObject);
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
{
|
||||
var navMeshLink = offMeshLink.gameObject.AddComponent<NavMeshLink>();
|
||||
offMeshLinkToNavMeshLink.Add(offMeshLink, navMeshLink);
|
||||
|
||||
CopyValues(offMeshLink, navMeshLink);
|
||||
}
|
||||
}
|
||||
|
||||
EditorSceneManager.SaveScene(scene);
|
||||
}
|
||||
|
||||
static bool CanWriteToAsset(string filePath)
|
||||
{
|
||||
var fileInfo = new System.IO.FileInfo(filePath);
|
||||
return !fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReadOnly);
|
||||
}
|
||||
|
||||
static bool DoesAssetExistOnDisk(string filePath)
|
||||
{
|
||||
return !string.IsNullOrEmpty(filePath) && System.IO.File.Exists(filePath);
|
||||
}
|
||||
|
||||
static void CopyValues(OffMeshLink offMeshLink, NavMeshLink navMeshLink)
|
||||
{
|
||||
navMeshLink.activated = offMeshLink.activated;
|
||||
navMeshLink.autoUpdate = offMeshLink.autoUpdatePositions;
|
||||
navMeshLink.bidirectional = offMeshLink.biDirectional;
|
||||
navMeshLink.costModifier = offMeshLink.costOverride;
|
||||
navMeshLink.startTransform = offMeshLink.startTransform;
|
||||
navMeshLink.endTransform = offMeshLink.endTransform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfers any existing override data from OffMeshLink components to NavMeshLink components.
|
||||
/// </summary>
|
||||
/// <param name="outerPrefabRoot">The out-most prefab root</param>
|
||||
/// <param name="offMeshLinkToNavMeshLink">Dictionary storing the OffMeshLink and NavMeshLink pairs</param>
|
||||
static void ApplyOverrideDataToPrefab(GameObject outerPrefabRoot, Dictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var didUpdate = false;
|
||||
var updatedRoots = new HashSet<GameObject>();
|
||||
var offMeshLinks = outerPrefabRoot.GetComponentsInChildren<OffMeshLink>(true);
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
{
|
||||
// Find the prefab root and check if we have already updated it.
|
||||
// A null root means that the root is the outerPrefabRoot.
|
||||
var rootGameObject = PrefabUtility.GetNearestPrefabInstanceRoot(offMeshLink.gameObject);
|
||||
rootGameObject = rootGameObject == null ? offMeshLink.gameObject : rootGameObject;
|
||||
if (updatedRoots.Contains(rootGameObject))
|
||||
continue;
|
||||
|
||||
var propertyModifications = PrefabUtility.GetPropertyModifications(rootGameObject);
|
||||
if (propertyModifications == null)
|
||||
continue;
|
||||
|
||||
UpdatePropertyModifications(ref propertyModifications, offMeshLinkToNavMeshLink);
|
||||
PrefabUtility.SetPropertyModifications(rootGameObject, propertyModifications);
|
||||
|
||||
updatedRoots.Add(rootGameObject);
|
||||
didUpdate = true;
|
||||
}
|
||||
|
||||
if (didUpdate)
|
||||
PrefabUtility.SavePrefabAsset(outerPrefabRoot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the NavMeshLink components that correspond to OffMeshLink components that were removed from the variant prefab.
|
||||
/// </summary>
|
||||
static void SyncOverriddenRemovalsOfComponents(GameObject prefabRoot, IReadOnlyDictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var isPrefabVariant = PrefabUtility.IsPartOfVariantPrefab(prefabRoot);
|
||||
if (!isPrefabVariant)
|
||||
return;
|
||||
|
||||
var removedComponents = PrefabUtility.GetRemovedComponents(prefabRoot);
|
||||
var variantNavMeshLinks = prefabRoot.GetComponents<NavMeshLink>();
|
||||
foreach (var removedComponent in removedComponents)
|
||||
{
|
||||
if (removedComponent.assetComponent is not OffMeshLink removedOffMeshLink)
|
||||
continue;
|
||||
|
||||
var sourceNavMeshLink = offMeshLinkToNavMeshLink[removedOffMeshLink];
|
||||
foreach(var variantComponent in variantNavMeshLinks)
|
||||
{
|
||||
var correspondingComponent = PrefabUtility.GetCorrespondingObjectFromSource(variantComponent);
|
||||
if (correspondingComponent == sourceNavMeshLink)
|
||||
{
|
||||
Object.DestroyImmediate(variantComponent, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(prefabRoot);
|
||||
PrefabUtility.SavePrefabAsset(prefabRoot);
|
||||
}
|
||||
|
||||
static void ApplyOverrideDataToScene(Scene scene, Dictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var rootGameObjects = scene.GetRootGameObjects();
|
||||
var updatedRoots = new HashSet<GameObject>();
|
||||
foreach (var rootGameObject in rootGameObjects)
|
||||
{
|
||||
var offMeshLinks = rootGameObject.GetComponentsInChildren<OffMeshLink>();
|
||||
foreach (var offMeshLink in offMeshLinks)
|
||||
{
|
||||
// Non-prefab instances cannot have overrides, so continue.
|
||||
if (!PrefabUtility.IsPartOfAnyPrefab(offMeshLink))
|
||||
continue;
|
||||
// Find the prefab root and check if it contains any overrides
|
||||
var prefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(offMeshLink.gameObject);
|
||||
if (!PrefabUtility.HasPrefabInstanceAnyOverrides(prefabRoot, false) && !updatedRoots.Contains(prefabRoot))
|
||||
continue;
|
||||
|
||||
var propertyModifications = PrefabUtility.GetPropertyModifications(prefabRoot);
|
||||
|
||||
UpdatePropertyModifications(ref propertyModifications, offMeshLinkToNavMeshLink);
|
||||
PrefabUtility.SetPropertyModifications(prefabRoot, propertyModifications);
|
||||
updatedRoots.Add(prefabRoot);
|
||||
}
|
||||
}
|
||||
|
||||
EditorSceneManager.SaveScene(scene);
|
||||
}
|
||||
|
||||
static void UpdatePropertyModifications(ref PropertyModification[] propertyModifications, Dictionary<OffMeshLink, NavMeshLink> offMeshLinkToNavMeshLink)
|
||||
{
|
||||
var updatedModifications = new List<PropertyModification>(propertyModifications);
|
||||
for (var i = 0; i < propertyModifications.Length; ++i)
|
||||
{
|
||||
var modification = propertyModifications[i];
|
||||
// Skip if the target is not an OffMeshLink
|
||||
if (modification.target is not OffMeshLink oml)
|
||||
{
|
||||
updatedModifications.Add(modification);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the NavMeshLink that corresponds to the OffMeshLink.
|
||||
var navMeshLink = offMeshLinkToNavMeshLink[oml];
|
||||
var newMod = new PropertyModification
|
||||
{
|
||||
target = navMeshLink,
|
||||
value = modification.value
|
||||
};
|
||||
switch (modification.propertyPath)
|
||||
{
|
||||
case "m_AutoUpdatePositions":
|
||||
newMod.propertyPath = "m_AutoUpdatePosition";
|
||||
break;
|
||||
case "m_CostOverride":
|
||||
newMod.propertyPath = "m_CostModifier";
|
||||
if (float.TryParse(newMod.value, out var floatVal))
|
||||
{
|
||||
if (floatVal > 0)
|
||||
{
|
||||
var overrideMod = new PropertyModification()
|
||||
{
|
||||
target = navMeshLink,
|
||||
propertyPath = "m_IsOverridingCost",
|
||||
value = "1"
|
||||
};
|
||||
updatedModifications.Add(overrideMod);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "m_BiDirectional":
|
||||
newMod.propertyPath = "m_Bidirectional";
|
||||
break;
|
||||
case "m_Start":
|
||||
newMod.propertyPath = "m_StartTransform";
|
||||
break;
|
||||
case "m_End":
|
||||
newMod.propertyPath = "m_EndTransform";
|
||||
break;
|
||||
case "m_Activated":
|
||||
newMod.propertyPath = modification.propertyPath;
|
||||
break;
|
||||
}
|
||||
updatedModifications.Add(newMod);
|
||||
}
|
||||
propertyModifications = updatedModifications.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the corresponding variant component of a source prefab component.
|
||||
/// </summary>
|
||||
/// <param name="component">The source prefab component</param>
|
||||
/// <param name="variantPrefab">The variant prefab</param>
|
||||
/// <typeparam name="TComponent">The type of component to search for</typeparam>
|
||||
/// <returns>Returns the corresponding component on the variant of the source prefab component, if found, or null.</returns>
|
||||
static TComponent GetCorrespondingVariantComponent<TComponent>(TComponent component, GameObject variantPrefab) where TComponent : Component
|
||||
{
|
||||
var variantComponents = variantPrefab.GetComponents(component.GetType());
|
||||
foreach (var variantComponent in variantComponents)
|
||||
{
|
||||
var correspondingComponent = PrefabUtility.GetCorrespondingObjectFromSource(variantComponent);
|
||||
if (correspondingComponent == component)
|
||||
return variantComponent as TComponent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2b338e146db4a408444f723bd4a2c77
|
||||
timeCreated: 1710247263
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Unity.AI.Navigation.Updater",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:8c4dd21966739024fbd72155091d199e",
|
||||
"GUID:86c9d8e67265f41469be06142c397d17",
|
||||
"GUID:c57630adab7a340ec94f10e4fcac12f1"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1664e92176d434ccd902c1705fefe682
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user