first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:36:52 +07:00
commit 6f2eafa33c
14093 changed files with 1253472 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.AI.Navigation.Editor.Tests")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ebc7acf85aca94da2a3cc5c2f5236c45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.";
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5747e6c564aaa4e468d457a54f2e508d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7df3797ad5e64406fb77aae77b5d42dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14f573b87e1ac42568de3d97da3a0f03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 582e6909cfc304d93b58ee91d453d156
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 202accd362464ac0b604bb6d336eb8aa
timeCreated: 1710163840

View File

@@ -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
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e2b338e146db4a408444f723bd4a2c77
timeCreated: 1710247263

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1664e92176d434ccd902c1705fefe682
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: