//#define KEEP_ARTIFACTS_FOR_INSPECTION //#define ENABLE_TEST_LOGS using System.Collections; using System.IO; using NUnit.Framework; using UnityEditor; #if !UNITY_2021_2_OR_NEWER using UnityEditor.Experimental.SceneManagement; #endif 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(); 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(); 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(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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); if (runMode == RunMode.PlayMode) yield return new EnterPlayMode(); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero); var instanceSurface = m_MainInstance.GetComponent(); 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(); 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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); var instanceSurface = m_MainInstance.GetComponent(); 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(); NavMeshAssetManager.instance.ClearSurfaces(new Object[] { prefabSurface }); PrefabSavingUtil.SavePrefab(prefabStage); if (EditorApplication.isPlaying) { yield return new ExitPlayMode(); instanceSurface = m_MainInstance.GetComponent(); } 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(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(); 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(); 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(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(); 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(); 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(); var prefabNavMeshData = prefabSurface.navMeshData; Assert.AreNotSame(mainNavMeshData, prefabNavMeshData); if (runMode != RunMode.PlayMode) { var instanceCloneSurface = m_SecondInstance.GetComponent(); 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(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(); 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(); Assume.That(instanceCloneSurface, Is.Not.Null); var instanceCloneNavMeshData = instanceCloneSurface.navMeshData; AssetDatabase.OpenAsset(prefab); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); var instanceSurface = m_MainInstance.GetComponent(); 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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); var instanceSurface = m_MainInstance.GetComponent(); 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(); 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(); 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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(); 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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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: ) 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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero); var instanceSurface = m_MainInstance.GetComponent(); 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(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(); 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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero); var instanceSurface = m_MainInstance.GetComponent(); 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(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero); var instanceSurface = m_MainInstance.GetComponent(); 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(m_PrefabPath); m_MainInstance = TestUtility.InstantiatePrefab(prefab, "Surface" + m_TestCounter + "PrefabInstance"); TestNavMeshExistsAloneAtPosition(k_PrefabDefaultArea, Vector3.zero); var instanceSurface = m_MainInstance.GetComponent(); 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(m_PrefabPath); AssetDatabase.OpenAsset(prefab); if (runMode == RunMode.PlayMode) yield return new EnterPlayMode(); var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var prefabSurface = prefabStage.prefabContentsRoot.GetComponent(); 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(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; } } }