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,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