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 @@
fileFormatVersion: 2
guid: 585e4d2e85b344909fd03397aa977b50
timeCreated: 1641493617

View File

@@ -0,0 +1,118 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using Unity.AI.Navigation.Updater;
#pragma warning disable CS0618 // UnityEditor.AI.NavMeshBuilder is necessary in this implementation
using NavMeshBuilder = UnityEditor.AI.NavMeshBuilder;
#pragma warning restore CS0618
namespace Unity.AI.Navigation.Editor.Tests
{
[Description("Tests suite related to the systems used to convert editor data from the legacy NavMesh systems to the modern component-based navigation extension")]
class NavMeshConverterTests
{
const string k_RootFolder = "Assets";
const string k_TestFolder = "ConverterTests";
const string k_TestFolderPath = k_RootFolder + "/" + k_TestFolder;
const string k_TestScenePath = k_TestFolderPath + "/ConverterTestsScene.unity";
const string k_BuildHeightMeshPropertyName = "m_BuildSettings.buildHeightMesh";
bool m_BuildHeightMeshPreviousValue;
[OneTimeSetUp]
public void OneTimeSetUp()
{
if (!AssetDatabase.IsValidFolder(k_TestFolderPath))
AssetDatabase.CreateFolder(k_RootFolder, k_TestFolder);
Assume.That(AssetDatabase.IsValidFolder(k_TestFolderPath));
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var planeGameObject = GameObject.CreatePrimitive(PrimitiveType.Plane);
#pragma warning disable 618
GameObjectUtility.SetStaticEditorFlags(planeGameObject, StaticEditorFlags.NavigationStatic);
#pragma warning restore 618
EditorSceneManager.SaveScene(scene, k_TestScenePath);
// Enable desired build settings (build HeightMesh)
var settingsObject = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
Assume.That(settingsObject, Is.Not.Null, "Unable to get the build settings object");
var buildHeightMeshProperty = settingsObject.FindProperty(k_BuildHeightMeshPropertyName);
Assume.That(buildHeightMeshProperty, Is.Not.Null, "Unable to get the buildHeightMesh property from the build settings object");
m_BuildHeightMeshPreviousValue = buildHeightMeshProperty.boolValue;
buildHeightMeshProperty.boolValue = true;
settingsObject.ApplyModifiedProperties();
Assume.That(buildHeightMeshProperty.boolValue, Is.True, "buildHeightMesh property from the build settings object should be true");
NavMeshBuilder.BuildNavMesh();
EditorSceneManager.SaveScene(scene, k_TestScenePath);
Assume.That(NavMeshUpdaterUtility.IsSceneReferencingLegacyNavMesh(k_TestScenePath));
NavMeshUpdaterUtility.ConvertScene(k_TestScenePath);
}
[Test]
public void Converter_AfterConversion_SceneNavMeshAssetIsGone()
{
var navMeshOwnedByScene = NavMeshUpdaterUtility.IsSceneReferencingLegacyNavMesh(k_TestScenePath);
Assert.IsFalse(navMeshOwnedByScene, "Converted scene should not own a NavMesh after conversion");
}
[Test]
public void Converter_AfterConversion_NavMeshSurfaceIsPresent()
{
var surface = Object.FindAnyObjectByType<NavMeshSurface>();
Assert.IsNotNull(surface, "Unable to find a NavMesh surface, it should have been created by the conversion");
}
[Test]
public void Converter_AfterConversion_NavMeshIsPresent()
{
var sampleSuccess = NavMesh.SamplePosition(Vector3.zero, out var hit, 1.0f, NavMesh.AllAreas);
Assert.IsTrue(sampleSuccess && hit.hit, "NavMesh should still be present after conversion");
}
[Test]
public void Converter_AfterConversion_NoNavigationStaticGameObjects()
{
var gameObjects = Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None);
foreach (var gameObject in gameObjects)
{
#pragma warning disable 618
Assert.IsFalse(GameObjectUtility.AreStaticEditorFlagsSet(gameObject, StaticEditorFlags.NavigationStatic), "Objects should not be flagged as NavigationStatic after conversion");
#pragma warning restore 618
}
}
[Test]
public void Converter_AfterConversion_HeightMeshIsPresent()
{
var surface = Object.FindAnyObjectByType<NavMeshSurface>();
Assume.That(surface, Is.Not.Null, "Unable to find a NavMesh surface, it should have been created by the conversion");
Assert.IsTrue(surface.buildHeightMesh, "A scene NavMesh built with HeightMesh should be converted to a surface with the buildHeightMesh option enabled");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
if (AssetDatabase.IsValidFolder(k_TestFolderPath))
AssetDatabase.DeleteAsset(k_TestFolderPath);
// Restore build settings value
var settingsObject = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
Assume.That(settingsObject, Is.Not.Null, "Unable to get the build settings object");
var buildHeightMeshProperty = settingsObject.FindProperty(k_BuildHeightMeshPropertyName);
Assume.That(buildHeightMeshProperty, Is.Not.Null, "Unable to get the buildHeightMesh property from the build settings object");
buildHeightMeshProperty.boolValue = m_BuildHeightMeshPreviousValue;
settingsObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 726f4ec00efc48ab9ab7f5647c4c2f7a
timeCreated: 1641493654

View File

@@ -0,0 +1,870 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.AI.Navigation.Updater;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
#pragma warning disable 618
internal class OffMeshLinkConverterTests
{
string m_TestFolderName;
string m_TestFolderPath;
struct OffMeshLinkData
{
public bool activated;
public bool autoUpdatePositions;
public bool biDirectional;
public float costOverride;
public Transform startTransform;
public Transform endTransform;
}
[TearDown]
public void TearDown()
{
if (!string.IsNullOrEmpty(m_TestFolderPath) && AssetDatabase.IsValidFolder(m_TestFolderPath))
{
AssetDatabase.DeleteAsset(m_TestFolderPath);
AssetDatabase.Refresh();
}
}
[SetUp]
public void SetUp()
{
m_TestFolderName = System.IO.Path.GetRandomFileName();
AssetDatabase.CreateFolder("Assets", m_TestFolderName);
m_TestFolderPath = "Assets/" + m_TestFolderName;
}
[Test]
public void Convert_InvalidGuid_NoConversion()
{
const string invalidGuid = "InvalidGuid";
var convertList = new List<string> { invalidGuid };
OffMeshLinkUpdaterUtility.Convert(convertList, out var failedConversions);
Assert.That(failedConversions.Count, Is.EqualTo(1), "Should have failed to convert the invalid GUID.");
}
[Test]
public void FindObjectsToConvert_PrefabWithOML_ConverterFindsPrefab()
{
var omlPrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
Assume.That(omlPrefab.GetComponent<OffMeshLink>(), Is.Not.Null, "Prefab should have OffMeshLink component");
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
Assert.That(foundPrefabs.Count, Is.EqualTo(1), "Should have found 1 prefab with OffMeshLink component.");
Assert.That(GuidToPrefab(foundPrefabs[0]) == omlPrefab, "Should have found the prefab with OffMeshLink component.");
}
[Test]
public void FindObjectsToConvert_PrefabWithoutOML_ConverterDoesNotFindPrefab()
{
CreatePrefabWithComponent<SpriteRenderer>(m_TestFolderPath + "/SpriteRendererPrefab.prefab");
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
Assert.That(foundPrefabs.Count, Is.EqualTo(0), "Should not have found any prefabs with OffMeshLink component.");
}
[Test]
public void FindObjectsToConvert_ConverterLookingInEmptyFolder_NoItemsFound()
{
CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var randomFolderName = System.IO.Path.GetRandomFileName();
var randomFolderPath = "Assets/" + randomFolderName;
AssetDatabase.CreateFolder("Assets", randomFolderName);
try
{
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { randomFolderPath });
Assert.That(foundPrefabs.Count, Is.EqualTo(0), "Should not have found any prefabs with OffMeshLink component.");
}
finally
{
AssetDatabase.DeleteAsset(randomFolderPath);
}
}
[Test]
public void Convert_PrefabIsReadOnly_NoConversion()
{
var prefabPath = m_TestFolderPath + "/OffMeshLinkPrefab.prefab";
CreatePrefabWithComponent<OffMeshLink>(prefabPath);
var attributes = System.IO.File.GetAttributes(prefabPath);
System.IO.File.SetAttributes(prefabPath, attributes | System.IO.FileAttributes.ReadOnly);
var foundPrefabs = new List<string> { AssetDatabase.AssetPathToGUID(prefabPath) };
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out var failedConversions);
Assert.That(foundPrefabs.Count, Is.Zero, "Prefab should have been removed from conversion list.");
Assert.That(failedConversions.Count, Is.EqualTo(1), "Should have failed to convert the prefab.");
Assert.That(failedConversions[0].failureMessage, Is.Not.Empty, "Should have a failure message.");
}
[Test]
public void FindObjectsToConvert_PrefabWithOMLOnChild_ConverterFindsPrefab()
{
var parent = new GameObject("Parent");
var child = new GameObject("Child");
child.transform.parent = parent.transform;
child.AddComponent<OffMeshLink>();
CreatePrefabFromGameObject(parent, m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
Assert.That(foundPrefabs.Count, Is.EqualTo(1), "Should have found 1 prefab with OffMeshLink component.");
}
[Test]
public void Convert_PrefabWithOML_PrefabConverted()
{
CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefab = GuidToPrefab(foundPrefabs[0]);
Assert.That(convertedPrefab.GetComponent<OffMeshLink>(), Is.Null, "Prefab should not have OffMeshLink component after conversion.");
Assert.That(convertedPrefab.GetComponent<NavMeshLink>(), Is.Not.Null, "Prefab should have NavMeshLink component after conversion.");
}
[Test]
public void Convert_PrefabWithOMLOnChild_ChildConverted()
{
var parent = new GameObject("Parent");
var child = new GameObject("Child");
child.transform.parent = parent.transform;
child.AddComponent<OffMeshLink>();
CreatePrefabFromGameObject(parent, m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var objectsToConvert = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab") };
OffMeshLinkUpdaterUtility.Convert(objectsToConvert, out _);
var convertedPrefab = GuidToPrefab(objectsToConvert[0]);
Assert.That(convertedPrefab.transform.GetChild(0).GetComponent<OffMeshLink>(), Is.Null, "Prefab should not have OffMeshLink component after conversion.");
Assert.That(convertedPrefab.transform.GetChild(0).GetComponent<NavMeshLink>(), Is.Not.Null, "Prefab should have NavMeshLink component after conversion.");
}
[Test]
public void FindObjectsToConvert_SceneWithOML_ConverterFindsScene()
{
var scene = CreateSceneWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkScene.unity");
var gameObjects = scene.GetRootGameObjects();
var omlGameObject = Array.Find(gameObjects, x => x.TryGetComponent<OffMeshLink>(out _));
Assume.That(omlGameObject, Is.Not.Null, "A GameObject in the scene should have an OffMeshLink component.");
var foundObjects = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
Assert.That(foundObjects.Count, Is.EqualTo(1), "Should have found 1 scene with OffMeshLink component.");
}
[Test]
public void FindObjectsToConvert_SceneWithPrefabInstance_ConverterFindsCorrectNumberToConvert()
{
CreateSceneWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkScene.unity");
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
// Open the scene and instantiate the prefab
var scene = EditorSceneManager.OpenScene(m_TestFolderPath + "/OffMeshLinkScene.unity", OpenSceneMode.Additive);
PrefabUtility.InstantiatePrefab(prefab);
EditorSceneManager.SaveScene(scene);
var foundObjects = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
Assert.That(foundObjects.Count, Is.EqualTo(2), "Should have found 2 objects to convert.");
}
[Test]
public void Convert_SceneWithOML_InstanceInSceneConverted()
{
CreateSceneWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkScene.unity");
var foundObjects = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
OffMeshLinkUpdaterUtility.Convert(foundObjects, out _);
var pathToObject = AssetDatabase.GUIDToAssetPath(foundObjects[0]);
var scene = EditorSceneManager.OpenScene(pathToObject, OpenSceneMode.Additive);
var convertedGameObject = scene.GetRootGameObjects()[0];
Assert.That(convertedGameObject.transform.GetComponent<OffMeshLink>(), Is.Null, "Prefab should not have OffMeshLink component after conversion.");
Assert.That(convertedGameObject.transform.GetComponent<NavMeshLink>(), Is.Not.Null, "Prefab should have NavMeshLink component after conversion.");
}
[Test]
public void Convert_SceneIsReadOnly_NoConversion()
{
var scenePath = m_TestFolderPath + "/OffMeshLinkScene.unity";
CreateSceneWithComponent<OffMeshLink>(scenePath);
var attributes = System.IO.File.GetAttributes(scenePath);
System.IO.File.SetAttributes(scenePath, attributes | System.IO.FileAttributes.ReadOnly);
var foundItems = new List<string> { AssetDatabase.AssetPathToGUID(scenePath) };
OffMeshLinkUpdaterUtility.Convert(foundItems, out var failedConversions);
Assert.That(foundItems.Count, Is.Zero, "Scene should have been removed from conversion list.");
Assert.That(failedConversions.Count, Is.EqualTo(1), "Should have failed to convert the scene.");
Assert.That(failedConversions[0].failureMessage, Is.Not.Empty, "Should have a failure message.");
}
[Test]
public void Convert_SceneWithOMLOnChild_ChildConverted()
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
SceneManager.SetActiveScene(scene);
var parent = new GameObject("Parent");
var child = new GameObject("Child");
child.transform.parent = parent.transform;
child.AddComponent<OffMeshLink>();
EditorSceneManager.SaveScene(scene, m_TestFolderPath + "/OffMeshLinkScene.unity");
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
var foundObjects = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkScene.unity") };
OffMeshLinkUpdaterUtility.Convert(foundObjects, out _);
var pathToObject = AssetDatabase.GUIDToAssetPath(foundObjects[0]);
scene = EditorSceneManager.OpenScene(pathToObject, OpenSceneMode.Additive);
var convertedGameObject = scene.GetRootGameObjects()[0];
Assert.That(convertedGameObject.transform.GetChild(0).GetComponent<OffMeshLink>(), Is.Null, "GameObject should not have OffMeshLink component after conversion.");
Assert.That(convertedGameObject.transform.GetChild(0).GetComponent<NavMeshLink>(), Is.Not.Null, "GameObject should have NavMeshLink component after conversion.");
}
[Test]
public void Convert_SceneWithPrefabInstance_PrefabAndInstanceConverted()
{
CreateSceneWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkScene.unity");
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
// Open the scene and instantiate the prefab
var scene = EditorSceneManager.OpenScene(m_TestFolderPath + "/OffMeshLinkScene.unity", OpenSceneMode.Additive);
PrefabUtility.InstantiatePrefab(prefab);
EditorSceneManager.SaveScene(scene);
var foundObjects = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkScene.unity")
};
OffMeshLinkUpdaterUtility.Convert(foundObjects, out _);
// Validate the converted prefab
var convertedPrefab = GuidToPrefab(foundObjects[0]);
Assume.That(convertedPrefab.GetComponent<OffMeshLink>(), Is.Null, "Prefab should not have OffMeshLink component after conversion.");
Assume.That(convertedPrefab.GetComponent<NavMeshLink>(), Is.Not.Null, "Prefab should have NavMeshLink component after conversion.");
Assert.That(convertedPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Prefab should have only one NavMeshLink component after conversion.");
// Validate the converted scene
scene = EditorSceneManager.OpenScene(AssetDatabase.GUIDToAssetPath(foundObjects[1]), OpenSceneMode.Additive);
var rootGameObjects = scene.GetRootGameObjects();
foreach (var sceneGo in rootGameObjects)
{
Assume.That(sceneGo.GetComponent<OffMeshLink>(), Is.Null, $"{sceneGo.name} should not have OffMeshLink component after conversion.");
Assume.That(sceneGo.GetComponent<NavMeshLink>(), Is.Not.Null, $"{sceneGo.name} Prefab should have NavMeshLink component after conversion.");
Assert.That(sceneGo.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), $"{sceneGo.name} Prefab should have only one NavMeshLink component after conversion.");
}
}
[Test]
public void Convert_PrefabWithCustomValues_ValuesAreCopiedOver()
{
var testData = new OffMeshLinkData()
{
activated = false,
autoUpdatePositions = false,
biDirectional = false,
costOverride = 123f
};
var gameObject = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var oml = gameObject.GetComponent<OffMeshLink>();
oml.activated = testData.activated;
oml.autoUpdatePositions = testData.autoUpdatePositions;
oml.biDirectional = testData.biDirectional;
oml.costOverride = testData.costOverride;
oml.startTransform = testData.startTransform;
oml.endTransform = testData.endTransform;
var foundPrefabs = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab") };
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefab = GuidToPrefab(foundPrefabs[0]);
AssertValuesAreEqual(testData, convertedPrefab.GetComponent<NavMeshLink>());
}
[Test]
public void Convert_NestedPrefabWithCustomValuesOnChild_ValuesAreCopiedOver([Values(true, false)] bool shouldFlipPrefabOrder)
{
var testData = new OffMeshLinkData()
{
activated = false,
autoUpdatePositions = false,
biDirectional = false,
costOverride = 123f
};
var parentPrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/ParentPrefab.prefab");
var childPrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/ChildPrefab.prefab");
NestPrefabs(parentPrefab, childPrefab);
var oml = childPrefab.GetComponent<OffMeshLink>();
oml.activated = testData.activated;
oml.autoUpdatePositions = testData.autoUpdatePositions;
oml.biDirectional = testData.biDirectional;
oml.costOverride = testData.costOverride;
oml.startTransform = testData.startTransform;
oml.endTransform = testData.endTransform;
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
if (shouldFlipPrefabOrder)
foundPrefabs.Reverse();
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefabs = GuidsToPrefabs(foundPrefabs);
var convertedPrefab = convertedPrefabs.Find(x => x.name == childPrefab.name);
Assume.That(convertedPrefab, Is.Not.Null, "Could not find child prefab.");
AssertValuesAreEqual(testData, convertedPrefab.GetComponent<NavMeshLink>());
}
[Test]
public void Convert_NestedPrefabWithOverriddenValues_ValuesAreOverriddenAfterConversion()
{
const float overrideValue = 321f;
var parentPrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/ParentPrefab.prefab");
var childPrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/ChildPrefab.prefab");
NestPrefabs(parentPrefab, childPrefab);
var parentInstance = PrefabUtility.InstantiatePrefab(parentPrefab) as GameObject;
var childInstance = parentInstance.transform.GetChild(0);
var oml = childInstance.GetComponent<OffMeshLink>();
oml.costOverride = overrideValue;
oml.activated = false;
oml.autoUpdatePositions = false;
oml.biDirectional = false;
oml.startTransform = oml.transform;
oml.endTransform = oml.transform;
PrefabUtility.ApplyPrefabInstance(parentInstance, InteractionMode.AutomatedAction);
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/ParentPrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/ChildPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefabs = GuidsToPrefabs(foundPrefabs);
var convertedParentPrefab = convertedPrefabs.Find(x => x.name == parentPrefab.name);
var convertedChildPrefab = convertedParentPrefab.transform.GetChild(0).gameObject;
var hasAnyOverrides = PrefabUtility.HasPrefabInstanceAnyOverrides(convertedChildPrefab, false);
Assume.That(hasAnyOverrides, Is.True, "Child prefab should have overrides.");
var propertyModifications = PrefabUtility.GetPropertyModifications(convertedChildPrefab);
Assume.That(propertyModifications.Length, Is.GreaterThan(0), "Child prefab should have property modifications.");
var propertyModification = Array.Find(propertyModifications, x => x.propertyPath == "m_CostModifier" && x.target?.GetType() == typeof(NavMeshLink));
Assume.That(propertyModification, Is.Not.Null, "Property modification for cost override should exist.");
Assert.That(propertyModification.value, Is.EqualTo(overrideValue.ToString()), "Property modification value should be equal to the override value.");
}
[Test]
public void Convert_SceneWithOverriddenPrefabInstance_ValuesAreOverriddenAfterConversion()
{
const float overrideValue = 42f;
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
CreateSceneWithPrefabInstance(prefab, m_TestFolderPath + "/OffMeshLinkScene.unity");
// Open the scene and override prefab instance values
var scene = EditorSceneManager.OpenScene(m_TestFolderPath + "/OffMeshLinkScene.unity", OpenSceneMode.Additive);
var rootGameObjects = scene.GetRootGameObjects();
var prefabInstance = Array.Find(rootGameObjects, x => x.GetComponent<OffMeshLink>());
var oml = prefabInstance.GetComponent<OffMeshLink>();
oml.costOverride = overrideValue;
oml.activated = false;
oml.autoUpdatePositions = false;
oml.biDirectional = false;
oml.startTransform = oml.transform;
oml.endTransform = oml.transform;
EditorSceneManager.SaveScene(scene);
var foundObjects = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkScene.unity")
};
OffMeshLinkUpdaterUtility.Convert(foundObjects, out _);
Assume.That(foundObjects.Count, Is.EqualTo(2));
scene = EditorSceneManager.OpenScene(m_TestFolderPath + "/OffMeshLinkScene.unity", OpenSceneMode.Additive);
rootGameObjects = scene.GetRootGameObjects();
prefabInstance = Array.Find(rootGameObjects, x => x.GetComponent<NavMeshLink>());
var hasAnyOverrides = PrefabUtility.HasPrefabInstanceAnyOverrides(prefabInstance, false);
Assume.That(hasAnyOverrides, Is.True, "Prefab instance should have overrides.");
var propertyModifications = PrefabUtility.GetPropertyModifications(prefabInstance);
Assume.That(propertyModifications.Length, Is.GreaterThan(0), "Prefab instance should have property modifications.");
var propertyModification = Array.Find(propertyModifications, x => x.propertyPath == "m_CostModifier" && x.target?.GetType() == typeof(NavMeshLink));
Assume.That(propertyModification, Is.Not.Null, "Property modification for cost override should exist.");
Assert.That(propertyModification.value, Is.EqualTo(overrideValue.ToString()), "Property modification value should be equal to the override value.");
}
[Test]
[TestCase(2)]
[TestCase(4)]
[TestCase(16)]
public void Convert_PrefabWithMultipleOML_ConvertedToMultipleNML(int noOfComponets)
{
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
// Start at 1 since we already have one component
for (var i = 1; i < noOfComponets; ++i)
prefab.AddComponent<OffMeshLink>();
PrefabUtility.SavePrefabAsset(prefab);
var foundPrefabs = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab") };
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefab = GuidToPrefab(foundPrefabs[0]);
Assert.That(convertedPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(noOfComponets), "Should have converted all OffMeshLink components to NavMeshLink components.");
Assert.That(convertedPrefab.GetComponents<OffMeshLink>().Length, Is.Zero, "Should not have any OffMeshLink components left.");
}
[Test]
public void Convert_PrefabWithOMLAndNML_PrefabConvertedAndHasOriginalNML()
{
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
prefab.AddComponent<NavMeshLink>();
PrefabUtility.SavePrefabAsset(prefab);
var foundPrefabs = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab") };
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefab = GuidToPrefab(foundPrefabs[0]);
Assert.That(convertedPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(2), "Should have converted all OffMeshLink components to NavMeshLink components.");
Assert.That(convertedPrefab.GetComponents<OffMeshLink>().Length, Is.Zero, "Should not have any OffMeshLink components left.");
}
[Test]
public void Convert_PrefabWithOMLAndNML_ValuesAreCopiedOverToCorrectNML()
{
var testData = new OffMeshLinkData()
{
activated = false,
autoUpdatePositions = false,
biDirectional = false,
costOverride = 123f
};
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var oml = prefab.GetComponent<OffMeshLink>();
oml.activated = testData.activated;
oml.autoUpdatePositions = testData.autoUpdatePositions;
oml.biDirectional = testData.biDirectional;
oml.costOverride = testData.costOverride;
oml.startTransform = testData.startTransform;
oml.endTransform = testData.endTransform;
var existingNml = prefab.AddComponent<NavMeshLink>();
PrefabUtility.SavePrefabAsset(prefab);
var foundPrefabs = new List<string> { AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab") };
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var convertedPrefab = GuidToPrefab(foundPrefabs[0]);
var links = convertedPrefab.GetComponents<NavMeshLink>();
var convertedNml = Array.Find(links, x => x != existingNml);
AssertValuesAreEqual(testData, convertedNml);
}
[Test]
public void Convert_VariantPrefabWithOml_VariantConverted()
{
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
var convertedSourcePrefab = prefabs.Find(x => !PrefabUtility.IsPartOfVariantPrefab(x));
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been converted.");
Assert.That(convertedVariantPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Variant prefab should have one NavMeshLink component.");
Assert.That(convertedSourcePrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Source prefab should have one NavMeshLink component.");
}
[Test]
public void Convert_VariantPrefabWithOverrides_ValuesAreOverriddenAfterConversion()
{
const float costOverride = 42f;
var prefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
variantPrefab.GetComponent<OffMeshLink>().costOverride = costOverride;
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
Assume.That(prefab.GetComponent<OffMeshLink>().costOverride, Is.EqualTo(-1), "Source prefab should have a cost override equal to -1.");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
var convertedSourcePrefab = prefabs.Find(x => !PrefabUtility.IsPartOfVariantPrefab(x));
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been converted.");
Assume.That(convertedVariantPrefab.GetComponent<NavMeshLink>(), Is.Not.Null, "Variant prefab should have NavMeshLink component.");
Assert.That(convertedVariantPrefab.GetComponent<NavMeshLink>().costModifier, Is.EqualTo(costOverride), $"Variant prefab should have a cost modifier equal to {costOverride}.");
Assert.That(convertedSourcePrefab.GetComponent<NavMeshLink>().costModifier, Is.EqualTo(-1), "Source prefab should have a cost modifier equal to -1.");
}
[Test]
public void Convert_VariantPrefabWithAddedOML_VariantConvertedWithAddedNMLAndOriginalNML()
{
var sourcePrefab = CreatePrefabFromGameObject(new GameObject(), m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
variantPrefab.AddComponent<OffMeshLink>();
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been converted.");
Assert.That(convertedVariantPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Variant prefab should have one NavMeshLink components.");
}
[Test]
public void FindObjectsToConvert_VariantPrefabRemovedOML_ConverterFindsVariant()
{
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
Object.DestroyImmediate(variantPrefab.GetComponent<OffMeshLink>());
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = OffMeshLinkUpdaterUtility.FindObjectsToConvert(new[] { m_TestFolderPath });
var prefabs = GuidsToPrefabs(foundPrefabs);
var foundVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
Assume.That(foundPrefabs.Count, Is.EqualTo(2), "Should have found 2 prefabs.");
Assert.That(foundVariantPrefab, Is.Not.Null, "Variant prefab should have been found.");
}
[Test]
public void Convert_VariantPrefabRemovedOML_SourceConvertedToNML()
{
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
Object.DestroyImmediate(variantPrefab.GetComponent<OffMeshLink>());
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedSourcePrefab = prefabs.Find(x => !PrefabUtility.IsPartOfVariantPrefab(x));
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been found.");
Assume.That(convertedSourcePrefab, Is.Not.Null, "Source prefab should have been found.");
Assert.That(convertedVariantPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(0), "Variant prefab should not have any NavMeshLink components.");
Assert.That(convertedSourcePrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Source prefab should have one NavMeshLink components.");
}
[Test]
public void Convert_VariantPrefabRemovedOneKeptOneOML_ConvertedWithOneNML()
{
const float costOverrideValue = 1234f;
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var secondaryOml = sourcePrefab.AddComponent<OffMeshLink>();
secondaryOml.costOverride = costOverrideValue;
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
var omlToDestroy = Array.Find(variantPrefab.GetComponents<OffMeshLink>(), x => !Mathf.Approximately(x.costOverride, costOverrideValue));
Object.DestroyImmediate(omlToDestroy);
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been found.");
var convertedNavMeshLink = convertedVariantPrefab.GetComponent<NavMeshLink>();
Assume.That(convertedNavMeshLink, Is.Not.Null, "Variant prefab should have one NavMeshLink components.");
Assert.That(convertedNavMeshLink.costModifier, Is.EqualTo(costOverrideValue).Within(Mathf.Epsilon), "Cost modifier should be equal to the cost override value.");
}
[Test]
public void Convert_VariantPrefabRemovedOMLAndAddNewOML_ConvertedWithOneNML()
{
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var variantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
Object.DestroyImmediate(variantPrefab.GetComponent<OffMeshLink>());
variantPrefab.AddComponent<OffMeshLink>();
PrefabUtility.RecordPrefabInstancePropertyModifications(variantPrefab);
PrefabUtility.SaveAsPrefabAsset(variantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariant.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedSourcePrefab = prefabs.Find(x => !PrefabUtility.IsPartOfVariantPrefab(x));
var convertedVariantPrefab = prefabs.Find(PrefabUtility.IsPartOfVariantPrefab);
Assume.That(convertedVariantPrefab, Is.Not.Null, "Variant prefab should have been found.");
Assume.That(convertedSourcePrefab, Is.Not.Null, "Source prefab should have been found.");
Assert.That(convertedVariantPrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Variant prefab should have one NavMeshLink components.");
Assert.That(convertedSourcePrefab.GetComponents<NavMeshLink>().Length, Is.EqualTo(1), "Source prefab should have one NavMeshLink components.");
}
[Test]
public void Convert_TwoVariantsWithOverrides_VariantsKeepTheirOverridesAfterConversion()
{
const float firstCostOverride = 42f;
const float secondCostOverride = 1234f;
var sourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/OffMeshLinkPrefab.prefab");
var firstVariantPrefab = PrefabUtility.InstantiatePrefab(sourcePrefab) as GameObject;
firstVariantPrefab.GetComponent<OffMeshLink>().costOverride = firstCostOverride;
PrefabUtility.RecordPrefabInstancePropertyModifications(firstVariantPrefab);
var firstVariantPrefabAsset = PrefabUtility.SaveAsPrefabAsset(firstVariantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariantOne.prefab");
var secondVariantPrefab = PrefabUtility.InstantiatePrefab(firstVariantPrefabAsset) as GameObject;
secondVariantPrefab.GetComponent<OffMeshLink>().costOverride = secondCostOverride;
PrefabUtility.RecordPrefabInstancePropertyModifications(secondVariantPrefab);
PrefabUtility.SaveAsPrefabAsset(secondVariantPrefab, m_TestFolderPath + "/OffMeshLinkPrefabVariantTwo.prefab");
Assume.That(sourcePrefab.GetComponent<OffMeshLink>().costOverride, Is.EqualTo(-1), "Source prefab should have a cost override equal to -1.");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariantOne.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefabVariantTwo.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/OffMeshLinkPrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var convertedVariantPrefabOne = prefabs.Find(x => x.name == "OffMeshLinkPrefabVariantOne");
var convertedVariantPrefabTwo = prefabs.Find(x => x.name == "OffMeshLinkPrefabVariantTwo");
var convertedSourcePrefab = prefabs.Find(x => x.name == "OffMeshLinkPrefab");
Assume.That(convertedVariantPrefabOne, Is.Not.Null, "Variant prefab one should have been found.");
Assume.That(convertedVariantPrefabTwo, Is.Not.Null, "Variant prefab two should have been found.");
Assume.That(convertedSourcePrefab, Is.Not.Null, "Source prefab should have been found.");
Assume.That(convertedVariantPrefabOne.GetComponent<NavMeshLink>(), Is.Not.Null, "Variant prefab one should have a NavMeshLink component.");
Assume.That(convertedVariantPrefabTwo.GetComponent<NavMeshLink>(), Is.Not.Null, "Variant prefab two should have a NavMeshLink component.");
Assume.That(convertedSourcePrefab.GetComponent<NavMeshLink>(), Is.Not.Null, "Source prefab should have a NavMeshLink component.");
Assert.That(convertedVariantPrefabOne.GetComponent<NavMeshLink>().costModifier, Is.EqualTo(firstCostOverride), $"Variant prefab one should have a cost modifier equal to {firstCostOverride}.");
Assert.That(convertedVariantPrefabTwo.GetComponent<NavMeshLink>().costModifier, Is.EqualTo(secondCostOverride), $"Variant prefab two should have a cost modifier equal to {secondCostOverride}.");
Assert.That(convertedSourcePrefab.GetComponent<NavMeshLink>().costModifier, Is.EqualTo(-1), "Source prefab should have a cost modifier equal to -1.");
}
[Test]
public void Convert_TwoVariantsSameSource_VariantsAreSuccessfullyConverted()
{
var topSourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/TopSourcePrefab.prefab");
var midSourcePrefab = PrefabUtility.InstantiatePrefab(topSourcePrefab) as GameObject;
var midSourcePrefabAsset = PrefabUtility.SaveAsPrefabAsset(midSourcePrefab, m_TestFolderPath + "/MidSourcePrefab.prefab");
var firstLeafPrefab = PrefabUtility.InstantiatePrefab(midSourcePrefabAsset) as GameObject;
PrefabUtility.SaveAsPrefabAsset(firstLeafPrefab, m_TestFolderPath + "/FirstLeafPrefab.prefab");
var secondLeafPrefab = PrefabUtility.InstantiatePrefab(midSourcePrefabAsset) as GameObject;
PrefabUtility.SaveAsPrefabAsset(secondLeafPrefab, m_TestFolderPath + "/SecondLeafPrefab.prefab");
var foundPrefabs = new List<string>
{
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/FirstLeafPrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/MidSourcePrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/SecondLeafPrefab.prefab"),
AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/TopSourcePrefab.prefab")
};
OffMeshLinkUpdaterUtility.Convert(foundPrefabs, out _);
var prefabs = GuidsToPrefabs(foundPrefabs);
var firstLeaf = prefabs.Find(x => x.name == "FirstLeafPrefab");
var secondLeaf = prefabs.Find(x => x.name == "SecondLeafPrefab");
var midSource = prefabs.Find(x => x.name == "MidSourcePrefab");
var topSource = prefabs.Find(x => x.name == "TopSourcePrefab");
Assume.That(firstLeaf, Is.Not.Null, "First leaf prefab should have been found.");
Assume.That(secondLeaf, Is.Not.Null, "Second leaf prefab should have been found.");
Assume.That(midSource, Is.Not.Null, "Mid source prefab should have been found.");
Assume.That(topSource, Is.Not.Null, "Top source prefab should have been found.");
Assert.That(firstLeaf.GetComponent<NavMeshLink>(), Is.Not.Null, "First leaf prefab should have a NavMeshLink component.");
Assert.That(secondLeaf.GetComponent<NavMeshLink>(), Is.Not.Null, "Second leaf prefab should have a NavMeshLink component.");
Assert.That(midSource.GetComponent<NavMeshLink>(), Is.Not.Null, "Mid source prefab should have a NavMeshLink component.");
Assert.That(topSource.GetComponent<NavMeshLink>(), Is.Not.Null, "Top source prefab should have a NavMeshLink component.");
}
[Test]
public void Sort_VariantsAndScene_ConversionOrderIsDeterministic()
{
var topSourcePrefab = CreatePrefabWithComponent<OffMeshLink>(m_TestFolderPath + "/TopSourcePrefab.prefab");
var midSourcePrefab = PrefabUtility.InstantiatePrefab(topSourcePrefab) as GameObject;
var midSourcePrefabAsset = PrefabUtility.SaveAsPrefabAsset(midSourcePrefab, m_TestFolderPath + "/MidSourcePrefab.prefab");
var firstLeafPrefab = PrefabUtility.InstantiatePrefab(midSourcePrefabAsset) as GameObject;
PrefabUtility.SaveAsPrefabAsset(firstLeafPrefab, m_TestFolderPath + "/FirstLeafPrefab.prefab");
var secondLeafPrefab = PrefabUtility.InstantiatePrefab(midSourcePrefabAsset) as GameObject;
PrefabUtility.SaveAsPrefabAsset(secondLeafPrefab, m_TestFolderPath + "/SecondLeafPrefab.prefab");
CreateSceneWithComponent<OffMeshLink>("Assets/OffMeshLinkScene.unity");
var firstLeafGuid = AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/FirstLeafPrefab.prefab");
var secondLeafGuid = AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/SecondLeafPrefab.prefab");
var midSourceGuid = AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/MidSourcePrefab.prefab");
var topSourceGuid = AssetDatabase.AssetPathToGUID(m_TestFolderPath + "/TopSourcePrefab.prefab");
var sceneGuid = AssetDatabase.AssetPathToGUID("Assets/OffMeshLinkScene.unity");
var assetsToConvert = new List<string>
{
firstLeafGuid,
midSourceGuid,
secondLeafGuid,
topSourceGuid,
sceneGuid
};
OffMeshLinkUpdaterUtility.Convert(assetsToConvert, out _);
Assume.That(assetsToConvert.Count, Is.EqualTo(5), "Should have 5 assets to convert.");
Assert.That(assetsToConvert[0], Is.EqualTo(topSourceGuid));
Assert.That(assetsToConvert[1], Is.EqualTo(midSourceGuid));
Assert.That(assetsToConvert[2], Is.EqualTo(firstLeafGuid));
Assert.That(assetsToConvert[3], Is.EqualTo(secondLeafGuid));
Assert.That(assetsToConvert[4], Is.EqualTo(sceneGuid));
}
static GameObject CreatePrefabWithComponent<T>(string savePath) where T : Component
{
var go = new GameObject();
go.AddComponent<T>();
return CreatePrefabFromGameObject(go, savePath);
}
static GameObject CreatePrefabFromGameObject(GameObject go, string savePath)
{
var prefabGo = PrefabUtility.SaveAsPrefabAsset(go, savePath);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
Object.DestroyImmediate(go);
return prefabGo;
}
static Scene CreateSceneWithComponent<T>(string savePath) where T : Component
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
SceneManager.SetActiveScene(scene);
var go = new GameObject();
go.AddComponent<T>();
EditorSceneManager.SaveScene(scene, savePath);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
return scene;
}
static Scene CreateSceneWithPrefabInstance(GameObject prefab, string savePath)
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
SceneManager.SetActiveScene(scene);
var instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
EditorSceneManager.SaveScene(scene, savePath);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
return scene;
}
static void NestPrefabs(GameObject parentPrefab, GameObject childPrefab)
{
var parentInstance = PrefabUtility.InstantiatePrefab(parentPrefab) as GameObject;
PrefabUtility.InstantiatePrefab(childPrefab, parentInstance.transform);
PrefabUtility.ApplyPrefabInstance(parentInstance, InteractionMode.AutomatedAction);
}
static GameObject GuidToPrefab(string guid)
{
var pathToObject = AssetDatabase.GUIDToAssetPath(guid);
return AssetDatabase.LoadAssetAtPath<GameObject>(pathToObject);
}
static List<GameObject> GuidsToPrefabs(List<string> guids)
{
var gameObjects = new List<GameObject>();
foreach (var guid in guids)
gameObjects.Add(GuidToPrefab(guid));
return gameObjects;
}
static void AssertValuesAreEqual(OffMeshLinkData expectedData, NavMeshLink navMeshLink)
{
Assert.That(navMeshLink.activated, Is.EqualTo(expectedData.activated), "Expected activated to be equal.");
Assert.That(navMeshLink.autoUpdate, Is.EqualTo(expectedData.autoUpdatePositions), "Expected autoUpdate to be equal.");
Assert.That(navMeshLink.bidirectional, Is.EqualTo(expectedData.biDirectional), "Expected bidirectional to be equal.");
Assert.That(navMeshLink.costModifier, Is.EqualTo(expectedData.costOverride), "Expected costModifier to be equal.");
Assert.That(navMeshLink.startTransform, Is.EqualTo(expectedData.startTransform), "Expected startTransform to be equal.");
Assert.That(navMeshLink.endTransform, Is.EqualTo(expectedData.endTransform), "Expected endTransform to be equal.");
}
}
#pragma warning restore 618
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0ec7ba7e5444cf7b7fa8ea5e2f8c4f5
timeCreated: 1710247468

View File

@@ -0,0 +1,56 @@
using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Editor.Tests
{
internal abstract class DomainReloadTestBase
{
struct OptionSet
{
public bool enterPlayModeOptionsEnabled;
public EnterPlayModeOptions enterPlayModeOptions;
}
OptionSet m_OriginalOptions;
[SerializeField] protected GameObject m_TestGo;
[UnityTearDown]
public IEnumerator TearDown()
{
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
if (m_TestGo != null)
{
Object.DestroyImmediate(m_TestGo);
m_TestGo = null;
}
}
[OneTimeSetUp]
public void OneTimeSetUp()
{
if (EditorApplication.isPlaying)
return;
m_OriginalOptions = new OptionSet()
{
enterPlayModeOptions = EditorSettings.enterPlayModeOptions,
enterPlayModeOptionsEnabled = EditorSettings.enterPlayModeOptionsEnabled
};
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (EditorApplication.isPlaying)
return;
EditorSettings.enterPlayModeOptions = m_OriginalOptions.enterPlayModeOptions;
EditorSettings.enterPlayModeOptionsEnabled = m_OriginalOptions.enterPlayModeOptionsEnabled;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c69ffaa78fdb4e7796dc916a104d99ba
timeCreated: 1700481896

View File

@@ -0,0 +1,105 @@
using System.Reflection;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.AI.Navigation.Editor.Tests
{
internal class InspectorWindowWrapper
{
readonly EditorWindow m_InspectorWindow;
readonly bool m_OriginalInspectorThrottling;
readonly bool m_OriginalPanelThrottling;
public InspectorWindowWrapper()
{
EditorApplication.ExecuteMenuItem("Window/General/Inspector");
var windows = Resources.FindObjectsOfTypeAll<EditorWindow>();
foreach (var window in windows)
{
if (window.GetType().Name != "InspectorWindow")
continue;
m_InspectorWindow = window;
break;
}
Assume.That(m_InspectorWindow, Is.Not.Null, "Inspector window not found");
m_OriginalInspectorThrottling = GetDisableInspectorElementThrottling();
m_OriginalPanelThrottling = GetDisablePanelThrottling();
SetDisableInspectorElementThrottling(true);
SetDisablePanelThrottling(true);
}
/// <summary>
/// Set the inspector's element throttling, by accessing and setting its internal disabledThrottling property.
/// The throttling is there to collect calls to UI Toolkit and execute them in batch.
/// Disabling the throttling means that calls will be executed at the next available time.
/// </summary>
/// <param name="enabled">True means that throttling is disabled.</param>
static void SetDisableInspectorElementThrottling(bool enabled)
{
var throttlingPropertyInfo = GetDisableInspectorElementThrottlingPropertyInfo();
throttlingPropertyInfo?.SetValue(null, enabled);
}
static bool GetDisableInspectorElementThrottling()
{
var throttlingPropertyInfo = GetDisableInspectorElementThrottlingPropertyInfo();
return (bool)throttlingPropertyInfo?.GetValue(null);
}
static PropertyInfo GetDisableInspectorElementThrottlingPropertyInfo()
{
return typeof(InspectorElement).GetProperty("disabledThrottling", BindingFlags.Static | BindingFlags.NonPublic);
}
/// <summary>
/// Set the inspector's panel throttling, by accessing and setting its internal TimerEventScheduler's disableThrottling property.
/// The throttling is there to reduce the number of times a UI panel is refreshed in a given interval.
/// Disabling the throttling means that the refresh will happen as frequently as the system allows it to refresh.
/// </summary>
/// <param name="enabled">True means that throttling is disabled.</param>
void SetDisablePanelThrottling(bool enabled)
{
var disableThrottlingPropInfo = GetDisablePanelThrottlingPropertyInfo(out var scheduler);
disableThrottlingPropInfo?.SetValue(scheduler, enabled);
}
bool GetDisablePanelThrottling()
{
var disableThrottlingPropInfo = GetDisablePanelThrottlingPropertyInfo(out var scheduler);
return (bool)disableThrottlingPropInfo.GetValue(scheduler);
}
FieldInfo GetDisablePanelThrottlingPropertyInfo(out object scheduler)
{
var panel = GetRootVisualElement().panel;
var schedulerPropInfo = panel.GetType().GetProperty("scheduler", BindingFlags.Instance | BindingFlags.NonPublic);
scheduler = schedulerPropInfo?.GetValue(panel);
Assume.That(scheduler?.GetType().Name, Is.EqualTo("TimerEventScheduler"), "Scheduler is not a TimerEventScheduler");
return scheduler?.GetType().GetField("disableThrottling", BindingFlags.Instance | BindingFlags.NonPublic);
}
public void RepaintImmediately()
{
var repaintMethodInfo = m_InspectorWindow.GetType().GetMethod("RepaintImmediately", BindingFlags.Instance | BindingFlags.NonPublic);
repaintMethodInfo?.Invoke(m_InspectorWindow, null);
}
public void Focus() => m_InspectorWindow.Focus();
public void Close()
{
SetDisablePanelThrottling(m_OriginalPanelThrottling);
SetDisableInspectorElementThrottling(m_OriginalInspectorThrottling);
m_InspectorWindow.Close();
}
public VisualElement GetRootVisualElement() => m_InspectorWindow.rootVisualElement;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fade91be62f4426eb39af79472c77206
timeCreated: 1717659840

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8061144b555b4fbbb5226d6e18f4dd50
timeCreated: 1717509602

View File

@@ -0,0 +1,58 @@
using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UIElements;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
public class NavMeshLinkInspectorTests
{
GameObject m_TestGameObject;
InspectorWindowWrapper m_InspectorWindowWrapper;
[SetUp]
public void SetUp()
{
m_TestGameObject = new GameObject("Test GameObject", typeof(NavMeshLink));
m_InspectorWindowWrapper = new InspectorWindowWrapper();
}
[TearDown]
public void TearDown()
{
Object.DestroyImmediate(m_TestGameObject);
m_InspectorWindowWrapper?.Close();
}
[UnityTest]
public IEnumerator ShowNavMeshLinkInInspector_NoErrorsOrWarnings()
{
Selection.objects = new Object[] { m_TestGameObject };
yield return WaitAmountOfMs(100);
m_InspectorWindowWrapper.Focus();
m_InspectorWindowWrapper.RepaintImmediately();
yield return WaitAmountOfMs(100);
// Validate that the inspector is showing the NavMeshLink component
var rootVisualElement = m_InspectorWindowWrapper.GetRootVisualElement();
Assume.That(rootVisualElement, Is.Not.Null, "Root visual element not found");
var headerElement = rootVisualElement.Q<IMGUIContainer>("NavMesh LinkHeader");
Assume.That(headerElement, Is.Not.Null, "NavMesh Link header element not found");
LogAssert.NoUnexpectedReceived();
}
static IEnumerator WaitAmountOfMs(int ms)
{
var startTime = GetTimeSinceStartupMs();
while (GetTimeSinceStartupMs() < startTime + ms)
yield return null;
}
static long GetTimeSinceStartupMs() => (long)(Time.realtimeSinceStartup * 1000f);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24d54ced0c104fe8a73ca517313d3dda
timeCreated: 1717509626

View File

@@ -0,0 +1,552 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.TestTools.Utils;
using Matrix4x4 = UnityEngine.Matrix4x4;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
using From = Unity.AI.Navigation.Editor.Tests.NavMeshLinkEditorTests.LinkEndType;
using To = Unity.AI.Navigation.Editor.Tests.NavMeshLinkEditorTests.LinkEndType;
// Note: To pause and inspect the state during these editor tests,
// run them in playmode from the NavMeshLinkEditorTestsInPlaymode class
// in the Unity.AI.Navigation.Editor.Tests.InPlaymode namespace.
namespace Unity.AI.Navigation.Editor.Tests
{
public class NavMeshLinkEditorTests
{
List<UnityEngine.Object> m_TestObjects = new();
GameObject m_LinkGameObject;
GameObject m_Start;
GameObject m_End;
NavMeshLink m_Link;
NavMeshLink m_LinkSibling1;
NavMeshLink m_LinkSibling2;
GameObject m_StartOnNavMesh;
GameObject m_EndOnNavMesh;
const int k_NotWalkable = 1;
const int k_Walkable = 0;
static readonly Vector3EqualityComparer k_DefaultThreshold = Vector3EqualityComparer.Instance;
GameObject CreateTestObject(string name, params Type[] components)
{
var go = new GameObject(name, components);
m_TestObjects.Add(go);
return go;
}
[OneTimeSetUp]
public void OneTimeSetup()
{
m_LinkGameObject = new GameObject("Link");
m_Link = m_LinkGameObject.AddComponent<NavMeshLink>();
m_Start = new GameObject("Start");
m_End = new GameObject("End");
m_StartOnNavMesh = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_StartOnNavMesh.gameObject.transform.position = new Vector3(20.0f, 0.0f, 20.0f);
m_EndOnNavMesh = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_EndOnNavMesh.gameObject.transform.position = new Vector3(20.0f, 0.0f, 40.0f);
var surface = m_StartOnNavMesh.gameObject.AddComponent<NavMeshSurface>();
surface.BuildNavMesh();
// To debug, add these components, only to show icons for them in the scene
//m_Start.AddComponent<NavMeshSurface>().enabled = false;
//m_End.AddComponent<NavMeshModifier>().enabled = false;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (m_LinkGameObject != null)
UnityEngine.Object.DestroyImmediate(m_LinkGameObject);
if (m_Start != null)
UnityEngine.Object.DestroyImmediate(m_Start);
if (m_End != null)
UnityEngine.Object.DestroyImmediate(m_End);
if (m_StartOnNavMesh != null)
UnityEngine.Object.DestroyImmediate(m_StartOnNavMesh);
if (m_EndOnNavMesh != null)
UnityEngine.Object.DestroyImmediate(m_EndOnNavMesh);
}
[SetUp]
public void Setup()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.area = k_Walkable;
m_Link.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
m_Link.transform.localScale = Vector3.one;
m_Link.startPoint = Vector3.left;
m_Link.endPoint = Vector3.right;
}
}
[TearDown]
public void TearDown()
{
if (m_LinkSibling1 != null)
UnityEngine.Object.DestroyImmediate(m_LinkSibling1);
if (m_LinkSibling2 != null)
UnityEngine.Object.DestroyImmediate(m_LinkSibling2);
foreach (var obj in m_TestObjects)
{
if (obj != null)
UnityEngine.Object.DestroyImmediate(obj);
}
m_TestObjects.Clear();
}
protected static readonly Vector3[] k_ReverseDirectionPositions =
{ Vector3.zero, new(1f, 2f, 3f), new(1f, -2f, 3f) };
protected static readonly Quaternion[] k_ReverseDirectionOrientations =
{ Quaternion.identity, new(0f, 0.7071067812f, 0f, 0.7071067812f) };
protected static readonly Vector3[] k_ReverseDirectionScales =
{ Vector3.one, new(0.5f, 1f, 2f), new(0.5f, -1f, 2f) };
[Test]
public void ReverseDirection_SwapsStartAndEndPoints(
[ValueSource(nameof(k_ReverseDirectionPositions))]
Vector3 position,
[ValueSource(nameof(k_ReverseDirectionOrientations))]
Quaternion orientation,
[ValueSource(nameof(k_ReverseDirectionScales))]
Vector3 scale
)
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.transform.SetPositionAndRotation(position, orientation);
m_Link.transform.localScale = scale;
m_Link.startPoint = new Vector3(2f, 0f, 0f);
m_Link.endPoint = new Vector3(0f, 0f, 2f);
}
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startPoint, m_Link.endPoint),
Is.EqualTo((new Vector3(0f, 0f, 2f), new Vector3(2f, 0f, 0f))),
"Start and end points did not swap."
);
}
[Test]
public void ReverseDirection_SwapsStartAndEndPoints_TargetTransformsDoNotAffect(
[ValueSource(nameof(k_ReverseDirectionPositions))]
Vector3 position,
[ValueSource(nameof(k_ReverseDirectionOrientations))]
Quaternion orientation,
[ValueSource(nameof(k_ReverseDirectionScales))]
Vector3 scale
)
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startTransform = CreateTestObject("Start").transform;
m_Link.endTransform = CreateTestObject("End").transform;
m_Link.transform.SetPositionAndRotation(position, orientation);
m_Link.transform.localScale = scale;
m_Link.startPoint = new Vector3(2f, 0f, 0f);
m_Link.endPoint = new Vector3(0f, 0f, 2f);
}
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startPoint, m_Link.endPoint),
Is.EqualTo((new Vector3(0f, 0f, 2f), new Vector3(2f, 0f, 0f))),
"Start and end points did not swap."
);
}
protected static readonly TestCaseData[] k_LinkEnabledInitially =
{
new TestCaseData(true).SetName("Enabled before setting Not Walkable"),
new TestCaseData(false).SetName("Disabled before setting Not Walkable")
};
[Test]
[TestCaseSource(nameof(k_LinkEnabledInitially))]
public void NavMeshLink_AfterSwitchingFromNonWalkableAreaType_BecomesWalkable(bool enabledInitially)
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startTransform = m_StartOnNavMesh.transform;
m_Link.endTransform = m_EndOnNavMesh.transform;
m_Link.area = k_Walkable;
}
var navMeshLinkObject = new SerializedObject(m_Link.GetComponent<NavMeshLink>());
var navMeshLinkEnabled = navMeshLinkObject.FindProperty("m_Enabled");
var areaTypeProperty = navMeshLinkObject.FindProperty("m_Area");
var path = new NavMeshPath();
NavMesh.CalculatePath(m_StartOnNavMesh.transform.position, m_EndOnNavMesh.transform.position, NavMesh.AllAreas, path);
Assume.That(path.status, Is.EqualTo(NavMeshPathStatus.PathComplete));
navMeshLinkEnabled.boolValue = enabledInitially;
navMeshLinkObject.ApplyModifiedProperties();
areaTypeProperty.intValue = k_NotWalkable;
navMeshLinkObject.ApplyModifiedProperties();
NavMesh.CalculatePath(m_StartOnNavMesh.transform.position, m_EndOnNavMesh.transform.position, NavMesh.AllAreas, path);
Assume.That(path.status, Is.Not.EqualTo(NavMeshPathStatus.PathComplete));
navMeshLinkEnabled.boolValue = true;
navMeshLinkObject.ApplyModifiedProperties();
areaTypeProperty.intValue = k_Walkable;
navMeshLinkObject.ApplyModifiedProperties();
NavMesh.CalculatePath(m_StartOnNavMesh.transform.position, m_EndOnNavMesh.transform.position, NavMesh.AllAreas, path);
Assert.That(path.status, Is.EqualTo(NavMeshPathStatus.PathComplete));
}
[Test]
public void ReverseDirection_SwapsStartAndEndTransforms()
{
var start = m_Link.startTransform = CreateTestObject("Start").transform;
var end = m_Link.endTransform = CreateTestObject("End").transform;
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startTransform, m_Link.endTransform),
Is.EqualTo((end, start)),
"Start and end transform did not swap."
);
}
[Test]
public void ReverseDirection_OneTransformIsNotSet_SwapsStartAndEndTransforms()
{
var start = m_Link.startTransform = CreateTestObject("Start").transform;
var end = m_Link.endTransform;
NavMeshLinkEditor.ReverseDirection(m_Link);
Assert.That(
(m_Link.startTransform, m_Link.endTransform),
Is.EqualTo((end, start)),
"Start and end transform did not swap."
);
}
static readonly Vector3 k_Offset101 = new(1, 1, 1);
static readonly Vector3 k_Offset103 = new(1, 1, 3);
static readonly Quaternion k_DoNotRotate = Quaternion.identity;
static readonly Quaternion k_FlipToRight = Quaternion.Euler(0, 0, -90);
static readonly Quaternion k_UpSideDown = Quaternion.Euler(0, 0, 180);
//TestCaseData( startType, endType, transformRotation,
// expectedPosition, expectedForward)
protected static readonly TestCaseData[] k_PointsOnly =
{
new(From.Point, To.Point, k_DoNotRotate,
new Vector3(1, 1, 2), Vector3.back),
new(From.Point, To.Point, k_UpSideDown,
new Vector3(-1, -1, 2), Vector3.back),
};
protected static readonly TestCaseData[] k_PointAndTransforms =
{
new(From.Point, To.Transform, k_DoNotRotate,
new Vector3(1, 2, 2), Vector3.back),
new(From.Point, To.Transform, k_FlipToRight,
new Vector3(1, 1, 2), Quaternion.Euler(-116.565f, 0, -90) * Vector3.forward),
new(From.Transform, To.Point, k_DoNotRotate,
new Vector3(1, 2, 2), Vector3.back),
new(From.Transform, To.Point, k_FlipToRight,
new Vector3(1, 1, 2), Quaternion.Euler(116.565f, 0, -90) * Vector3.forward),
new(From.Transform, To.Transform, k_DoNotRotate,
new Vector3(1, 3, 2), Vector3.back),
new(From.Transform, To.Transform, k_UpSideDown,
new Vector3(1, 3, 2), Vector3.back),
};
protected static readonly TestCaseData[] k_ChildTransforms =
{
new(From.TransformChild, To.TransformChild, k_FlipToRight,
new Vector3(3, -1, 2), Vector3.back),
};
void ConfigureLinkForTest(From startType, To endType, Quaternion transformRotation)
{
m_Start.transform.position = k_Offset103 + 2f * Vector3.up;
m_End.transform.position = k_Offset101 + 2f * Vector3.up;
m_Start.transform.parent = startType == From.TransformChild ? m_Link.transform : null;
m_End.transform.parent = endType == To.TransformChild ? m_Link.transform : null;
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = startType != From.Point ? m_Start.transform : null;
m_Link.endTransform = endType != To.Point ? m_End.transform : null;
m_Link.transform.rotation = transformRotation;
}
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_MovesTransformInTheMiddle(
From startType, To endType, Quaternion transformRotation,
Vector3 expectedPosition, Vector3 _)
{
ConfigureLinkForTest(startType, endType, transformRotation);
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.position, Is.EqualTo(expectedPosition).Using(k_DefaultThreshold),
"The Link object should be in the middle between the endpoints.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[Description("When the endpoints remain at the same world position it means that the local points must have been adjusted correctly.")]
public void AlignTransformToEndPoints_EndpointsWorldPositionsRemainUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialEndpointsMatrix = LocalToWorldUnscaled(m_Link);
var initialStartWorld = initialEndpointsMatrix.MultiplyPoint3x4(m_Link.startPoint);
var initialEndWorld = initialEndpointsMatrix.MultiplyPoint3x4(m_Link.endPoint);
var initialStartLocal = m_Link.startPoint;
var initialEndLocal = m_Link.endPoint;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assume.That(m_Link.startPoint, Is.Not.EqualTo(initialStartLocal).Using(k_DefaultThreshold),
"The local position of Start point should have been adjusted.");
Assert.That(m_Link.endPoint, Is.Not.EqualTo(initialEndLocal).Using(k_DefaultThreshold),
"The local position of End point should have been adjusted.");
var endpointsMatrix = LocalToWorldUnscaled(m_Link);
var startWorld = endpointsMatrix.MultiplyPoint3x4(m_Link.startPoint);
var endWorld = endpointsMatrix.MultiplyPoint3x4(m_Link.endPoint);
Assert.That(startWorld, Is.EqualTo(initialStartWorld).Using(k_DefaultThreshold),
"The world position of Start should remain unchanged.");
Assert.That(endWorld, Is.EqualTo(initialEndWorld).Using(k_DefaultThreshold),
"The world position of End should remain unchanged.");
}
static Matrix4x4 LocalToWorldUnscaled(NavMeshLink link)
{
return Matrix4x4.TRS(link.transform.position, link.transform.rotation, Vector3.one);
}
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_EndsTransformsRemainUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialStartPosition = m_Link.startTransform != null ? m_Link.startTransform.position : Vector3.negativeInfinity;
var initialEndPosition = m_Link.endTransform != null ? m_Link.endTransform.position : Vector3.negativeInfinity;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
if (m_Link.startTransform != null)
Assert.That(m_Link.startTransform.position, Is.EqualTo(initialStartPosition).Using(k_DefaultThreshold),
"The Link start transform should not have moved.");
if (m_Link.endTransform != null)
Assert.That(m_Link.endTransform.position, Is.EqualTo(initialEndPosition).Using(k_DefaultThreshold),
"The Link end transform should not have moved.");
}
[Test]
[Explicit("Functionality not implemented yet for child game objects")]
public void AlignTransformToEndPoints_ChildGameObjectsRetainWorldPositions()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = null;
m_Link.endTransform = null;
m_Link.transform.rotation = k_FlipToRight;
}
var child1 = CreateTestObject("Child 1").GetComponent<Transform>();
var child2 = CreateTestObject("Child 2").GetComponent<Transform>();
var grandchild = CreateTestObject("Grandchild").GetComponent<Transform>();
child2.rotation = Quaternion.Euler(0, 90, 0);
child1.parent = m_Link.transform;
child2.parent = m_Link.transform;
grandchild.parent = child2.transform;
child1.position = new Vector3(2, 1, 3);
child2.position = new Vector3(-0.5f, 0.6f, 0.7f);
grandchild.position = new Vector3(-3, 2, 1);
var child1Earlier = child1.position;
var child2Earlier = child2.position;
var grandchildEarlier = grandchild.position;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(child1.position, Is.EqualTo(child1Earlier).Using(k_DefaultThreshold),
"Child object 1 should not have moved.");
Assert.That(child2.position, Is.EqualTo(child2Earlier).Using(k_DefaultThreshold),
"Child object 2 should not have moved.");
Assert.That(grandchild.position, Is.EqualTo(grandchildEarlier).Using(k_DefaultThreshold),
"Grandchild object should not have moved.");
}
[Test]
public void AlignTransformToEndPoints_EndpointsWorldPositionsRemainUnchanged_InSiblingLinks()
{
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = k_Offset103;
m_Link.endPoint = k_Offset101;
m_Link.startTransform = null;
m_Link.endTransform = null;
m_Link.transform.rotation = k_FlipToRight;
}
m_LinkSibling1 = m_Link.gameObject.AddComponent<NavMeshLink>();
m_LinkSibling2 = m_Link.gameObject.AddComponent<NavMeshLink>();
m_LinkSibling1.startPoint = m_Link.endPoint;
m_LinkSibling1.endPoint = m_Link.startPoint;
m_LinkSibling2.startPoint = m_Link.endPoint;
m_LinkSibling2.endPoint = m_Link.startPoint;
var initialStartLocal = m_Link.startPoint;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assume.That(m_Link.startPoint, Is.Not.EqualTo(initialStartLocal).Using(k_DefaultThreshold));
Assert.That(m_LinkSibling1.startPoint, Is.EqualTo(m_Link.endPoint).Using(k_DefaultThreshold),
"The sibling 1 Start point should have been adjusted.");
Assert.That(m_LinkSibling1.endPoint, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold),
"The sibling 1 End point should have been adjusted.");
Assert.That(m_LinkSibling2.startPoint, Is.EqualTo(m_Link.endPoint).Using(k_DefaultThreshold),
"The sibling 2 Start point should have been adjusted.");
Assert.That(m_LinkSibling2.endPoint, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold),
"The sibling 2 End point should have been adjusted.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_UpVectorRemainsUnchanged(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 __)
{
ConfigureLinkForTest(startType, endType, transformRotation);
var initialUp = m_Link.transform.up;
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.up, Is.EqualTo(initialUp).Using(k_DefaultThreshold),
"The Link's up vector should remain unchanged.");
}
[TestCaseSource(nameof(k_PointsOnly))]
[TestCaseSource(nameof(k_PointAndTransforms))]
[TestCaseSource(nameof(k_ChildTransforms))]
public void AlignTransformToEndPoints_OrientsForwardVectorFromStartToEndInXZPlane(
From startType, To endType, Quaternion transformRotation,
Vector3 _, Vector3 expectedForward)
{
ConfigureLinkForTest(startType, endType, transformRotation);
NavMeshLinkEditor.AlignTransformToEndPoints(m_Link);
Assert.That(m_Link.transform.forward, Is.EqualTo(expectedForward).Using(k_DefaultThreshold),
"The Link's forward vector should point from the start towards the end of the link.");
}
static readonly Vector3 k_ForwardRightDiagonal = Quaternion.Euler(0, 45, 0) * Vector3.forward;
static readonly Quaternion k_RotatedX90 = Quaternion.Euler(90, 0, 0);
static readonly Quaternion k_RotatedY180 = Quaternion.Euler(0, 180, 0);
static readonly Quaternion k_RotatedZ90 = Quaternion.Euler(0, 0, 90);
static readonly Quaternion k_RotatedZ180 = Quaternion.Euler(0, 0, 180);
protected static readonly TestCaseData[] k_RotateToChangeLinkDirectionLocally =
{
new TestCaseData(Quaternion.identity, Vector3.right, Vector3.one)
.SetName("Link aligned to World axes"),
new TestCaseData(k_RotatedX90, Vector3.zero, new Vector3(1, 1, -1))
.SetName("Endpoints following the Up direction")
.SetDescription("The right vector cannot be properly defined in this case"),
new TestCaseData(k_RotatedY180, Vector3.forward, new Vector3(-1, 1, -1))
.SetName("Endpoints following Right direction"),
new TestCaseData(k_RotatedZ90, Vector3.right, new Vector3(1, -1, 1))
.SetName("Link tipped to the left"),
new TestCaseData(k_RotatedZ180, k_ForwardRightDiagonal, new Vector3(-1, -1, 1))
.SetName("Link rotated freely")
.SetDescription("The rotation has been chosen to produce a Right vector easy to identify and verify."),
};
[TestCaseSource(nameof(k_RotateToChangeLinkDirectionLocally))]
[Description("The link's direction changes because the end transform moves in local space when the game object rotates.")]
public void CalcLinkRight_ReturnsLocalRightIn2D(
Quaternion transformRotation,
Vector3 expectedLocalRight, Vector3 expectedEnd)
{
m_Link.transform.SetPositionAndRotation(Vector3.one, transformRotation);
m_Link.transform.localScale = 2f * Vector3.one;
m_End.transform.position = 2f * Vector3.one;
using (new NavMeshLinkEditor.DeferredLinkUpdateScope(m_Link))
{
m_Link.startPoint = new Vector3(1, -1, -1);
m_Link.endTransform = m_End.transform;
m_Link.startTransform = null;
m_Link.endPoint = Vector3.negativeInfinity;
}
var linkRight = NavMeshLinkEditor.GetLocalDirectionRight(m_Link, out var localStart, out var localEnd);
Assume.That(localStart, Is.EqualTo(m_Link.startPoint).Using(k_DefaultThreshold), "Wrong local Start reported.");
Assume.That(localEnd, Is.EqualTo(expectedEnd).Using(k_DefaultThreshold), "Wrong local End reported.");
Assert.That(linkRight, Is.EqualTo(expectedLocalRight).Using(k_DefaultThreshold),
"Wrong Right vector relative to the direction from start to end.");
}
public enum LinkEndType
{
Point,
Transform,
TransformChild
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6edd135750744d2ba70784191858ba6e
timeCreated: 1707859138

View File

@@ -0,0 +1,40 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace Unity.AI.Navigation.Editor.Tests
{
class NavMeshLinkInPrefabTests
{
[Test]
public void NavMeshLink_DifferentGOActiveState_PromotedToPrefab_InstanceHasNoOverrides([Values]bool isActive)
{
var assetPath = $"Assets/{GUID.Generate()}.prefab";
NavMeshLink nml = null;
try
{
var go = new GameObject("NavMesh Link");
go.SetActive(isActive);
nml = go.AddComponent<NavMeshLink>();
PrefabUtility.SaveAsPrefabAssetAndConnect(nml.gameObject, assetPath, InteractionMode.AutomatedAction);
var sp = new SerializedObject(nml).GetIterator();
var overrides = new List<string>();
while (sp.NextVisible(true))
{
if (!sp.isDefaultOverride && sp.prefabOverride)
overrides.Add(sp.propertyPath);
}
Assert.That(overrides, Is.Empty, "Newly promoted prefab instance overrides one or more properties.");
}
finally
{
if (nml != null)
Object.DestroyImmediate(nml.gameObject);
AssetDatabase.DeleteAsset(assetPath);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5b652cf08f54e3bae634658f9a4e119
timeCreated: 1710416712

View File

@@ -0,0 +1,179 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions.Comparers;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
internal class NavMeshLinkTests : DomainReloadTestBase
{
const string k_TrackedListFieldName = "s_Tracked";
[SerializeField] NavMeshLink m_Link;
[UnityTest]
public IEnumerator TrackedList_AddOneItemToListInEditMode_TrackedListSetToZeroInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var listVar = GetTrackedList();
Assume.That(listVar, Is.Not.Null);
Assume.That(listVar.Count, Is.Zero);
listVar.Add(null);
Assume.That(listVar.Count, Is.Not.Zero);
yield return new EnterPlayMode();
listVar = GetTrackedList();
Assert.That(listVar.Count, Is.Zero);
}
[UnityTest]
public IEnumerator TrackedList_CreateAutoUpdatedNavMeshLinkInEditMode_NavMeshLinkRemainsInTrackedListInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var trackedList = GetTrackedList();
Assume.That(trackedList, Is.Not.Null);
Assume.That(trackedList.Count, Is.Zero);
// Create a link during edit mode.
// Setting it up so that it gets added to the tracked list.
m_TestGo = new GameObject("TestObj", typeof(NavMeshLink));
m_Link = m_TestGo.GetComponent<NavMeshLink>();
m_Link.autoUpdate = true;
trackedList = GetTrackedList();
Assume.That(trackedList, Is.Not.Null);
Assume.That(trackedList.Count, Is.EqualTo(1));
Assume.That(trackedList[0], Is.EqualTo(m_Link));
yield return new EnterPlayMode();
trackedList = GetTrackedList();
Assert.That(trackedList.Count, Is.EqualTo(1));
Assert.That(trackedList[0], Is.EqualTo(m_Link));
}
static List<NavMeshLink> GetTrackedList()
{
var field = typeof(NavMeshLink).GetField(k_TrackedListFieldName, BindingFlags.Static | BindingFlags.NonPublic);
Assume.That(field, Is.Not.Null, $"Cannot find field '{k_TrackedListFieldName}' in class {nameof(NavMeshLink)}.");
return field?.GetValue(null) as List<NavMeshLink>;
}
[TestCase(0, 0, true, TestName = "CostModifier at 0 should override cost and return 0.")]
[TestCase(1, 1, true, TestName = "CostModifier at 1 should override cost and return 1.")]
[TestCase(-1, 1, false, TestName = "CostModifier at -1 should not override cost and return 1.")]
[TestCase(42, 42, true, TestName = "CostModifier at 42 should override cost and return 42.")]
[TestCase(-42, 42, false, TestName = "CostModifier at -42 should not override cost and return 42.")]
public void CostModifier_SetValue_SerializedPropertyIsTheSame(int costModifier, float expectedCostValue, bool expectedOverrideValue)
{
m_TestGo = new GameObject("TestObj", typeof(NavMeshLink));
m_Link = m_TestGo.GetComponent<NavMeshLink>();
m_Link.costModifier = costModifier;
var serializedObject = new SerializedObject(m_Link);
var costModifierProperty = serializedObject.FindProperty("m_CostModifier");
var isOverridingCostProperty = serializedObject.FindProperty("m_IsOverridingCost");
Assume.That(costModifierProperty, Is.Not.Null, "Cannot find property 'm_CostModifier' in NavMeshLink.");
Assume.That(isOverridingCostProperty, Is.Not.Null, "Cannot find property 'm_IsOverridingCost' in NavMeshLink.");
Assert.That(costModifierProperty.floatValue, Is.EqualTo(expectedCostValue).Using(FloatComparer.s_ComparerWithDefaultTolerance));
Assert.That(isOverridingCostProperty.boolValue, Is.EqualTo(expectedOverrideValue));
}
[Test]
public void CostModifier_Reset_CostModifierRunsThroughUpgrader()
{
m_TestGo = new GameObject("TestObj", typeof(NavMeshLink));
m_Link = m_TestGo.GetComponent<NavMeshLink>();
m_Link.costModifier = 42;
var serializedObject = new SerializedObject(m_Link);
var costModifierProperty = serializedObject.FindProperty("m_CostModifier");
var isOverridingCostProperty = serializedObject.FindProperty("m_IsOverridingCost");
Assume.That(costModifierProperty.floatValue, Is.EqualTo(42f), "Cost modifier is not 42.");
Assume.That(isOverridingCostProperty.boolValue, Is.True, "Is overriding cost is false.");
Unsupported.SmartReset(m_Link);
serializedObject.Update();
Assume.That(costModifierProperty, Is.Not.Null, "Cannot find property 'm_CostModifier' in NavMeshLink.");
Assume.That(isOverridingCostProperty, Is.Not.Null, "Cannot find property 'm_IsOverridingCost' in NavMeshLink.");
Assert.That(costModifierProperty.floatValue, Is.EqualTo(1f), "Cost modifier is not 1.");
Assert.That(isOverridingCostProperty.boolValue, Is.False, "Is overriding cost is true.");
}
[Test]
public void NavMeshLink_Reset_NavMeshLinkHasDefaultValues()
{
m_TestGo = new GameObject("TestObj", typeof(NavMeshLink));
m_Link = m_TestGo.GetComponent<NavMeshLink>();
m_Link.costModifier = 42;
Unsupported.SmartReset(m_Link);
AssertNavMeshLinkHasDefaultValues(m_Link);
}
static void AssertNavMeshLinkHasDefaultValues(NavMeshLink link)
{
var serializedObject = new SerializedObject(link);
serializedObject.Update();
var serializedVersionProperty = serializedObject.FindProperty("m_SerializedVersion");
var agentTypeIdProperty = serializedObject.FindProperty("m_AgentTypeID");
var startPointProperty = serializedObject.FindProperty("m_StartPoint");
var endPointProperty = serializedObject.FindProperty("m_EndPoint");
var startTransformProperty = serializedObject.FindProperty("m_StartTransform");
var endTransformProperty = serializedObject.FindProperty("m_EndTransform");
var activatedProperty = serializedObject.FindProperty("m_Activated");
var widthProperty = serializedObject.FindProperty("m_Width");
var costModifierProperty = serializedObject.FindProperty("m_CostModifier");
var isOverridingCostProperty = serializedObject.FindProperty("m_IsOverridingCost");
var bidirectionalProperty = serializedObject.FindProperty("m_Bidirectional");
var autoUpdatePositionProperty = serializedObject.FindProperty("m_AutoUpdatePosition");
var areaProperty = serializedObject.FindProperty("m_Area");
Assume.That(serializedVersionProperty, Is.Not.Null, "Cannot find property 'm_SerializedVersion' in NavMeshLink.");
Assume.That(agentTypeIdProperty, Is.Not.Null, "Cannot find property 'm_AgentTypeID' in NavMeshLink.");
Assume.That(startPointProperty, Is.Not.Null, "Cannot find property 'm_StartPoint' in NavMeshLink.");
Assume.That(endPointProperty, Is.Not.Null, "Cannot find property 'm_EndPoint' in NavMeshLink.");
Assume.That(startTransformProperty, Is.Not.Null, "Cannot find property 'm_StartTransform' in NavMeshLink.");
Assume.That(endTransformProperty, Is.Not.Null, "Cannot find property 'm_EndTransform' in NavMeshLink.");
Assume.That(activatedProperty, Is.Not.Null, "Cannot find property 'm_Activated' in NavMeshLink.");
Assume.That(widthProperty, Is.Not.Null, "Cannot find property 'm_Width' in NavMeshLink.");
Assume.That(costModifierProperty, Is.Not.Null, "Cannot find property 'm_CostModifier' in NavMeshLink.");
Assume.That(isOverridingCostProperty, Is.Not.Null, "Cannot find property 'm_IsOverridingCost' in NavMeshLink.");
Assume.That(bidirectionalProperty, Is.Not.Null, "Cannot find property 'm_Bidirectional' in NavMeshLink.");
Assume.That(autoUpdatePositionProperty, Is.Not.Null, "Cannot find property 'm_AutoUpdatePosition' in NavMeshLink.");
Assume.That(areaProperty, Is.Not.Null, "Cannot find property 'm_Area' in NavMeshLink.");
Assert.That(serializedVersionProperty.intValue, Is.EqualTo(1), "Serialized version is not 1.");
Assert.That(agentTypeIdProperty.intValue, Is.EqualTo(0), "Agent type ID is not 0.");
Assert.That(startPointProperty.vector3Value, Is.EqualTo(new Vector3(0f, 0f, -2.50f)), "Start point is not at (0, 0, -2.50).");
Assert.That(endPointProperty.vector3Value, Is.EqualTo(new Vector3(0f, 0f, 2.50f)), "End point is not at (0, 0, 2.50).");
Assert.That(startTransformProperty.objectReferenceValue, Is.Null, "Start transform is not null.");
Assert.That(endTransformProperty.objectReferenceValue, Is.Null, "End transform is not null.");
Assert.That(activatedProperty.boolValue, Is.True, "Link is not activated.");
Assert.That(widthProperty.floatValue, Is.Zero, "Width is not 0.");
Assert.That(bidirectionalProperty.boolValue, Is.True, "Link is not bidirectional.");
Assert.That(autoUpdatePositionProperty.boolValue, Is.False, "Auto update position is true.");
Assert.That(areaProperty.intValue, Is.EqualTo(0), "Area is not 0.");
Assert.That(costModifierProperty.floatValue, Is.EqualTo(1f), "Cost modifier is not 1.");
Assert.That(isOverridingCostProperty.boolValue, Is.False, "Is overriding cost is true.");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 612fb35aac99445d9d7560fe6ad2c4f4
timeCreated: 1700481746

View File

@@ -0,0 +1,167 @@
#if UNITY_EDITOR || UNITY_STANDALONE
//#define KEEP_ARTIFACTS_FOR_INSPECTION
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
class NavMeshLinkUpgradeInPrefabTests
{
// Adjust these tests whenever you change the content of the prefab
const string k_PrefabName = "TestBundleWithAllLinks.prefab";
const string k_ParentFolder = "Assets";
const string k_TempFolder = "TempLinkUpgrade";
static readonly string k_TempFolderPath = Path.Combine(k_ParentFolder, k_TempFolder);
static readonly string k_TestPrefabPath = Path.Combine(k_TempFolderPath, k_PrefabName);
static readonly string k_PrebuiltPrefabPath = Path.Combine("Packages", "com.unity.ai.navigation", "Tests", "PrebuiltAssets~", k_PrefabName);
[SerializeField]
string m_PreviousScenePath;
[SerializeField]
GameObject m_LinkPrefab;
GameObject m_PrefabInstance;
[OneTimeSetUp]
public void OneTimeSetUp()
{
AssetDatabase.DeleteAsset(k_TempFolderPath);
var folderGUID = AssetDatabase.CreateFolder(k_ParentFolder, k_TempFolder);
Assume.That(folderGUID, Is.Not.Empty);
File.Copy(k_PrebuiltPrefabPath, k_TestPrefabPath);
AssetDatabase.Refresh();
m_LinkPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(k_TestPrefabPath);
m_PreviousScenePath = SceneManager.GetActiveScene().path;
}
[UnitySetUp]
public IEnumerator SetUp()
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
yield return null;
}
[UnityTearDown]
public IEnumerator TearDown()
{
Object.DestroyImmediate(m_PrefabInstance);
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (string.IsNullOrEmpty(m_PreviousScenePath))
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
else
EditorSceneManager.OpenScene(m_PreviousScenePath);
#if !KEEP_ARTIFACTS_FOR_INSPECTION
AssetDatabase.DeleteAsset(k_TempFolderPath);
#endif
}
[Test]
public void PrefabLinkVersion2_0_0_Instantiated_WarnsAboutOutdatedFormat()
{
ForgetPastWarnings();
LogAssert.Expect(LogType.Warning, new Regex("A NavMesh Link component has an outdated format..*"));
m_PrefabInstance = TestUtility.InstantiatePrefab(m_LinkPrefab, "Links Prefab Instance");
Assume.That(m_PrefabInstance, Is.Not.Null);
#if KEEP_ARTIFACTS_FOR_INSPECTION
var sceneWithPrefab = Path.Combine(k_TempFolderPath, "SceneWithPrefab1.unity");
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), sceneWithPrefab);
#endif
}
[Test]
public void PrefabLinkVersion2_0_0_WhenInstantiated_KeepsReferencesToOtherObjects()
{
m_PrefabInstance = TestUtility.InstantiatePrefab(m_LinkPrefab, "Links Prefab Instance");
#if KEEP_ARTIFACTS_FOR_INSPECTION
var sceneWithPrefab = Path.Combine(k_TempFolderPath, "SceneWithPrefab2.unity");
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), sceneWithPrefab);
#endif
var scaledParent = m_PrefabInstance.transform.Find("Scaled_Links_Root");
Assume.That(scaledParent, Is.Not.Null, "There should be a Scaled_Links_Root object.");
var objectWithLinks = scaledParent.transform.Find("Multiple NavMesh Links");
Assume.That(objectWithLinks, Is.Not.Null, "There should be a Multiple NavMesh Links object.");
var links = objectWithLinks.GetComponents<NavMeshLink>();
Assume.That(links.Length, Is.EqualTo(4));
foreach (var navMeshLink in links)
{
// Differentiate links by width
if (Math.Abs(navMeshLink.width - 1f) < 0.1f)
{
Assert.That(navMeshLink.startTransform, Is.Null,
"Start Transform self-reference should have been removed in first link.");
Assert.That(navMeshLink.endTransform, Is.Null,
"End Transform self-reference should have been removed in first link.");
}
else if (Math.Abs(navMeshLink.width - 2f) < 0.1f)
{
Assert.That(navMeshLink.startTransform.name, Is.EqualTo("Bundle Plane 2"),
"Start Transform should reference Bundle Plane 2 in second link.");
Assert.That(navMeshLink.endTransform, Is.SameAs(navMeshLink.startTransform),
"Start Transform and End Transform should reference the same object in second link.");
}
}
}
[UnityTest]
[Explicit("Entering playmode is rather slow and the situation being tested happens rarely")]
public IEnumerator PrefabLinkVersion2_0_0_WhenInstantiatedInPlaymode_WarnsAboutOutdatedReferences()
{
yield return new EnterPlayMode();
LogAssert.Expect(LogType.Warning, new Regex(
"The NavMesh Link component does not reference the intended transforms.*"));
m_PrefabInstance = TestUtility.InstantiatePrefab(m_LinkPrefab, "Links Prefab Instance In Playmode");
var scaledObjectWithLinks = GameObject.Find("Multiple NavMesh Links");
Assume.That(scaledObjectWithLinks, Is.Not.Null);
var linksFromScaledObject = scaledObjectWithLinks.GetComponents<NavMeshLink>();
Assume.That(linksFromScaledObject.Length, Is.EqualTo(4));
var unscaledObjectWithLinks = GameObject.Find("Unscaled Links");
Assume.That(unscaledObjectWithLinks, Is.Not.Null);
var linksFromUnscaledObject = unscaledObjectWithLinks.GetComponents<NavMeshLink>();
Assume.That(linksFromUnscaledObject.Length, Is.EqualTo(3));
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
static void ForgetPastWarnings()
{
var lastWarnedPrefab = typeof(NavMeshLink).GetField("s_LastWarnedPrefab",
BindingFlags.Static | BindingFlags.NonPublic);
Assume.That(lastWarnedPrefab, Is.Not.Null,
"Correct the test script if NavMeshLink.s_LastWarnedPrefab has been renamed or removed.");
lastWarnedPrefab?.SetValue(null, null);
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bee8f0175a385304086f01e517a21ff8

View File

@@ -0,0 +1,228 @@
#if UNITY_EDITOR || UNITY_STANDALONE
//#define KEEP_ARTIFACTS_FOR_INSPECTION
using System;
using System.Collections;
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
namespace Unity.AI.Navigation.Editor.Tests
{
class NavMeshLinkUpgradeTests
{
// Adjust these tests whenever you change the content of the scene
const string k_SceneName = "Test_Links_Created_With_2_0_0.unity";
const string k_ParentFolder = "Assets";
const string k_TempFolder = "TempLinkUpgrade";
static readonly string k_TempFolderPath = Path.Combine(k_ParentFolder, k_TempFolder);
static readonly string k_TestScenePath = Path.Combine(k_TempFolderPath, k_SceneName);
static readonly string k_PrebuiltScenePath = Path.Combine("Packages", "com.unity.ai.navigation", "Tests", "PrebuiltAssets~", k_SceneName);
string m_PreviousScenePath;
bool m_DelayCallHappened;
GameObject m_ScaledObjectWithLinks;
GameObject m_UnscaledObjectWithLinks;
NavMeshLink[] m_LinksFromScaledObject;
NavMeshLink[] m_LinksFromUnscaledObject;
[OneTimeSetUp]
public void OneTimeSetUp()
{
AssetDatabase.DeleteAsset(k_TempFolderPath);
var folderGUID = AssetDatabase.CreateFolder(k_ParentFolder, k_TempFolder);
Assume.That(folderGUID, Is.Not.Empty);
File.Copy(k_PrebuiltScenePath, k_TestScenePath);
AssetDatabase.Refresh();
m_PreviousScenePath = SceneManager.GetActiveScene().path;
var testScene = EditorSceneManager.OpenScene(k_TestScenePath);
Assume.That(testScene, Is.Not.Null, "The test scene should exist under the Assets folder.");
m_ScaledObjectWithLinks = GameObject.Find("Multiple NavMesh Links");
Assume.That(m_ScaledObjectWithLinks, Is.Not.Null);
m_LinksFromScaledObject = m_ScaledObjectWithLinks.GetComponents<NavMeshLink>();
Assume.That(m_LinksFromScaledObject.Length, Is.EqualTo(4));
m_UnscaledObjectWithLinks = GameObject.Find("Unscaled Links");
Assume.That(m_UnscaledObjectWithLinks, Is.Not.Null);
m_LinksFromUnscaledObject = m_UnscaledObjectWithLinks.GetComponents<NavMeshLink>();
Assume.That(m_LinksFromUnscaledObject.Length, Is.EqualTo(3));
}
[UnitySetUp]
public IEnumerator SetUp()
{
if (!m_DelayCallHappened)
{
// Allow for the Link upgrade to be completed by the delayCall
EditorApplication.delayCall += () => m_DelayCallHappened = true;
yield return new WaitUntil(() => m_DelayCallHappened);
yield return null;
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
}
}
[Test]
public void ScaledLinkVersion2_0_0_WhenLoaded_HasCorrectStart([NUnit.Framework.Range(0, 3)] int i)
{
var link = m_LinksFromScaledObject[i];
var desc = $"(scaled[{i}]_{link.width}_{link.costModifier})";
Assert.That(link.startTransform, Is.Null.Or.Not.SameAs(link.gameObject.transform),
"The start transform should not point to the link GameObject itself. {0}", desc);
if (link.startTransform != null)
{
var startParent = link.startTransform.parent;
if (startParent != null)
{
Assert.That(startParent.name, Is.EqualTo("Plane 2"),
"The start transform should be a child of Plane 2. {0}", desc);
Assert.That(link.startTransform.name, Contains.Substring($"Link Start {link.name}"),
"The new start transform should be named after the link that uses it. {0}", desc);
Assert.That(link.startTransform.position, Is.Not.EqualTo(Vector3.zero),
"The new start transform should be placed away from origin. {0}", desc);
Assert.That(link.startTransform.position, Is.Not.EqualTo(startParent.position),
"The new start transform should be placed away from the parent. {0}", desc);
}
else
{
Assert.That(link.startPoint, Is.EqualTo(Vector3.zero).Using(new Vector3EqualityComparer(0.001f)),
"Start Point should be around zero when start transform keeps the original reference. {0}", desc);
Assert.That(link.startTransform.name, Is.EqualTo("NavMesh Surface"),
"The upgraded link should keep referencing the GameObject NavMesh Surface. {0}", desc);
}
}
}
[Test]
public void ScaledLinkVersion2_0_0_WhenLoaded_HasCorrectEnd([NUnit.Framework.Range(0, 3)] int i)
{
var link = m_LinksFromScaledObject[i];
var desc = $"(scaled[{i}]_{link.width}_{link.costModifier})";
Assert.That(link.endTransform, Is.Null.Or.Not.SameAs(link.gameObject.transform),
"The end transform should not point to the link GameObject itself. {0}", desc);
if (link.endTransform != null)
{
var endParent = link.endTransform.parent;
if (endParent != null)
{
Assert.That(link.endTransform.parent.name, Is.EqualTo("Plane 2"),
"The end transform should be a child of Plane 2. {0}", desc);
Assert.That(link.endTransform.name, Contains.Substring($"Link End {link.name}"),
"The new end transform should be named after the link that uses it. {0}", desc);
Assert.That(link.endTransform.position, Is.Not.EqualTo(Vector3.zero),
"The new end transform should be placed away from origin. {0}", desc);
Assert.That(link.endTransform.position, Is.Not.EqualTo(endParent.position),
"The new end transform should be placed away from the parent. {0}", desc);
}
else
{
Assert.That(link.endPoint, Is.EqualTo(Vector3.zero).Using(new Vector3EqualityComparer(0.001f)),
"End Point should be around zero when end transform keeps the original reference. {0}", desc);
Assert.That(link.endTransform.name, Is.EqualTo("Plane 2"),
"The upgraded link should keep referencing the GameObject Plane 2. {0}", desc);
}
}
}
[Test]
public void UnscaledLinkVersion2_0_0_WhenLoaded_HasCorrectStart([NUnit.Framework.Range(0, 2)] int i)
{
var link = m_LinksFromUnscaledObject[i];
var desc = $"(unscaled[{i}]_{link.width}_{link.costModifier})";
Assert.That(link.startTransform, Is.Null.Or.Not.SameAs(link.gameObject.transform),
"The start transform should not point to the link GameObject itself. {0}", desc);
if (link.startTransform != null)
{
var startParent = link.startTransform.parent;
if (startParent != null)
{
Assert.That(startParent.name, Is.EqualTo("Plane 2"),
"The start transform should be a child of Plane 2. {0}", desc);
Assert.That(link.startTransform.name, Contains.Substring($"Link Start {link.name}"),
"The new start transform should be named after the link that uses it. {0}", desc);
Assert.That(link.startTransform.position, Is.Not.EqualTo(Vector3.zero),
"The new start transform should be placed away from origin. {0}", desc);
Assert.That(link.startTransform.position, Is.Not.EqualTo(startParent.position),
"The new start transform should be placed away from the parent. {0}", desc);
}
else
{
Assert.That(link.startPoint, Is.EqualTo(Vector3.zero).Using(new Vector3EqualityComparer(0.001f)),
"Start Point should be around zero when start transform keeps the original reference. {0}", desc);
Assert.That(link.startTransform.name, Is.EqualTo("NavMesh Surface"),
"The upgraded link should keep referencing the GameObject NavMesh Surface. {0}", desc);
}
}
}
[Test]
public void UnscaledLinkVersion2_0_0_WhenLoaded_HasCorrectEnd([NUnit.Framework.Range(0, 2)] int i)
{
var link = m_LinksFromUnscaledObject[i];
var desc = $"(unscaled[{i}]_{link.width}_{link.costModifier})";
Assert.That(link.endTransform, Is.Null.Or.Not.SameAs(link.gameObject.transform),
"The end transform should not point to the link GameObject itself. {0}", desc);
if (link.endTransform != null)
{
var endParent = link.endTransform.parent;
if (endParent != null)
{
Assert.That(link.endTransform.parent.name, Is.EqualTo("Plane 2"),
"The end transform should be a child of Plane 2. {0}", desc);
Assert.That(link.endTransform.name, Contains.Substring($"Link End {link.name}"),
"The new end transform should be named after the link that uses it. {0}", desc);
Assert.That(link.endTransform.position, Is.Not.EqualTo(Vector3.zero),
"The new end transform should be placed away from origin. {0}", desc);
Assert.That(link.endTransform.position, Is.Not.EqualTo(endParent.position),
"The new end transform should be placed away from the parent. {0}", desc);
}
else
{
Assert.That(link.endPoint, Is.EqualTo(Vector3.zero).Using(new Vector3EqualityComparer(0.001f)),
"End Point should be around zero when end transform keeps the original reference. {0}", desc);
Assert.That(link.endTransform.name, Is.EqualTo("Plane 2"),
"The upgraded link should keep referencing the GameObject Plane 2. {0}", desc);
}
}
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
Assume.That(SceneManager.GetActiveScene().isDirty, Is.False,
"Scene should not have changed after it was saved in SetUp.");
if (string.IsNullOrEmpty(m_PreviousScenePath))
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
else
EditorSceneManager.OpenScene(m_PreviousScenePath);
#if !KEEP_ARTIFACTS_FOR_INSPECTION
AssetDatabase.DeleteAsset(k_TempFolderPath);
#endif
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,57 @@
using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
internal class NavMeshModifierTests : DomainReloadTestBase
{
[SerializeField] NavMeshModifier m_Modifier;
[UnityTest]
public IEnumerator ActiveModifiers_AddOneItemToListInEditMode_ModifierListSetToZeroInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeModifiers = NavMeshModifier.activeModifiers;
Assume.That(activeModifiers, Is.Not.Null);
Assume.That(activeModifiers.Count, Is.Zero);
activeModifiers.Add(null);
Assume.That(activeModifiers.Count, Is.Not.Zero);
yield return new EnterPlayMode();
activeModifiers = NavMeshModifier.activeModifiers;
Assert.That(activeModifiers.Count, Is.Zero);
}
[UnityTest]
public IEnumerator ActiveModifiers_CreateModifierInEditMode_ModifierRemainsInActiveModifiersInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeModifiers = NavMeshModifier.activeModifiers;
Assume.That(activeModifiers, Is.Not.Null);
Assume.That(activeModifiers.Count, Is.Zero);
m_TestGo = new GameObject("TestObj", typeof(NavMeshModifier));
m_Modifier = m_TestGo.GetComponent<NavMeshModifier>();
activeModifiers = NavMeshModifier.activeModifiers;
Assume.That(activeModifiers, Is.Not.Null);
Assume.That(activeModifiers.Count, Is.EqualTo(1));
Assume.That(activeModifiers[0], Is.EqualTo(m_Modifier));
yield return new EnterPlayMode();
activeModifiers = NavMeshModifier.activeModifiers;
Assert.That(activeModifiers.Count, Is.EqualTo(1));
Assert.That(activeModifiers[0], Is.EqualTo(m_Modifier));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e8dd5168f86d4ab2b5f75d1991720402
timeCreated: 1700482678

View File

@@ -0,0 +1,278 @@
#if UNITY_EDITOR || UNITY_STANDALONE
//#define KEEP_ARTIFACTS_FOR_INSPECTION
using System.Collections;
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
[Category("PrefabsWithNavMeshModifierVolume")]
class NavMeshModifierVolumeInPrefabTests
{
const string k_AutoSaveKey = "AutoSave";
const string k_ParentFolder = "Assets";
const string k_TempFolderName = "TempPrefabAndModifiers";
static readonly string k_TempFolder = Path.Combine(k_ParentFolder, k_TempFolderName);
const int k_PinkArea = 3;
const int k_GreenArea = 4;
const int k_RedArea = 18;
const int k_PrefabDefaultArea = k_GreenArea;
static readonly NavMeshQueryFilter k_QueryAnyArea = new() { agentTypeID = 0, areaMask = NavMesh.AllAreas };
static bool s_EnterPlayModeOptionsEnabled;
static EnterPlayModeOptions s_EnterPlayModeOptions;
[SerializeField]
string m_PrefabPath;
[SerializeField]
string m_PreviousScenePath;
[SerializeField]
string m_TempScenePath;
[SerializeField]
int m_TestCounter;
[SerializeField]
GameObject m_SurfaceInstance;
[SerializeField]
GameObject m_ModVolInstance;
#if KEEP_ARTIFACTS_FOR_INSPECTION
const bool k_KeepSceneObjects = true;
#else
const bool k_KeepSceneObjects = false;
#endif
[OneTimeSetUp]
public void OneTimeSetup()
{
if (EditorApplication.isPlaying)
return;
AssetDatabase.DeleteAsset(k_TempFolder);
var folderGUID = AssetDatabase.CreateFolder(k_ParentFolder, k_TempFolderName);
Assume.That(folderGUID, Is.Not.Empty);
SessionState.SetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave());
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(false);
StageUtility.GoToMainStage();
m_PreviousScenePath = SceneManager.GetActiveScene().path;
m_TempScenePath = Path.Combine(k_TempFolder, "NavMeshModifierVolumePrefabTestsScene.unity");
var tempScene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
EditorSceneManager.SaveScene(tempScene, m_TempScenePath);
AssetDatabase.Refresh();
EditorSceneManager.OpenScene(m_TempScenePath);
s_EnterPlayModeOptionsEnabled = EditorSettings.enterPlayModeOptionsEnabled;
s_EnterPlayModeOptions = EditorSettings.enterPlayModeOptions;
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = EnterPlayModeOptions.DisableDomainReload | EnterPlayModeOptions.DisableSceneReload;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (EditorApplication.isPlaying)
return;
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(SessionState.GetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave()));
StageUtility.GoToMainStage();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
if (string.IsNullOrEmpty(m_PreviousScenePath))
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
EditorSettings.enterPlayModeOptionsEnabled = s_EnterPlayModeOptionsEnabled;
EditorSettings.enterPlayModeOptions = s_EnterPlayModeOptions;
#if !KEEP_ARTIFACTS_FOR_INSPECTION
AssetDatabase.DeleteAsset(k_TempFolder);
#endif
}
[SetUp]
public void SetupNewPrefabWithEmptyNavMesh()
{
if (EditorApplication.isPlaying)
return;
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
plane.name = "SurfaceSeekingModVol" + ++m_TestCounter + "Prefab";
var surface = plane.AddComponent<NavMeshSurface>();
surface.collectObjects = CollectObjects.All;
m_PrefabPath = Path.Combine(k_TempFolder, plane.name + ".prefab");
PrefabUtility.SaveAsPrefabAsset(plane, m_PrefabPath);
Object.DestroyImmediate(plane);
NavMesh.RemoveAllNavMeshData();
}
[UnityTearDown]
public IEnumerator TearDownAndReturnToMainStage()
{
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
StageUtility.GoToMainStage();
TestUtility.EliminateFromScene(ref m_ModVolInstance, k_KeepSceneObjects);
TestUtility.EliminateFromScene(ref m_SurfaceInstance, k_KeepSceneObjects);
yield return null;
}
[UnityTest]
public IEnumerator ModifierVolume_WhenInsidePrefabMode_ModifiesTheNavMeshInPrefab(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_SurfaceInstance = TestUtility.InstantiatePrefab(prefab, "SurfaceSeekingModVol" + m_TestCounter + "PrefabInstance");
NavMesh.SamplePosition(Vector3.zero, out var hit, 0.1f, k_QueryAnyArea);
Assume.That(hit.hit, Is.False, "Prefab should not have a NavMesh in the beginning.");
if (runMode == RunMode.PlayMode)
{
yield return new EnterPlayMode();
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
}
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var modifierVolume = prefabStage.prefabContentsRoot.AddComponent<NavMeshModifierVolume>();
modifierVolume.area = k_RedArea;
modifierVolume.center = Vector3.zero;
modifierVolume.size = Vector3.one;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_PrefabDefaultArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
NavMesh.SamplePosition(Vector3.zero, out var hitCenter, 0.1f, k_QueryAnyArea);
Assume.That(hitCenter.hit, Is.True, "A NavMesh should have been baked in the center of the prefab.");
Assert.That(hitCenter.mask, Is.EqualTo(1 << k_RedArea),
"Area type (0x{0:x8}) found in the center should be 0x{1:x8}.", hitCenter.mask, 1 << k_RedArea);
NavMesh.SamplePosition(new Vector3(0.6f, 0, 0.6f), out var hitSides, 0.1f, k_QueryAnyArea);
Assume.That(hitSides.hit, Is.True, "A NavMesh should have been baked in the outer sides of the prefab.");
Assert.That(hitSides.mask, Is.EqualTo(1 << k_PrefabDefaultArea),
"Area type (0x{0:x8}) found on the sides should be 0x{1:x8}.", hitSides.mask, 1 << k_PrefabDefaultArea);
Assert.That(hitCenter.mask, Is.Not.EqualTo(hitSides.mask),
"Area type (0x{0:x8}) in the center should be different than on the sides.", hitCenter.mask);
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
}
[UnityTest]
public IEnumerator ModifierVolume_WhenInsidePrefabMode_DoesNotAffectTheNavMeshInMainScene(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
m_SurfaceInstance = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_SurfaceInstance.name = "SurfaceOutsidePrefab" + m_TestCounter;
var mainSceneSurface = m_SurfaceInstance.AddComponent<NavMeshSurface>();
mainSceneSurface.defaultArea = k_PinkArea;
mainSceneSurface.agentTypeID = 0;
mainSceneSurface.collectObjects = CollectObjects.All;
NavMesh.SamplePosition(Vector3.zero, out var hit, 0.1f, k_QueryAnyArea);
Assume.That(hit.hit, Is.False, "The main scene should not have a NavMesh in the beginning.");
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabModVol = prefabStage.prefabContentsRoot.AddComponent<NavMeshModifierVolume>();
prefabModVol.area = k_PrefabDefaultArea;
prefabModVol.center = Vector3.zero;
prefabModVol.size = new Vector3(100, 100, 100);
// Bake the NavMeshSurface from the main scene while the prefab mode is open
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
mainSceneSurface = m_SurfaceInstance.GetComponent<NavMeshSurface>();
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
yield return TestUtility.BakeNavMeshAsync(mainSceneSurface, mainSceneSurface.defaultArea);
PrefabSavingUtil.SavePrefab(prefabStage);
if (!EditorApplication.isPlaying)
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
StageUtility.GoToMainStage();
NavMesh.SamplePosition(Vector3.zero, out hit, 0.1f, k_QueryAnyArea);
Assert.That(hit.hit, Is.True, "A NavMesh should have been baked by the surface in the main scene.");
Assert.That(hit.mask, Is.EqualTo(1 << mainSceneSurface.defaultArea),
"NavMesh has the area type 0x{0:x8} instead of the expected 0x{1:x8}.", hit.mask, 1 << mainSceneSurface.defaultArea);
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
[UnityTest]
public IEnumerator ModifierVolume_WhenOutsidePrefabMode_DoesNotAffectTheNavMeshInPrefab(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
m_ModVolInstance = new GameObject("ModifierVolumeOutsidePrefab" + m_TestCounter);
var modifierVolume = m_ModVolInstance.AddComponent<NavMeshModifierVolume>();
modifierVolume.area = k_RedArea;
modifierVolume.center = Vector3.zero;
modifierVolume.size = new Vector3(20, 20, 20);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_SurfaceInstance = TestUtility.InstantiatePrefab(prefab, "SurfaceSeekingModVol" + m_TestCounter + "PrefabInstance");
NavMesh.SamplePosition(Vector3.zero, out var hit, 0.1f, k_QueryAnyArea);
Assume.That(hit.hit, Is.False, "Prefab should not have a NavMesh in the beginning.");
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
if (runMode == RunMode.PlayMode)
{
yield return new EnterPlayMode();
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
}
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_PrefabDefaultArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
NavMesh.SamplePosition(Vector3.zero, out hit, 0.1f, k_QueryAnyArea);
Assume.That(hit.hit, Is.True, "A NavMesh should have been baked in the prefab.");
Assert.That(hit.mask, Is.EqualTo(1 << k_PrefabDefaultArea),
"A different area type (0x{0:x8}) was found instead of the expected one (0x{1:x8}).", hit.mask, 1 << k_PrefabDefaultArea);
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,55 @@
using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
internal class NavMeshModifierVolumeTests : DomainReloadTestBase
{
[SerializeField] NavMeshModifierVolume m_Modifier;
[UnityTest]
public IEnumerator ActiveModifiers_AddOneItemToListInEditMode_ModifierListSetToZeroInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeModifiers = NavMeshModifierVolume.activeModifiers;
Assume.That(activeModifiers, Is.Not.Null);
Assume.That(activeModifiers.Count, Is.Zero);
activeModifiers.Add(null);
Assume.That(activeModifiers.Count, Is.Not.Zero);
yield return new EnterPlayMode();
activeModifiers = NavMeshModifierVolume.activeModifiers;
Assert.That(activeModifiers.Count, Is.Zero);
}
[UnityTest]
public IEnumerator ActiveModifiers_CreateModifierInEditMode_ModifierRemainsInActiveModifiersInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeModifiers = NavMeshModifierVolume.activeModifiers;
Assume.That(activeModifiers, Is.Not.Null);
Assume.That(activeModifiers.Count, Is.Zero);
m_TestGo = new GameObject("TestObj", typeof(NavMeshModifierVolume));
m_Modifier = m_TestGo.GetComponent<NavMeshModifierVolume>();
Assume.That(activeModifiers.Count, Is.EqualTo(1));
Assume.That(activeModifiers[0], Is.EqualTo(m_Modifier));
yield return new EnterPlayMode();
activeModifiers = NavMeshModifierVolume.activeModifiers;
Assert.That(activeModifiers.Count, Is.EqualTo(1));
Assert.That(activeModifiers[0], Is.EqualTo(m_Modifier));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3662a816aa4c42c98966d59a714ae215
timeCreated: 1700483523

View File

@@ -0,0 +1,905 @@
#if UNITY_EDITOR || UNITY_STANDALONE
//#define KEEP_ARTIFACTS_FOR_INSPECTION
//#define ENABLE_TEST_LOGS
using System.Collections;
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
[Category("PrefabsWithNavMeshComponents")]
class NavMeshSurfaceInPrefabTests
{
const string k_AutoSaveKey = "AutoSave";
const string k_ParentFolder = "Assets";
const string k_TempFolderName = "TempPrefab";
static readonly string k_TempFolder = Path.Combine(k_ParentFolder, k_TempFolderName);
const int k_GrayArea = 7;
const int k_BrownArea = 10;
const int k_RedArea = 18;
const int k_OrangeArea = 26;
const int k_YellowArea = 30;
const int k_PrefabDefaultArea = k_YellowArea;
static bool s_EnterPlayModeOptionsEnabled;
static EnterPlayModeOptions s_EnterPlayModeOptions;
[SerializeField]
string m_PrefabPath;
[SerializeField]
string m_PreviousScenePath;
[SerializeField]
string m_TempScenePath;
[SerializeField]
int m_TestCounter;
[SerializeField]
GameObject m_MainInstance;
[SerializeField]
GameObject m_SecondInstance;
#if KEEP_ARTIFACTS_FOR_INSPECTION
const bool k_KeepSceneObjects = true;
#else
const bool k_KeepSceneObjects = false;
#endif
[OneTimeSetUp]
public void OneTimeSetup()
{
// Skip the entire setup phase that runs again each time an editor test enters playmode
if (EditorApplication.isPlaying)
return;
AssetDatabase.DeleteAsset(k_TempFolder);
var folderGUID = AssetDatabase.CreateFolder(k_ParentFolder, k_TempFolderName);
Assume.That(folderGUID, Is.Not.Empty);
SessionState.SetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave());
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(false);
StageUtility.GoToMainStage();
m_PreviousScenePath = SceneManager.GetActiveScene().path;
m_TempScenePath = Path.Combine(k_TempFolder, "NavMeshSurfacePrefabTestsScene.unity");
var tempScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
EditorSceneManager.SaveScene(tempScene, m_TempScenePath);
EditorSceneManager.OpenScene(m_TempScenePath);
s_EnterPlayModeOptionsEnabled = EditorSettings.enterPlayModeOptionsEnabled;
s_EnterPlayModeOptions = EditorSettings.enterPlayModeOptions;
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = EnterPlayModeOptions.DisableDomainReload | EnterPlayModeOptions.DisableSceneReload;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (EditorApplication.isPlaying)
return;
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(SessionState.GetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave()));
StageUtility.GoToMainStage();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
if (string.IsNullOrEmpty(m_PreviousScenePath))
{
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
}
EditorSettings.enterPlayModeOptionsEnabled = s_EnterPlayModeOptionsEnabled;
EditorSettings.enterPlayModeOptions = s_EnterPlayModeOptions;
#if !KEEP_ARTIFACTS_FOR_INSPECTION
AssetDatabase.DeleteAsset(k_TempFolder);
#endif
}
[UnitySetUp]
public IEnumerator SetupNewPrefabWithNavMesh()
{
if (EditorApplication.isPlaying)
yield break;
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
plane.name = "NavMeshSurface" + (++m_TestCounter) + "Prefab";
var surface = plane.AddComponent<NavMeshSurface>();
surface.collectObjects = CollectObjects.Children;
m_PrefabPath = Path.Combine(k_TempFolder, plane.name + ".prefab");
var planePrefab = PrefabUtility.SaveAsPrefabAsset(plane, m_PrefabPath);
Object.DestroyImmediate(plane);
AssetDatabase.OpenAsset(planePrefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_PrefabDefaultArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
NavMesh.RemoveAllNavMeshData();
yield return null;
}
[UnityTearDown]
public IEnumerator TearDownAndReturnToMainStage()
{
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
prefabStage.ClearDirtiness();
StageUtility.GoToMainStage();
TestUtility.EliminateFromScene(ref m_MainInstance, k_KeepSceneObjects);
TestUtility.EliminateFromScene(ref m_SecondInstance, k_KeepSceneObjects);
yield return null;
}
static void TestNavMeshExistsAloneAtPosition(int expectedArea, Vector3 pos)
{
var expectedAreaMask = 1 << expectedArea;
#if ENABLE_TEST_LOGS
var areaExists = HasNavMeshAtPosition(pos, expectedAreaMask);
var otherAreasExist = HasNavMeshAtPosition(pos, ~expectedAreaMask);
Debug.Log(" mask=" + expectedAreaMask.ToString("x8") + " area " + expectedArea +
" Exists=" + areaExists + " otherAreasExist=" + otherAreasExist + " at position " + pos);
if (otherAreasExist)
{
for (var i = 0; i < 32; i++)
{
if (i == expectedArea)
continue;
var thisOtherAreaExists = HasNavMeshAtPosition(pos, 1 << i);
if (thisOtherAreaExists)
{
Debug.Log(" _another area that exists here " + i);
}
}
}
#endif
Assert.IsTrue(HasNavMeshAtPosition(pos, expectedAreaMask), "Expected NavMesh with area {0} at position {1}.", expectedArea, pos);
Assert.IsFalse(HasNavMeshAtPosition(pos, ~expectedAreaMask), "A NavMesh with an area other than {0} exists at position {1}.", expectedArea, pos);
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenOpenedInPrefabMode_DoesNotActivateItsNavMesh(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
NavMesh.SamplePosition(Vector3.zero, out var hit, 1000000f, new NavMeshQueryFilter { areaMask = NavMesh.AllAreas, agentTypeID = 0 });
Assert.That(hit.hit, Is.False, "The NavMesh instance of a prefab opened for edit should not be active under any circumstances.");
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_AfterBakingInPrefabMode_DoesNotActivateItsNavMesh(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
NavMeshAssetManager.instance.ClearSurfaces(new Object[] { prefabSurface });
PrefabSavingUtil.SavePrefab(prefabStage);
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_RedArea);
NavMesh.SamplePosition(Vector3.zero, out var hit, 1000000f, new NavMeshQueryFilter { areaMask = NavMesh.AllAreas, agentTypeID = 0 });
Assert.That(hit.hit, Is.False, "The NavMesh instance of a prefab opened for edit should not be active after baking the surface.");
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
PrefabSavingUtil.SavePrefab(prefabStage);
NavMesh.SamplePosition(Vector3.zero, out hit, 1000000f, new NavMeshQueryFilter { areaMask = NavMesh.AllAreas, agentTypeID = 0 });
Assert.That(hit.hit, Is.False, "The NavMesh instance of a prefab opened for edit should not be active after baking the surface.");
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_AfterBakingInPrefabMode_LeavesMainSceneUntouched(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
Assume.That(HasNavMeshAtPosition(Vector3.zero), Is.False);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
Assume.That(prefabStage, Is.Not.Null);
Assume.That(prefabStage.prefabContentsRoot, Is.Not.Null);
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialPrefabNavMeshData = prefabSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_RedArea);
Assert.AreNotSame(initialPrefabNavMeshData, prefabSurface.navMeshData);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
yield return null;
Assert.IsFalse(HasNavMeshAtPosition(Vector3.zero, NavMesh.AllAreas, 0, 1000.0f));
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstantiated_ReferencesTheSameNavMeshData(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
var instanceNavMeshData = instanceSurface.navMeshData;
var clonePosition = new Vector3(20, 0, 0);
m_SecondInstance = Object.Instantiate(m_MainInstance, clonePosition, Quaternion.identity);
Assume.That(m_SecondInstance, Is.Not.Null);
m_SecondInstance.name = "Surface" + m_TestCounter + "PrefabInstanceClone";
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsTrue(HasNavMeshAtPosition(clonePosition, expectedAreaMask));
Assert.IsFalse(HasNavMeshAtPosition(clonePosition, ~expectedAreaMask));
var instanceCloneSurface = m_SecondInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceCloneSurface, Is.Not.Null);
var instanceCloneNavMeshData = instanceCloneSurface.navMeshData;
Assert.AreSame(instanceNavMeshData, instanceCloneNavMeshData);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
Assume.That(prefabStage, Is.Not.Null);
Assume.That(prefabStage.prefabContentsRoot, Is.Not.Null);
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurface.navMeshData;
Assert.AreSame(prefabNavMeshData, instanceNavMeshData);
StageUtility.GoToMainStage();
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstantiatedAndCleared_InstanceHasEmptyNavMeshData(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface.navMeshData != null, "NavMeshSurface in prefab instance must have NavMeshData.");
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
NavMeshAssetManager.instance.ClearSurfaces(new Object[] { prefabSurface });
PrefabSavingUtil.SavePrefab(prefabStage);
if (EditorApplication.isPlaying)
{
yield return new ExitPlayMode();
instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
}
StageUtility.GoToMainStage();
Assert.IsTrue(instanceSurface.navMeshData == null,
"After the NavMeshSurface in the prefab has been cleared the prefab instance should no longer hold NavMeshData.");
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsFalse(HasNavMeshAtPosition(Vector3.zero, expectedAreaMask));
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenBakesNewNavMesh_UpdatesTheInstance(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstanceOne");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, m_MainInstance.transform.position);
AssetDatabase.OpenAsset(prefab);
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, m_MainInstance.transform.position);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_RedArea);
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, m_MainInstance.transform.position);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
m_SecondInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstanceTwo");
// Reactivate the object to apply the change of position immediately
m_SecondInstance.SetActive(false);
m_SecondInstance.transform.position = new Vector3(20, 0, 0);
m_SecondInstance.SetActive(true);
// Check that the second prefab instance has the new prefab area type
TestNavMeshExistsAloneAtPosition(k_RedArea, m_SecondInstance.transform.position);
// Only in edit mode, check that the prefab change has been picked up by the first instance
if (!EditorApplication.isPlaying)
{
TestNavMeshExistsAloneAtPosition(k_RedArea, m_MainInstance.transform.position);
// Modify the first instance
var instanceOneSurface = m_MainInstance.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(instanceOneSurface, k_BrownArea);
// Check that the first prefab instance kept its modified area type
TestNavMeshExistsAloneAtPosition(k_BrownArea, m_MainInstance.transform.position);
}
else
{
// After the prefab has been saved the running prefab instance should still have the old NavMeshData
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, m_MainInstance.transform.position);
}
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstanceRebaked_HasDifferentNavMeshData(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
var clonePosition = new Vector3(20, 0, 0);
m_SecondInstance = Object.Instantiate(m_MainInstance, clonePosition, Quaternion.identity);
Assume.That(m_SecondInstance, Is.Not.Null);
m_SecondInstance.name = "Surface" + m_TestCounter + "PrefabInstanceClone";
var mainSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(mainSurface, Is.Not.Null);
yield return TestUtility.BakeNavMeshAsync(mainSurface, k_RedArea);
var mainNavMeshData = mainSurface.navMeshData;
TestNavMeshExistsAloneAtPosition(k_RedArea, m_MainInstance.transform.position);
// For when multiple instances of the same NavMesh prefab modify their data in playmode the behavior is currently undefined
if (runMode != RunMode.PlayMode)
{
var cloneSurface = m_SecondInstance.GetComponent<NavMeshSurface>();
Assert.IsTrue(cloneSurface.navMeshData != null, "The clone should still have NavMesh data.");
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsTrue(HasNavMeshAtPosition(clonePosition, expectedAreaMask), "The clone should still reference the prefab's data.");
Assert.IsFalse(HasNavMeshAtPosition(clonePosition, ~expectedAreaMask));
}
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurface.navMeshData;
Assert.AreNotSame(mainNavMeshData, prefabNavMeshData);
if (runMode != RunMode.PlayMode)
{
var instanceCloneSurface = m_SecondInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceCloneSurface, Is.Not.Null);
var instanceCloneNavMeshData = instanceCloneSurface.navMeshData;
Assert.AreNotSame(instanceCloneNavMeshData, mainNavMeshData);
Assert.That(instanceCloneNavMeshData, Is.EqualTo(prefabNavMeshData));
}
StageUtility.GoToMainStage();
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstanceCleared_InstanceHasEmptyNavMeshData()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
var clonePosition = new Vector3(20, 0, 0);
m_SecondInstance = Object.Instantiate(m_MainInstance, clonePosition, Quaternion.identity);
Assume.That(m_SecondInstance, Is.Not.Null);
m_SecondInstance.name = "Surface" + m_TestCounter + "PrefabInstanceClone";
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
NavMeshAssetManager.instance.ClearSurfaces(new Object[] { instanceSurface });
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsFalse(HasNavMeshAtPosition(Vector3.zero, expectedAreaMask));
Assert.IsTrue(HasNavMeshAtPosition(clonePosition, expectedAreaMask));
Assert.IsFalse(HasNavMeshAtPosition(clonePosition, ~expectedAreaMask));
var instanceCloneSurface = m_SecondInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceCloneSurface, Is.Not.Null);
var instanceCloneNavMeshData = instanceCloneSurface.navMeshData;
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurface.navMeshData;
Assert.AreNotSame(prefabNavMeshData, instanceSurface.navMeshData);
Assert.AreNotSame(instanceCloneNavMeshData, instanceSurface.navMeshData);
Assert.AreSame(prefabNavMeshData, instanceCloneNavMeshData);
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstanceCleared_PrefabKeepsNavMeshData()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
var initialPrefabNavMeshData = instanceSurface.navMeshData;
NavMeshAssetManager.instance.ClearSurfaces(new Object[] { instanceSurface });
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsFalse(HasNavMeshAtPosition(Vector3.zero, expectedAreaMask));
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurface.navMeshData;
Assert.IsTrue(prefabNavMeshData != null,
"NavMeshSurface in the prefab must still have NavMeshData even though the instance was cleared.");
Assert.AreSame(initialPrefabNavMeshData, prefabNavMeshData);
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenRebakedButInstanceModified_DoesNotChangeDataReferencedByInstance(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
yield return TestUtility.BakeNavMeshAsync(instanceSurface, k_RedArea);
var instanceNavMeshData = instanceSurface.navMeshData;
TestNavMeshExistsAloneAtPosition(k_RedArea, Vector3.zero);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialPrefabNavMeshData = prefabSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
AssetDatabase.OpenAsset(prefab);
var prefabStageReopened = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurfaceReopened = prefabStageReopened.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurfaceReopened.navMeshData;
Assert.IsTrue(prefabNavMeshData != null,
"NavMeshSurface in prefab must have NavMeshData after baking, saving, closing and reopening.");
Assert.AreNotSame(instanceNavMeshData, prefabNavMeshData);
Assert.AreNotSame(initialPrefabNavMeshData, prefabNavMeshData);
StageUtility.GoToMainStage();
Assert.AreSame(instanceNavMeshData, instanceSurface.navMeshData);
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenRebakedButNotSaved_RevertsToTheInitialNavMeshData()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialPrefabNavMeshData = prefabSurface.navMeshData;
var initialPrefabNavMeshAssetPath = AssetDatabase.GetAssetPath(initialPrefabNavMeshData);
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
var rebuiltPrefabNavMeshData = prefabSurface.navMeshData;
Assert.IsTrue(rebuiltPrefabNavMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
Assert.AreNotSame(initialPrefabNavMeshData, rebuiltPrefabNavMeshData);
prefabStage.ClearDirtiness();
StageUtility.GoToMainStage();
AssetDatabase.OpenAsset(prefab);
var prefabStageReopened = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurfaceReopened = prefabStageReopened.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurfaceReopened.navMeshData;
Assert.AreSame(initialPrefabNavMeshData, prefabNavMeshData);
Assert.AreNotSame(rebuiltPrefabNavMeshData, prefabNavMeshData);
var prefabNavMeshAssetPath = AssetDatabase.GetAssetPath(prefabNavMeshData);
StringAssert.AreEqualIgnoringCase(initialPrefabNavMeshAssetPath, prefabNavMeshAssetPath,
"The NavMeshData asset referenced by the prefab should remain the same when exiting prefab mode without saving.");
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenRebakedButNotSaved_TheRebakedAssetNoLongerExists()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
var rebakedAssetPath = AssetDatabase.GetAssetPath(prefabSurface.navMeshData);
Assert.IsTrue(File.Exists(rebakedAssetPath), "NavMeshData file must exist. ({0})", rebakedAssetPath);
prefabStage.ClearDirtiness();
StageUtility.GoToMainStage();
Assert.IsFalse(File.Exists(rebakedAssetPath), "NavMeshData file still exists after discarding the changes. ({0})", rebakedAssetPath);
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenRebaked_TheOldAssetExistsUntilSavingAndNotAfter()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialNavMeshData = prefabSurface.navMeshData;
var initialAssetPath = AssetDatabase.GetAssetPath(prefabSurface.navMeshData);
Assume.That(initialNavMeshData != null, "Prefab must have some NavMeshData.");
Assume.That(File.Exists(initialAssetPath), Is.True, "NavMeshData file must exist. ({0})", initialAssetPath);
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
Assert.IsTrue(initialNavMeshData != null, "The initial NavMeshData must still exist immediately after prefab re-bake.");
Assert.IsTrue(File.Exists(initialAssetPath), "The initial NavMeshData file must exist after prefab re-bake. ({0})", initialAssetPath);
Assert.IsTrue(prefabSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
var unsavedRebakedNavMeshData = prefabSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_OrangeArea);
// Assert.IsNull would return a wrong result here (e.g. Expected: null But was: <null>)
Assert.IsTrue(unsavedRebakedNavMeshData == null,"An unsaved NavMeshData should not exist after a re-bake.");
Assert.IsTrue(prefabSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
PrefabSavingUtil.SavePrefab(prefabStage);
Assert.IsFalse(File.Exists(initialAssetPath), "NavMeshData file still exists after saving. ({0})", initialAssetPath);
Assert.IsTrue(initialNavMeshData == null, "The initial NavMeshData must no longer exist after saving the prefab.");
// This code is still reachable because initialNavMeshData has been affected by BakeNavMeshAsync()
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenRebakedAndAutoSaved_InstanceHasTheNewNavMeshData()
{
var wasAutoSave = PrefabStageAutoSavingUtil.GetPrefabStageAutoSave();
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(true);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialPrefabNavMeshData = prefabSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
var rebuiltPrefabNavMeshData = prefabSurface.navMeshData;
Assert.IsTrue(rebuiltPrefabNavMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
Assert.AreNotSame(initialPrefabNavMeshData, rebuiltPrefabNavMeshData);
StageUtility.GoToMainStage();
AssetDatabase.OpenAsset(prefab);
var prefabStageReopened = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurfaceReopened = prefabStageReopened.prefabContentsRoot.GetComponent<NavMeshSurface>();
var prefabNavMeshData = prefabSurfaceReopened.navMeshData;
Assert.AreNotSame(initialPrefabNavMeshData, prefabNavMeshData);
Assert.AreSame(rebuiltPrefabNavMeshData, prefabNavMeshData);
StageUtility.GoToMainStage();
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(wasAutoSave);
yield return null;
}
[Ignore("Currently the deletion of the old asset must be done manually.")]
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_AfterModifiedInstanceAppliedBack_TheOldAssetNoLongerExists()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
var initialInstanceAssetPath = AssetDatabase.GetAssetPath(instanceSurface.navMeshData);
Assert.IsTrue(File.Exists(initialInstanceAssetPath), "Prefab's NavMeshData file must exist. ({0})", initialInstanceAssetPath);
yield return TestUtility.BakeNavMeshAsync(instanceSurface, k_RedArea);
Assert.IsTrue(File.Exists(initialInstanceAssetPath),
"Prefab's NavMeshData file exists after the instance has changed. ({0})", initialInstanceAssetPath);
PrefabUtility.ApplyPrefabInstance(m_MainInstance, InteractionMode.AutomatedAction);
Assert.IsFalse(File.Exists(initialInstanceAssetPath),
"Prefab's NavMeshData file still exists after the changes from the instance have been applied back to the prefab. ({0})",
initialInstanceAssetPath);
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_AfterModifiedInstanceAppliedBack_UpdatedAccordingToInstance()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstanceOne");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
m_SecondInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstanceTwo");
// reactivate the object to apply the change of position immediately
m_SecondInstance.SetActive(false);
m_SecondInstance.transform.position = new Vector3(20, 0, 0);
m_SecondInstance.SetActive(true);
var instanceOneSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceOneSurface, Is.Not.Null);
yield return TestUtility.BakeNavMeshAsync(instanceOneSurface, k_RedArea);
TestNavMeshExistsAloneAtPosition(k_RedArea, Vector3.zero);
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, m_SecondInstance.transform.position);
PrefabUtility.ApplyPrefabInstance(m_MainInstance, InteractionMode.AutomatedAction);
TestNavMeshExistsAloneAtPosition(k_RedArea, m_SecondInstance.transform.position);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_GrayArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
TestNavMeshExistsAloneAtPosition(k_GrayArea, Vector3.zero);
TestNavMeshExistsAloneAtPosition(k_GrayArea, m_SecondInstance.transform.position);
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_AfterClearedInstanceAppliedBack_HasEmptyData()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
NavMeshAssetManager.instance.ClearSurfaces(new Object[] { instanceSurface });
const int expectedAreaMask = 1 << k_PrefabDefaultArea;
Assert.IsFalse(HasNavMeshAtPosition(Vector3.zero, expectedAreaMask));
PrefabUtility.ApplyPrefabInstance(m_MainInstance, InteractionMode.AutomatedAction);
AssetDatabase.OpenAsset(prefab);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
Assert.IsTrue(prefabSurface.navMeshData == null,
"Prefab should have empty NavMeshData when empty data has been applied back from the instance.");
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstanceRevertsBack_InstanceIsLikePrefab()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
yield return TestUtility.BakeNavMeshAsync(instanceSurface, k_RedArea);
TestNavMeshExistsAloneAtPosition(k_RedArea, Vector3.zero);
PrefabUtility.RevertPrefabInstance(m_MainInstance, InteractionMode.AutomatedAction);
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
yield return null;
}
[Ignore("Deletion of the old asset is expected to be done manually for the time being.")]
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenInstanceRevertsBack_TheInstanceAssetNoLongerExists()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance");
TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero);
var instanceSurface = m_MainInstance.GetComponent<NavMeshSurface>();
Assume.That(instanceSurface, Is.Not.Null);
yield return TestUtility.BakeNavMeshAsync(instanceSurface, k_RedArea);
var instanceAssetPath = AssetDatabase.GetAssetPath(instanceSurface.navMeshData);
Assert.IsTrue(File.Exists(instanceAssetPath), "Instance's NavMeshData file must exist. ({0})", instanceAssetPath);
PrefabUtility.RevertPrefabInstance(m_MainInstance, InteractionMode.AutomatedAction);
Assert.IsFalse(File.Exists(instanceAssetPath), "Instance's NavMeshData file still exists after revert. ({0})", instanceAssetPath);
yield return null;
}
[Ignore("The expected behaviour has not been decided.")]
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenDeleted_InstancesMakeCopiesOfData(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
yield return null;
Assert.Fail("not implemented yet");
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefab_WhenBakingInPrefabModeScene_CollectsOnlyPrefabModeSceneObjects(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
m_SecondInstance = GameObject.CreatePrimitive(PrimitiveType.Plane);
var goName = "MainScenePlane" + m_TestCounter;
m_SecondInstance.name = goName;
m_SecondInstance.transform.localScale = new Vector3(100, 1, 100);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(prefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabSurface = prefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
prefabSurface.collectObjects = CollectObjects.All;
yield return TestUtility.BakeNavMeshAsync(prefabSurface, k_RedArea);
PrefabSavingUtil.SavePrefab(prefabStage);
StageUtility.GoToMainStage();
if (EditorApplication.isPlaying)
{
yield return new ExitPlayMode();
m_SecondInstance = GameObject.Find(goName);
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
}
m_MainInstance = TestUtility.InstantiatePrefab(prefab, "PrefabInstance" + m_TestCounter);
TestNavMeshExistsAloneAtPosition(k_RedArea, Vector3.zero);
var posNearby = new Vector3(20, 0, 0);
Assert.IsFalse(HasNavMeshAtPosition(posNearby, 1 << k_RedArea),
"NavMesh with the prefab's area exists at position {1}, outside the prefab's plane. ({0})",
k_RedArea, posNearby);
yield return null;
}
public static bool HasNavMeshAtPosition(Vector3 pos, int areaMask = NavMesh.AllAreas, int agentTypeId = 0, float range = 0.1f)
{
var filter = new NavMeshQueryFilter
{
areaMask = areaMask,
agentTypeID = agentTypeId
};
var result = NavMesh.SamplePosition(pos, out _, range, filter);
return result;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,289 @@
#if UNITY_EDITOR || UNITY_STANDALONE
//#define KEEP_ARTIFACTS_FOR_INSPECTION
using System.Collections;
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
[Category("PrefabsWithNavMeshComponents")]
class NavMeshSurfaceInPrefabVariantTests
{
const string k_AutoSaveKey = "AutoSave";
const string k_ParentFolder = "Assets";
const string k_TempFolderName = "TempPrefabVariants";
static readonly string k_TempFolder = Path.Combine(k_ParentFolder, k_TempFolderName);
const int k_GrayArea = 7;
const int k_BrownArea = 10;
const int k_RedArea = 18;
const int k_OrangeArea = 26;
const int k_YellowArea = 30;
const int k_PrefabDefaultArea = k_YellowArea;
static bool s_EnterPlayModeOptionsEnabled;
static EnterPlayModeOptions s_EnterPlayModeOptions;
[SerializeField]
string m_PrefabPath;
[SerializeField]
string m_PrefabVariantPath;
[SerializeField]
string m_PreviousScenePath;
[SerializeField]
string m_TempScenePath;
[SerializeField]
int m_TestCounter;
#if KEEP_ARTIFACTS_FOR_INSPECTION
const bool k_KeepSceneObjects = true;
#else
const bool k_KeepSceneObjects = false;
#endif
[OneTimeSetUp]
public void OneTimeSetup()
{
if (EditorApplication.isPlaying)
return;
AssetDatabase.DeleteAsset(k_TempFolder);
var folderGUID = AssetDatabase.CreateFolder(k_ParentFolder, k_TempFolderName);
Assume.That(folderGUID, Is.Not.Empty);
SessionState.SetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave());
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(false);
StageUtility.GoToMainStage();
m_PreviousScenePath = SceneManager.GetActiveScene().path;
m_TempScenePath = Path.Combine(k_TempFolder, "NavMeshSurfacePrefabVariantTestsScene.unity");
var tempScene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
EditorSceneManager.SaveScene(tempScene, m_TempScenePath);
EditorSceneManager.OpenScene(m_TempScenePath);
s_EnterPlayModeOptionsEnabled = EditorSettings.enterPlayModeOptionsEnabled;
s_EnterPlayModeOptions = EditorSettings.enterPlayModeOptions;
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = EnterPlayModeOptions.DisableDomainReload | EnterPlayModeOptions.DisableSceneReload;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (EditorApplication.isPlaying)
return;
PrefabStageAutoSavingUtil.SetPrefabStageAutoSave(SessionState.GetBool(k_AutoSaveKey, PrefabStageAutoSavingUtil.GetPrefabStageAutoSave()));
StageUtility.GoToMainStage();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene());
if (string.IsNullOrEmpty(m_PreviousScenePath))
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
EditorSettings.enterPlayModeOptionsEnabled = s_EnterPlayModeOptionsEnabled;
EditorSettings.enterPlayModeOptions = s_EnterPlayModeOptions;
#if !KEEP_ARTIFACTS_FOR_INSPECTION
AssetDatabase.DeleteAsset(k_TempFolder);
#endif
}
[UnitySetUp]
public IEnumerator SetupVariantOfPrefabWithNavMesh()
{
if (EditorApplication.isPlaying)
yield break;
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
plane.name = "NavMeshSurfacePrefab" + (++m_TestCounter);
var surface = plane.AddComponent<NavMeshSurface>();
surface.collectObjects = CollectObjects.Children;
m_PrefabPath = Path.Combine(k_TempFolder, plane.name + ".prefab");
m_PrefabVariantPath = Path.Combine(k_TempFolder, plane.name + "Variant.prefab");
var planePrefab = PrefabUtility.SaveAsPrefabAsset(plane, m_PrefabPath);
Object.DestroyImmediate(plane);
AssetDatabase.OpenAsset(planePrefab);
var theOriginalPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var theOriginalPrefabSurface = theOriginalPrefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(theOriginalPrefabSurface, k_PrefabDefaultArea);
PrefabSavingUtil.SavePrefab(theOriginalPrefabStage);
StageUtility.GoToMainStage();
var instanceForVariant = PrefabUtility.InstantiatePrefab(planePrefab) as GameObject;
PrefabUtility.SaveAsPrefabAsset(instanceForVariant, m_PrefabVariantPath);
instanceForVariant!.name += "_Saved";
TestUtility.EliminateFromScene(ref instanceForVariant, k_KeepSceneObjects);
NavMesh.RemoveAllNavMeshData();
yield return null;
}
[UnityTearDown]
public IEnumerator TearDown()
{
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
StageUtility.GoToMainStage();
yield return null;
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefabVariant_WhenFreshAndRebaked_ParentAssetUnchanged(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var theOriginalPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabPath);
AssetDatabase.OpenAsset(theOriginalPrefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var theOriginalPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var theOriginalPrefabSurface = theOriginalPrefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var theOriginalPrefabNavMeshData = theOriginalPrefabSurface.navMeshData;
var theOriginalPrefabAssetPath = AssetDatabase.GetAssetPath(theOriginalPrefabSurface.navMeshData);
Assert.IsTrue(theOriginalPrefabNavMeshData != null, "Original prefab must have some NavMeshData.");
Assert.IsTrue(File.Exists(theOriginalPrefabAssetPath), "NavMeshData file must exist for the original prefab. ({0})", theOriginalPrefabAssetPath);
var prefabVariant = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabVariantPath);
AssetDatabase.OpenAsset(prefabVariant);
var prefabVariantStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabVariantSurface = prefabVariantStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var initialVariantNavMeshData = prefabVariantSurface.navMeshData;
var initialVariantAssetPath = AssetDatabase.GetAssetPath(prefabVariantSurface.navMeshData);
Assert.AreEqual(theOriginalPrefabNavMeshData, initialVariantNavMeshData, "Fresh variant must have the same NavMeshData as the original prefab.");
Assert.IsTrue(initialVariantNavMeshData != null, "Prefab must have some NavMeshData.");
Assert.IsTrue(File.Exists(initialVariantAssetPath), "NavMeshData file must exist. ({0})", initialVariantAssetPath);
yield return TestUtility.BakeNavMeshAsync(prefabVariantSurface, k_GrayArea);
Assert.IsTrue(initialVariantNavMeshData != null, "The initial NavMeshData (from original prefab) must still exist immediately after prefab variant re-bake.");
Assert.IsTrue(File.Exists(initialVariantAssetPath), "The initial NavMeshData file (from original prefab) must exist after prefab variant re-bake. ({0})", initialVariantAssetPath);
Assert.IsTrue(prefabVariantSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
var unsavedRebakedNavMeshData = prefabVariantSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabVariantSurface, k_BrownArea);
Assert.IsTrue(unsavedRebakedNavMeshData == null, "An unsaved NavMeshData should not exist after a re-bake.");
Assert.IsTrue(prefabVariantSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
PrefabSavingUtil.SavePrefab(prefabVariantStage);
var theNewVariantNavMeshData = prefabVariantSurface.navMeshData;
var theNewVariantAssetPath = AssetDatabase.GetAssetPath(theNewVariantNavMeshData);
Assert.IsTrue(File.Exists(theNewVariantAssetPath), "Variant's own NavMeshData exists in a file after saving. ({0})", theNewVariantAssetPath);
Assert.IsTrue(File.Exists(theOriginalPrefabAssetPath), "NavMeshData file of the original prefab still exists after saving the variant. ({0})", theOriginalPrefabAssetPath);
Assert.IsTrue(theOriginalPrefabNavMeshData != null, "Original prefab must still have NavMeshData.");
StageUtility.GoToMainStage();
yield return null;
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
[UnityTest]
public IEnumerator NavMeshSurfacePrefabVariant_WhenCustomizedAndRebaked_OldAssetDiscardedAndParentAssetUnchanged(
[Values(RunMode.EditMode, RunMode.PlayMode)]
RunMode runMode)
{
var prefabVariant = AssetDatabase.LoadAssetAtPath<GameObject>(m_PrefabVariantPath);
var theOriginalPrefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefabVariant);
var theOriginalPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(theOriginalPrefabPath);
AssetDatabase.OpenAsset(theOriginalPrefab);
if (runMode == RunMode.PlayMode)
yield return new EnterPlayMode();
var theOriginalPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
var theOriginalPrefabSurface = theOriginalPrefabStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
var theOriginalPrefabNavMeshData = theOriginalPrefabSurface.navMeshData;
var theOriginalPrefabAssetPath = AssetDatabase.GetAssetPath(theOriginalPrefabSurface.navMeshData);
Assert.IsTrue(theOriginalPrefabNavMeshData != null, "Original prefab must have some NavMeshData.");
Assert.IsTrue(File.Exists(theOriginalPrefabAssetPath), "NavMeshData file must exist for the original prefab. ({0})", theOriginalPrefabAssetPath);
AssetDatabase.OpenAsset(prefabVariant);
var prefabVariantStage = PrefabStageUtility.GetCurrentPrefabStage();
var prefabVariantSurface = prefabVariantStage.prefabContentsRoot.GetComponent<NavMeshSurface>();
yield return TestUtility.BakeNavMeshAsync(prefabVariantSurface, k_GrayArea);
PrefabSavingUtil.SavePrefab(prefabVariantStage);
var modifiedVariantNavMeshData = prefabVariantSurface.navMeshData;
var modifiedVariantAssetPath = AssetDatabase.GetAssetPath(prefabVariantSurface.navMeshData);
Assert.IsTrue(modifiedVariantNavMeshData != null, "Prefab must have some NavMeshData.");
Assert.IsTrue(File.Exists(modifiedVariantAssetPath), "NavMeshData file for modifier variant must exist. ({0})", modifiedVariantAssetPath);
Assert.AreNotEqual(theOriginalPrefabNavMeshData, modifiedVariantNavMeshData, "Modified variant must have a NavMeshData different than that of the original prefab.");
yield return TestUtility.BakeNavMeshAsync(prefabVariantSurface, k_OrangeArea);
Assert.IsTrue(modifiedVariantNavMeshData != null, "The initial NavMeshData of a modified variant must still exist immediately after prefab variant re-bake.");
Assert.IsTrue(File.Exists(modifiedVariantAssetPath), "The initial NavMeshData file of a modified variant must exist after prefab variant re-bake. ({0})", modifiedVariantAssetPath);
Assert.IsTrue(prefabVariantSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
var unsavedRebakedNavMeshData = prefabVariantSurface.navMeshData;
yield return TestUtility.BakeNavMeshAsync(prefabVariantSurface, k_RedArea);
Assert.IsTrue(unsavedRebakedNavMeshData == null, "An unsaved NavMeshData should not exist after a re-bake.");
Assert.IsTrue(prefabVariantSurface.navMeshData != null, "NavMeshSurface must have NavMeshData after baking.");
PrefabSavingUtil.SavePrefab(prefabVariantStage);
var theNewVariantNavMeshData = prefabVariantSurface.navMeshData;
var theNewVariantAssetPath = AssetDatabase.GetAssetPath(theNewVariantNavMeshData);
if (EditorApplication.isPlaying)
{
Assert.IsTrue(modifiedVariantNavMeshData != null, "Playmode: Initial NavMeshData of the modified variant must still exist until the end of playmode.");
Assert.IsTrue(File.Exists(modifiedVariantAssetPath), "Playmode: Initial NavMeshData file of the modified and saved variant must still exist after saving the variant during playmode. ({0})", modifiedVariantAssetPath);
}
else
{
Assert.IsTrue(modifiedVariantNavMeshData == null, "Editmode: Initial NavMeshData of the modified variant must no longer exist after saving the variant.");
// This code is still reachable because modifiedVariantNavMeshData has been affected by BakeNavMeshAsync()
Assert.IsFalse(File.Exists(modifiedVariantAssetPath), "Editmode: Initial NavMeshData file of the modified and saved variant must no longer exist after saving the variant. ({0})", modifiedVariantAssetPath);
}
Assert.IsTrue(File.Exists(theNewVariantAssetPath), "Variant's own NavMeshData exists in a file after saving. ({0})", theNewVariantAssetPath);
Assert.IsTrue(File.Exists(theOriginalPrefabAssetPath), "NavMeshData file of the original prefab still exists after saving the variant. ({0})", theOriginalPrefabAssetPath);
Assert.AreNotEqual(theOriginalPrefabNavMeshData, theNewVariantNavMeshData, "Re-baked modified variant must have a NavMeshData different than that of the original prefab.");
StageUtility.GoToMainStage();
yield return null;
if (EditorApplication.isPlaying)
yield return new ExitPlayMode();
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,57 @@
using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
internal class NavMeshSurfacesTests : DomainReloadTestBase
{
[SerializeField] NavMeshSurface m_Surface;
[UnityTest]
public IEnumerator ActiveSurfaces_AddOneItemToListInEditMode_SurfaceListSetToZeroInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeSurfaces = NavMeshSurface.activeSurfaces;
Assume.That(activeSurfaces, Is.Not.Null);
Assume.That(activeSurfaces.Count, Is.Zero);
activeSurfaces.Add(null);
Assume.That(activeSurfaces.Count, Is.Not.Zero);
yield return new EnterPlayMode();
activeSurfaces = NavMeshSurface.activeSurfaces;
Assert.That(activeSurfaces.Count, Is.Zero);
}
[UnityTest]
public IEnumerator ActiveSurfaces_CreateSurfaceInEditMode_SurfaceRemainsInActiveSurfacesInPlayMode([Values(EnterPlayModeOptions.DisableDomainReload, EnterPlayModeOptions.None)] EnterPlayModeOptions option)
{
EditorSettings.enterPlayModeOptionsEnabled = true;
EditorSettings.enterPlayModeOptions = option;
var activeSurfaces = NavMeshSurface.activeSurfaces;
Assume.That(activeSurfaces, Is.Not.Null);
Assume.That(activeSurfaces.Count, Is.Zero);
m_TestGo = new GameObject("TestObj", typeof(NavMeshSurface));
m_Surface = m_TestGo.GetComponent<NavMeshSurface>();
activeSurfaces = NavMeshSurface.activeSurfaces;
Assume.That(activeSurfaces, Is.Not.Null);
Assume.That(activeSurfaces.Count, Is.EqualTo(1));
Assume.That(activeSurfaces[0], Is.EqualTo(m_Surface));
yield return new EnterPlayMode();
activeSurfaces = NavMeshSurface.activeSurfaces;
Assert.That(activeSurfaces.Count, Is.EqualTo(1));
Assert.That(activeSurfaces[0], Is.EqualTo(m_Surface));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02ce4f3c80e34778a448252aa86d70bc
timeCreated: 1700483654

View File

@@ -0,0 +1,50 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace Unity.AI.Navigation.Editor.Tests
{
[TestFixture]
[Description("Verifies that the desired Navigation editor menus are accessible with the package.")]
class NavigationPresenceInMenus
{
GameObject m_ComponentsReceiver;
[OneTimeSetUp]
public void OneTimeSetUp()
{
// Create an empty game object and select it in order for components menus to be available
m_ComponentsReceiver = new GameObject("ComponentsReceiver");
Selection.activeObject = m_ComponentsReceiver;
}
static IEnumerable<string> NavigationMenuItemProvider()
{
yield return "Component/Navigation/Nav Mesh Agent";
yield return "Component/Navigation/Nav Mesh Obstacle";
yield return "Component/Navigation/NavMesh Surface";
yield return "Component/Navigation/NavMesh Modifier Volume";
yield return "Component/Navigation/NavMesh Modifier";
yield return "Component/Navigation/NavMesh Link";
yield return "Window/AI/Navigation";
}
[Test]
[TestCaseSource(nameof(NavigationMenuItemProvider))]
public void MenuIsEnabled(string menuPath)
{
var menuEnabled = Menu.GetEnabled(menuPath);
Assert.That(menuEnabled, Is.True, $"Navigation component menu '{menuPath}' should be available");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
Object.DestroyImmediate(m_ComponentsReceiver);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 689549d6be484a1491705f5d24fc6240
timeCreated: 1649814444

View File

@@ -0,0 +1,21 @@
using System;
using System.Reflection;
using UnityEditor.SceneManagement;
namespace Unity.AI.Navigation.Editor.Tests
{
class PrefabSavingUtil
{
public static void SavePrefab(PrefabStage prefabStage)
{
if (prefabStage == null)
throw new ArgumentNullException();
var savePrefabMethod = prefabStage.GetType().GetMethod("SavePrefab", BindingFlags.NonPublic | BindingFlags.Instance);
if (savePrefabMethod == null)
throw new InvalidOperationException();
savePrefabMethod.Invoke(prefabStage, null);
}
}
}

View File

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

View File

@@ -0,0 +1,47 @@
using System;
using System.Reflection;
using UnityEditor;
namespace Unity.AI.Navigation.Editor.Tests
{
static class PrefabStageAutoSavingUtil
{
public static bool GetPrefabStageAutoSave()
{
var stageNavMgrInstance = GetStageNavigationManagerInstance();
var autoSaveProperty = GetAutoSaveProperty(stageNavMgrInstance);
return (bool) autoSaveProperty.GetValue(stageNavMgrInstance, null);
}
public static void SetPrefabStageAutoSave(bool value)
{
var stageNavMgrInstance = GetStageNavigationManagerInstance();
var autoSaveProperty = GetAutoSaveProperty(stageNavMgrInstance);
autoSaveProperty.SetValue(stageNavMgrInstance, value, null);
}
static object GetStageNavigationManagerInstance()
{
var editorAssemblyName = typeof(EditorWindow).Assembly.FullName;
var t = Type.GetType("UnityEditor.SceneManagement.StageNavigationManager, " + editorAssemblyName, true, true);
if (t == null)
throw new ArgumentException();
var instanceProperty = t.GetProperty("instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (instanceProperty == null)
throw new ArgumentException();
var stageNavMgrInstance = instanceProperty.GetValue(null, null);
return stageNavMgrInstance;
}
static PropertyInfo GetAutoSaveProperty(object stageNavigationManagerInstance)
{
var autoSaveProperty = stageNavigationManagerInstance.GetType().GetProperty("autoSave", BindingFlags.Instance | BindingFlags.NonPublic);
if (autoSaveProperty == null)
throw new ArgumentException();
return autoSaveProperty;
}
}
}

View File

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

View File

@@ -0,0 +1,55 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using UnityEditor;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Editor.Tests
{
class TestUtility
{
[return: NotNull]
public static GameObject InstantiatePrefab(GameObject prefab, string name)
{
GameObject result;
if (EditorApplication.isPlaying)
result = Object.Instantiate(prefab);
else
result = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
Assume.That(result, Is.Not.Null);
result!.name = name;
return result!;
}
public static IEnumerator BakeNavMeshAsync(NavMeshSurface surface, int defaultArea)
{
surface.defaultArea = defaultArea;
NavMeshAssetManager.instance.StartBakingSurfaces(new Object[] { surface });
yield return new WaitWhile(() => NavMeshAssetManager.instance.IsSurfaceBaking(surface));
}
public static void EliminateFromScene(ref GameObject go, bool keepDeactivated = false)
{
if (go == null)
return;
if (keepDeactivated)
go.SetActive(false);
else
Object.DestroyImmediate(go);
}
}
enum RunMode
{
EditMode,
PlayMode
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4a69fe61f096bf843a85c885aabf5780

View File

@@ -0,0 +1,26 @@
{
"name": "Unity.AI.Navigation.Editor.Tests",
"rootNamespace": "",
"references": [
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:8c4dd21966739024fbd72155091d199e",
"GUID:86c9d8e67265f41469be06142c397d17",
"GUID:1664e92176d434ccd902c1705fefe682"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

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