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,8 @@
fileFormatVersion: 2
guid: 276ab4071255d9f49931e6a8373248e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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:

View File

@@ -0,0 +1,853 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &519134644249346471
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4271906378904925157}
- component: {fileID: 8212897370511830212}
- component: {fileID: 8596281887684468867}
- component: {fileID: 5890966754145026877}
m_Layer: 0
m_Name: Bundle NavMesh Surface
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4271906378904925157
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 519134644249346471}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &8212897370511830212
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 519134644249346471}
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8596281887684468867
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 519134644249346471}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &5890966754145026877
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 519134644249346471}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7a5ac11cc976e418e8d13136b07e1f52, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_CollectObjects: 0
m_Size: {x: 10, y: 10, z: 10}
m_Center: {x: 0, y: 2, z: 0}
m_LayerMask:
serializedVersion: 2
m_Bits: 4294967295
m_UseGeometry: 0
m_DefaultArea: 0
m_GenerateLinks: 0
m_IgnoreNavMeshAgent: 1
m_IgnoreNavMeshObstacle: 1
m_OverrideTileSize: 0
m_TileSize: 256
m_OverrideVoxelSize: 0
m_VoxelSize: 0.16666667
m_MinRegionArea: 2
m_NavMeshData: {fileID: 0}
m_BuildHeightMesh: 0
--- !u!1 &769986786307910025
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6480314771685140824}
- component: {fileID: 2957784056397967524}
- component: {fileID: 1096483578175528470}
m_Layer: 0
m_Name: Bundle Plane 2
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6480314771685140824
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 769986786307910025}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 12, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &2957784056397967524
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 769986786307910025}
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &1096483578175528470
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 769986786307910025}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &2206811650915575901
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5235247620429186276}
m_Layer: 0
m_Name: Scaled InterCenters Link Start Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 7174288486110832750, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5235247620429186276
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2206811650915575901}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 4.3526, y: 0.5017, z: -1.75}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2411862089752181049
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7027408494437500396}
m_Layer: 0
m_Name: InterCenters Link End Should Be Here
m_TagString: Untagged
m_Icon: {fileID: -5938655980376293919, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7027408494437500396
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2411862089752181049}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 12, y: 0, z: 0}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2759056808729940325
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6620030167200409806}
m_Layer: 0
m_Name: InterCenters Link Start Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 6519382022992737161, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6620030167200409806
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2759056808729940325}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &3799698425930382998
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3612743069469093135}
- component: {fileID: 6183284072014405755}
- component: {fileID: 5432432520571072814}
- component: {fileID: 8593906665911883465}
- component: {fileID: 7375800272983125944}
m_Layer: 0
m_Name: Multiple NavMesh Links
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3612743069469093135
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3799698425930382998}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915}
m_LocalPosition: {x: 2.4, y: 0.2, z: -2}
m_LocalScale: {x: 2, y: 2, z: 2}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2095176847815880072}
m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0}
--- !u!114 &6183284072014405755
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3799698425930382998}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -0.3, y: -0.1, z: -2.5}
m_EndPoint: {x: 0.3, y: 0.1, z: 2.5}
m_StartTransform: {fileID: 3612743069469093135}
m_EndTransform: {fileID: 3612743069469093135}
m_Activated: 1
m_Width: 1
m_CostModifier: -1
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &5432432520571072814
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3799698425930382998}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -17.6, y: 1.23, z: 37.7}
m_EndPoint: {x: 5.8, y: 0.05, z: 22.45}
m_StartTransform: {fileID: 6480314771685140824}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 1
m_Width: 2
m_CostModifier: 2
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &8593906665911883465
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3799698425930382998}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -17.6, y: 1.23, z: 37.7}
m_EndPoint: {x: 5.8, y: 0.05, z: 22.45}
m_StartTransform: {fileID: 6480314771685140824}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 0
m_Width: 2.5
m_CostModifier: 2
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 1
--- !u!114 &7375800272983125944
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3799698425930382998}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: 0.001, y: 0.001, z: 0.001}
m_EndPoint: {x: -0.001, y: -0.001, z: -0.001}
m_StartTransform: {fileID: 4271906378904925157}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 1
m_Width: 0.5
m_CostModifier: 3
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!1 &4145527836750073399
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8143012993110909644}
m_Layer: 0
m_Name: Scaled InterCenters Link End Should Be Here
m_TagString: Untagged
m_Icon: {fileID: -5442936267250999957, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8143012993110909644
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4145527836750073399}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 7.3478, y: 0.4983, z: -1.7515}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &4239459959405439086
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5632226390324501746}
m_Layer: 0
m_Name: First Link End Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 7148428337604731935, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5632226390324501746
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4239459959405439086}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 8.1926, y: 0.69996, z: -1.2160001}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &4552647711432885867
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8708194500741321703}
m_Layer: 0
m_Name: Second Link Start Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 8418204508859773708, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8708194500741321703
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4552647711432885867}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 2.9379, y: 0.69999, z: 2.9552}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &4797585877503007664
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3507495875724678348}
m_Layer: 0
m_Name: First Link Start Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 1206586993520771344, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3507495875724678348
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4797585877503007664}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 3.40742, y: 0.49997997, z: -2.7844}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &5574978382145436126
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2095176847815880072}
m_Layer: 0
m_Name: Scaled_Links_Root
m_TagString: Untagged
m_Icon: {fileID: 7250588514170254948, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2095176847815880072
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5574978382145436126}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 1, y: 0, z: 6}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3612743069469093135}
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &5990160592295572745
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1467712365982528894}
- component: {fileID: 7571120346557858792}
- component: {fileID: 8971793099447168519}
- component: {fileID: 6280812729872134344}
- component: {fileID: 3857943089573655079}
m_Layer: 0
m_Name: TestBundleWithAllLinks
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1467712365982528894
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5990160592295572745}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4271906378904925157}
- {fileID: 6480314771685140824}
- {fileID: 2095176847815880072}
- {fileID: 4222975083141804362}
- {fileID: 3507495875724678348}
- {fileID: 5632226390324501746}
- {fileID: 8708194500741321703}
- {fileID: 3952342784954718853}
- {fileID: 6620030167200409806}
- {fileID: 7027408494437500396}
- {fileID: 5235247620429186276}
- {fileID: 8143012993110909644}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &7571120346557858792
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5990160592295572745}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -9.05, y: 0.7, z: 2.96}
m_EndPoint: {x: -3.21, y: 0.5, z: 1.05}
m_StartTransform: {fileID: 6480314771685140824}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 1
m_Width: 1.7
m_CostModifier: 7
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &8971793099447168519
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5990160592295572745}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: 3.4193363, y: 0.4997115, z: -2.7743223}
m_EndPoint: {x: 8.193943, y: 0.7000145, z: -1.2156277}
m_StartTransform: {fileID: 1467712365982528894}
m_EndTransform: {fileID: 1467712365982528894}
m_Activated: 1
m_Width: 1.8
m_CostModifier: 8
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &6280812729872134344
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5990160592295572745}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: 3.4193363, y: 0.4997115, z: -2.7743223}
m_EndPoint: {x: 8.193943, y: 0.7000145, z: -1.2156277}
m_StartTransform: {fileID: 4271906378904925157}
m_EndTransform: {fileID: 1467712365982528894}
m_Activated: 1
m_Width: 1.9
m_CostModifier: -4
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &3857943089573655079
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5990160592295572745}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: 3.4193363, y: 0.4997115, z: -2.7743223}
m_EndPoint: {x: 8.193943, y: 0.7000145, z: -1.2156277}
m_StartTransform: {fileID: 1467712365982528894}
m_EndTransform: {fileID: 4271906378904925157}
m_Activated: 1
m_Width: 1.95
m_CostModifier: -4
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!1 &7410374023104900550
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3952342784954718853}
m_Layer: 0
m_Name: Second Link End Should Be Here
m_TagString: Untagged
m_Icon: {fileID: 3306451490063965843, guid: 0000000000000000d000000000000000, type: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3952342784954718853
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7410374023104900550}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 8.79132, y: 0.50001, z: 1.04984}
m_LocalScale: {x: 2, y: 3, z: 4}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &8073499793372499723
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4222975083141804362}
- component: {fileID: 2015390662727589698}
- component: {fileID: 6149203982048707568}
- component: {fileID: 450827611633533040}
m_Layer: 0
m_Name: Unscaled Links
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4222975083141804362
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8073499793372499723}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.53729963, z: -0, w: 0.8433915}
m_LocalPosition: {x: 5.8, y: 0.6, z: -2}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1467712365982528894}
m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0}
--- !u!114 &2015390662727589698
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8073499793372499723}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -0.3, y: -0.1, z: -2.5}
m_EndPoint: {x: 0.3, y: 0.1, z: 2.5}
m_StartTransform: {fileID: 4222975083141804362}
m_EndTransform: {fileID: 4222975083141804362}
m_Activated: 1
m_Width: 1.5
m_CostModifier: -1
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &6149203982048707568
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8073499793372499723}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: -9.05, y: 0.7, z: 2.96}
m_EndPoint: {x: -3.21, y: 0.5, z: 1.05}
m_StartTransform: {fileID: 6480314771685140824}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 1
m_Width: 1.5
m_CostModifier: 2
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2
--- !u!114 &450827611633533040
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8073499793372499723}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6eeb5dc026fdf4b488bc7ae0138ab719, type: 3}
m_Name:
m_EditorClassIdentifier:
m_AgentTypeID: 0
m_StartPoint: {x: 0.001, y: 0.001, z: 0.001}
m_EndPoint: {x: -0.001, y: -0.001, z: -0.001}
m_StartTransform: {fileID: 4271906378904925157}
m_EndTransform: {fileID: 6480314771685140824}
m_Activated: 1
m_Width: 0.5
m_CostModifier: 3
m_Bidirectional: 0
m_AutoUpdatePosition: 1
m_Area: 2

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c62c6d05e86bc124c9f6f5d49c93c158
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5ad99e7d0c24c3845a39ca29f7c1c5e6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 077676819252b4349a7677a483a0a8f4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,252 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools.Utils;
namespace Unity.AI.Navigation.Tests
{
public class NavMeshLinkTests
{
NavMeshLink m_NavMeshLink;
Transform m_StartTransform;
Transform m_EndTransform;
GameObject m_ScaledSkewer;
GameObject m_LinkImitator;
GameObject m_LinkStartImitator;
GameObject m_LinkEndImitator;
[OneTimeSetUp]
public void OneTimeSetUp()
{
m_NavMeshLink = new GameObject("NavMeshLink").AddComponent<NavMeshLink>();
m_StartTransform = new GameObject("Start Transform").transform;
m_EndTransform = new GameObject("End Transform").transform;
m_ScaledSkewer = new GameObject("Unevenly Scaled Skewer");
m_LinkImitator = new GameObject("Link Imitator");
m_LinkStartImitator = new GameObject("Link Start Imitator");
m_LinkEndImitator = new GameObject("Link End Imitator");
Assume.That(k_UnevenScale / k_UnevenScale.x, Is.Not.EqualTo(Vector3.one).Using(Vector3EqualityComparer.Instance));
m_ScaledSkewer.transform.localScale = k_UnevenScale;
m_ScaledSkewer.transform.SetPositionAndRotation(new Vector3(2f, 1f, 1f), Quaternion.identity);
m_LinkImitator.transform.parent = m_ScaledSkewer.transform;
m_LinkStartImitator.transform.parent = m_LinkImitator.transform;
m_LinkEndImitator.transform.parent = m_LinkImitator.transform;
// To debug, add these components to imitators, only to show icons for them in the scene
//m_LinkStartImitator.AddComponent<NavMeshModifier>().enabled = false;
//m_LinkEndImitator.AddComponent<NavMeshModifier>().enabled = false;
//m_ScaledSkewer.AddComponent<NavMeshModifierVolume>().enabled = false;
}
[SetUp]
public void SetUp()
{
m_NavMeshLink.transform.parent = null;
// Note: Adjust the expected test return values if you change the setup
m_NavMeshLink.startPoint = Vector3.back;
m_NavMeshLink.endPoint = Vector3.forward;
m_StartTransform.position = Vector3.left;
m_EndTransform.position = Vector3.right;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
if (m_NavMeshLink != null)
Object.DestroyImmediate(m_NavMeshLink.gameObject);
if (m_StartTransform != null)
Object.DestroyImmediate(m_StartTransform.gameObject);
if (m_EndTransform != null)
Object.DestroyImmediate(m_EndTransform.gameObject);
if (m_LinkStartImitator != null)
Object.DestroyImmediate(m_LinkStartImitator);
if (m_LinkEndImitator != null)
Object.DestroyImmediate(m_LinkEndImitator);
if (m_LinkImitator != null)
Object.DestroyImmediate(m_LinkImitator);
if (m_ScaledSkewer != null)
Object.DestroyImmediate(m_ScaledSkewer);
}
static readonly Quaternion k_RotatedAroundYAxis = new(0f, 1f, 0f, 0f);
static readonly Quaternion k_ArbitraryRotationAroundYAxis = Quaternion.Euler(0f, 25f, 0f);
static readonly Vector3 k_UniformScale = new(2f, 2f, 2f);
static readonly Vector3 k_UnevenScale = new(2f, 1f, 0.7f);
static readonly TestCaseData[] k_TestCases =
{
new TestCaseData(Vector3.zero, Quaternion.identity, Vector3.one, false, false)
.SetName("At origin, use start and end points")
.Returns((Vector3.back, Vector3.forward)),
new TestCaseData(Vector3.zero, Quaternion.identity, Vector3.one, false, true)
.SetName("At origin, use start point and end transform")
.Returns((Vector3.back, Vector3.right)),
new TestCaseData(Vector3.zero, Quaternion.identity, Vector3.one, true, false)
.SetName("At origin, use start transform and end point")
.Returns((Vector3.left, Vector3.forward)),
new TestCaseData(Vector3.zero, Quaternion.identity, Vector3.one, true, true)
.SetName("At origin, use start and end transforms")
.Returns((Vector3.left, Vector3.right)),
new TestCaseData(Vector3.one, Quaternion.identity, Vector3.one, false, false)
.SetName("Offset from origin, use start and end points")
.Returns((Vector3.one + Vector3.back, Vector3.one + Vector3.forward)),
new TestCaseData(Vector3.one, Quaternion.identity, Vector3.one, false, true)
.SetName("Offset from origin, use start point and end transform")
.Returns((Vector3.one + Vector3.back, Vector3.right)),
new TestCaseData(Vector3.one, Quaternion.identity, Vector3.one, true, false)
.SetName("Offset from origin, use start transform and end point")
.Returns((Vector3.left, Vector3.one + Vector3.forward)),
new TestCaseData(Vector3.one, Quaternion.identity, Vector3.one, true, true)
.SetName("Offset from origin, use start and end transforms")
.Returns((Vector3.left, Vector3.right)),
new TestCaseData(Vector3.zero, k_RotatedAroundYAxis, Vector3.one, false, false)
.SetName("Rotated at origin, use start and end points")
.Returns((Vector3.forward, Vector3.back)),
new TestCaseData(Vector3.zero, k_RotatedAroundYAxis, Vector3.one, false, true)
.SetName("Rotated at origin, use start point and end transform")
.Returns((Vector3.forward, Vector3.right)),
new TestCaseData(Vector3.zero, k_RotatedAroundYAxis, Vector3.one, true, false)
.SetName("Rotated at origin, use start transform and end point")
.Returns((Vector3.left, Vector3.back)),
new TestCaseData(Vector3.zero, k_RotatedAroundYAxis, Vector3.one, true, true)
.SetName("Rotated at origin, use start and end transforms")
.Returns((Vector3.left, Vector3.right)),
new TestCaseData(Vector3.zero, Quaternion.identity, k_UniformScale, false, false)
.SetName("Scaled at origin, use start and end points")
.Returns((Vector3.back, Vector3.forward)),
new TestCaseData(Vector3.zero, Quaternion.identity, k_UniformScale, false, true)
.SetName("Scaled at origin, use start point and end transform")
.Returns((Vector3.back, Vector3.right)),
new TestCaseData(Vector3.zero, Quaternion.identity, k_UniformScale, true, false)
.SetName("Scaled at origin, use start transform and end point")
.Returns((Vector3.left, Vector3.forward)),
new TestCaseData(Vector3.zero, Quaternion.identity, k_UniformScale, true, true)
.SetName("Scaled at origin, use start and end transforms")
.Returns((Vector3.left, Vector3.right)),
};
[TestCaseSource(nameof(k_TestCases))]
public (Vector3 start, Vector3 end) GetWorldPositions_ReturnsExpectedResults(
Vector3 transformPosition, Quaternion transformRotation, Vector3 transformScale,
bool useStartTransform, bool useEndTransform
)
{
m_NavMeshLink.transform.position = transformPosition;
m_NavMeshLink.transform.rotation = transformRotation;
m_NavMeshLink.transform.localScale = transformScale;
m_NavMeshLink.startTransform = useStartTransform ? m_StartTransform : null;
m_NavMeshLink.endTransform = useEndTransform ? m_EndTransform : null;
m_NavMeshLink.GetWorldPositions(out var worldStart, out var worldEnd);
return (start: worldStart, end: worldEnd);
}
// The expected values have been obtained by observing a correct result in the Editor
static readonly TestCaseData[] k_SkewedTestCases =
{
new TestCaseData(Vector3.zero, Quaternion.identity, Vector3.one,
new Vector3(2f, 1f, 0f), new Vector3(2f, 1f, 2f))
.SetName("At parent origin"),
new TestCaseData(Vector3.zero, k_ArbitraryRotationAroundYAxis, Vector3.one,
new Vector3(1.577382f, 1f, 0.09369224f), new Vector3(2.422618f, 1f, 1.906308f))
.SetName("Rotated"),
new TestCaseData(Vector3.one, k_ArbitraryRotationAroundYAxis, Vector3.one,
new Vector3(3.577382f, 2f, 0.7936923f), new Vector3(4.422618f, 2f, 2.606308f))
.SetName("Offset from parent"),
new TestCaseData(Vector3.zero, Quaternion.identity, k_UniformScale,
new Vector3(2f, 1f, 0f), new Vector3(2f, 1f, 2f))
.SetName("Scaled"),
new TestCaseData(Vector3.one, k_ArbitraryRotationAroundYAxis, k_UniformScale,
new Vector3(3.577382f, 2f, 0.7936923f), new Vector3(4.422618f, 2f, 2.606308f))
.SetName("Rotated, scaled and with offset")
};
[TestCaseSource(nameof(k_SkewedTestCases))]
public void GetWorldPositionsForPoints_WhenLinkParentHasUnevenScale_ReturnsEndpointsNonSkewed(
Vector3 transformPosition, Quaternion transformRotation, Vector3 transformScale,
Vector3 expectedStart, Vector3 expectedEnd)
{
m_NavMeshLink.transform.parent = m_ScaledSkewer.transform;
m_NavMeshLink.transform.localPosition = transformPosition;
m_NavMeshLink.transform.localRotation = transformRotation;
m_NavMeshLink.transform.localScale = transformScale;
m_NavMeshLink.startTransform = null;
m_NavMeshLink.endTransform = null;
Assume.That(Vector3.zero, Is.Not.EqualTo(m_NavMeshLink.startPoint).Or.Not.EqualTo(m_NavMeshLink.endPoint),
"At least one endpoint should be skewed away from the local origin.");
m_LinkImitator.transform.parent = m_ScaledSkewer.transform;
m_LinkImitator.transform.localPosition = transformPosition;
m_LinkImitator.transform.localRotation = transformRotation;
m_LinkImitator.transform.localScale = transformScale;
m_LinkStartImitator.transform.localPosition = m_NavMeshLink.startPoint;
m_LinkEndImitator.transform.localPosition = m_NavMeshLink.endPoint;
Assume.That(m_LinkStartImitator.transform.position, Is.Not.EqualTo(expectedStart)
.Using(Vector3EqualityComparer.Instance),
"The wanted link start position should not be skewed along with the transform hierarchy.");
Assume.That(m_LinkEndImitator.transform.position, Is.Not.EqualTo(expectedEnd)
.Using(Vector3EqualityComparer.Instance),
"The wanted link end position should not be skewed along with the transform hierarchy.");
m_NavMeshLink.GetWorldPositions(out var worldStart, out var worldEnd);
// Uncomment to get the new expected values if you change the setup
//Debug.Log($"(new Vector3({worldStart.x}f, {worldStart.y}f, {worldStart.z}f), new Vector3({worldEnd.x}f, {worldEnd.y}f, {worldEnd.z}f))");
Assert.That(worldStart, Is.EqualTo(expectedStart)
.Using(Vector3EqualityComparer.Instance),
"Start position should be at an unscaled offset from the Link.");
Assert.That(worldEnd, Is.EqualTo(expectedEnd)
.Using(Vector3EqualityComparer.Instance),
"End position should be at an unscaled offset from the Link.");
}
static readonly Vector3 k_DoubleOne = 2f * Vector3.one;
static readonly TestCaseData[] k_TestCasesForLocal =
{
new TestCaseData(false, false, Vector3.one, -Vector3.one)
.SetName("From start and end points"),
new TestCaseData(false, true, Vector3.one, -k_DoubleOne)
.SetName("From start point and end transform"),
new TestCaseData(true, false, k_DoubleOne, -Vector3.one)
.SetName("From start transform and end point"),
new TestCaseData(true, true, k_DoubleOne, -k_DoubleOne)
.SetName("From start and end transforms")
};
[TestCaseSource(nameof(k_TestCasesForLocal))]
public void GetLocalPositions_ReturnsExpectedResults(
bool useStartTransform, bool useEndTransform,
Vector3 expectedStart, Vector3 expectedEnd)
{
var origin = Vector3.one;
m_NavMeshLink.transform.SetPositionAndRotation(origin, Quaternion.Euler(90f, -90f, 90f));
m_NavMeshLink.transform.localScale = k_DoubleOne;
m_StartTransform.position = origin - k_DoubleOne;
m_EndTransform.position = origin + k_DoubleOne;
m_NavMeshLink.enabled = false;
m_NavMeshLink.startTransform = useStartTransform ? m_StartTransform : null;
m_NavMeshLink.endTransform = useEndTransform ? m_EndTransform : null;
m_NavMeshLink.startPoint = !useStartTransform ? Vector3.one : 100f * Vector3.one;
m_NavMeshLink.endPoint = !useEndTransform ? -Vector3.one : -100f * Vector3.one;
m_NavMeshLink.enabled = true;
m_NavMeshLink.GetLocalPositions(out var localStart, out var localEnd);
Assert.That(localStart, Is.EqualTo(expectedStart).Using(Vector3EqualityComparer.Instance),
"Start should be reported at a different position.");
Assert.That(localEnd, Is.EqualTo(expectedEnd).Using(Vector3EqualityComparer.Instance),
"End should be reported at a different position.");
}
}
}

View File

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

View File

@@ -0,0 +1,106 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
class NavMeshSurfaceAgentTests
{
NavMeshSurface m_Surface;
NavMeshAgent m_Agent;
[SetUp]
public void Setup()
{
m_Surface = GameObject.CreatePrimitive(PrimitiveType.Plane).AddComponent<NavMeshSurface>();
}
[TearDown]
public void TearDown()
{
Object.DestroyImmediate(m_Agent.gameObject);
Object.DestroyImmediate(m_Surface.gameObject);
m_Agent = null;
m_Surface = null;
}
[Test]
public void AgentIdentifiesSurfaceOwner()
{
m_Surface.BuildNavMesh();
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
Assert.AreEqual(m_Surface, m_Agent.navMeshOwner);
Assert.IsTrue(m_Agent.isOnNavMesh);
}
[Test]
[Ignore("1012991 : Missing functionality for notifying the NavMeshAgent about the removal of the NavMesh.")]
public void AgentDetachesAndAttachesToSurface()
{
m_Surface.BuildNavMesh();
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
Assert.AreEqual(m_Surface, m_Agent.navMeshOwner);
Assert.IsTrue(m_Agent.isOnNavMesh);
m_Surface.enabled = false;
Assert.IsNull(m_Agent.navMeshOwner);
Assert.IsFalse(m_Agent.isOnNavMesh);
m_Surface.enabled = true;
Assert.AreEqual(m_Surface, m_Agent.navMeshOwner);
Assert.IsTrue(m_Agent.isOnNavMesh);
}
/*
[Test]
public void AgentIsOnNavMeshWhenMatchingAgentTypeID()
{
m_Surface.agentTypeID = 1234;
m_Surface.BuildNavMesh();
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
Assert.IsFalse(m_Agent.isOnNavMesh);
m_Agent.agentTypeID = 1234;
Assert.IsTrue(m_Agent.isOnNavMesh);
}
*/
[UnityTest]
public IEnumerator AgentAlignsToSurfaceNextFrame()
{
m_Surface.transform.rotation = new Quaternion(-0.679622f, 0.351242f, -0.373845f, 0.524388f);
m_Surface.BuildNavMesh();
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
yield return null;
var residual = m_Surface.transform.up - m_Agent.transform.up;
Assert.IsTrue(residual.magnitude < 0.01f);
}
[UnityTest]
public IEnumerator AgentDoesNotAlignToSurfaceNextFrame()
{
m_Surface.transform.rotation = new Quaternion(-0.679622f, 0.351242f, -0.373845f, 0.524388f);
m_Surface.BuildNavMesh();
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
m_Agent.updateUpAxis = false;
yield return null;
var residual = Vector3.up - m_Agent.transform.up;
Assert.IsTrue(residual.magnitude < 0.01f);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 45e898f1334e740678c21331ac30d746
timeCreated: 1490606318
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace Unity.AI.Navigation.Tests
{
[DefaultExecutionOrder(-1)]
class NavMeshSurfaceBuildFromAwake : MonoBehaviour
{
public NavMeshSurface surface;
void Awake()
{
surface.BuildNavMesh();
}
}
}

View File

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

View File

@@ -0,0 +1,966 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
using Object = UnityEngine.Object;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
class NavMeshSurfaceLinkTests
{
GameObject m_PlaneAtOrigin;
GameObject m_PlaneOnTheSide;
NavMeshLink m_Link;
NavMeshSurface m_Surface;
readonly Vector3 m_OffsetX = new(11f, 0f, 0f);
readonly Vector3 m_OffsetZ = new(0f, 0f, 11f);
readonly Vector3 m_DefaultEndpointOffset = new(0f, 0f, 2.5f);
readonly int m_PandaTypeID = NavMesh.CreateSettings().agentTypeID;
const int k_AreaTypeForPanda = 3;
NavMeshAgent m_Agent;
GameObject m_ExtraNavMesh;
GameObject m_FarFromNavMesh;
GameObject m_TempGO;
GameObject m_PathfindingStart;
GameObject m_PathfindingEnd;
NavMeshDataInstance m_NavMeshClone;
NavMeshDataInstance m_NavMeshForPanda;
NavMeshSurface m_SurfaceForPanda;
[OneTimeSetUp]
public void OneTimeSetUp()
{
m_PlaneAtOrigin = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_PlaneOnTheSide = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_PlaneOnTheSide.transform.position = m_OffsetX;
m_PlaneOnTheSide.transform.localScale = 0.4f * Vector3.one;
m_PlaneAtOrigin.transform.localScale = 0.4f * Vector3.one;
m_Surface = new GameObject("Surface").AddComponent<NavMeshSurface>();
m_Surface.BuildNavMesh();
Assume.That(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide), Is.False);
Assume.That(HasPathConnecting(m_PlaneOnTheSide, m_PlaneAtOrigin), Is.False);
m_Agent = new GameObject("Agent").AddComponent<NavMeshAgent>();
m_Agent.transform.position = m_PlaneOnTheSide.transform.position - 2f * Vector3.back;
m_Agent.enabled = false;
m_Agent.acceleration = 100f;
m_Agent.speed = 10f;
Assume.That(m_Agent.speed, Is.LessThan(m_OffsetX.x),
"Too high of a speed causes the agent to jump straight to the path's end.");
m_PathfindingStart = new GameObject("Path Start");
m_PathfindingEnd = new GameObject("Path End");
m_ExtraNavMesh = new GameObject("Origin of Additional NavMesh")
{
transform =
{
position = m_PlaneAtOrigin.transform.position + m_OffsetZ
}
};
m_FarFromNavMesh = new GameObject("Position Far From NavMesh")
{
transform =
{
position = m_ExtraNavMesh.transform.position - 2f * m_OffsetX
}
};
m_NavMeshClone = NavMesh.AddNavMeshData(m_Surface.navMeshData, m_ExtraNavMesh.transform.position, Quaternion.identity);
m_SurfaceForPanda = m_Surface.gameObject.AddComponent<NavMeshSurface>();
m_SurfaceForPanda.agentTypeID = m_PandaTypeID;
m_SurfaceForPanda.defaultArea = k_AreaTypeForPanda;
m_SurfaceForPanda.BuildNavMesh();
m_SurfaceForPanda.enabled = false;
}
[SetUp]
public void SetUp()
{
// move m_Link to OneTimeSetup
m_Link = new GameObject("Link").AddComponent<NavMeshLink>();
m_Link.transform.position = Vector3.zero;
m_Link.startPoint = m_PlaneAtOrigin.transform.position;
m_Link.endPoint = m_PlaneOnTheSide.transform.position;
m_Link.UpdateLink();
Assume.That(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide), Is.True);
Assume.That(HasPathConnecting(m_PlaneOnTheSide, m_PlaneAtOrigin), Is.True);
if (m_Agent.isActiveAndEnabled)
{
m_Agent.ResetPath();
m_Agent.Warp(m_PlaneOnTheSide.transform.position - 2f * Vector3.back);
}
m_PathfindingStart.transform.position = m_Link.transform.position + m_Link.startPoint;
m_PathfindingEnd.transform.position = m_Link.transform.position + m_Link.endPoint;
}
[TearDown]
public void TearDown()
{
Object.DestroyImmediate(m_Link.gameObject);
if (m_TempGO != null)
Object.DestroyImmediate(m_TempGO);
if (m_NavMeshForPanda.valid)
NavMesh.RemoveNavMeshData(m_NavMeshForPanda);
m_Agent.enabled = false;
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
Object.DestroyImmediate(m_Surface.gameObject);
Object.DestroyImmediate(m_PlaneAtOrigin);
Object.DestroyImmediate(m_PlaneOnTheSide);
Object.DestroyImmediate(m_Agent);
Object.DestroyImmediate(m_ExtraNavMesh);
Object.DestroyImmediate(m_FarFromNavMesh);
Object.DestroyImmediate(m_PathfindingStart);
Object.DestroyImmediate(m_PathfindingEnd);
if (m_NavMeshClone.valid)
NavMesh.RemoveNavMeshData(m_NavMeshClone);
}
[Test]
public void Link_WhenCreated_HasDefaultEndpointOffsets()
{
var link = m_Link.gameObject.AddComponent<NavMeshLink>();
Assert.That(link.startPoint, Is.EqualTo(-m_DefaultEndpointOffset).Using(Vector3EqualityComparer.Instance),
"Newly created NavMeshLink should have the start point located at an offset from the game object.");
Assert.That(link.endPoint, Is.EqualTo(m_DefaultEndpointOffset).Using(Vector3EqualityComparer.Instance),
"Newly created NavMeshLink should have the end point located at an offset from the game object.");
}
[Test]
public void Link_WhenCreatedAtRuntime_HasNullStartAndEndTransforms()
{
var link = m_Link.gameObject.AddComponent<NavMeshLink>();
Assert.That(link.startTransform, Is.EqualTo(null),
"A NavMeshLink newly created should have a null start transform.");
Assert.That(link.endTransform, Is.EqualTo(null),
"A NavMeshLink newly created should have a null end transform.");
}
[Test]
public void Link_WhenCreated_HasTheExpectedValuesForProperties()
{
var link = m_Link.gameObject.AddComponent<NavMeshLink>();
Assert.That(link.area, Is.EqualTo(0),
"Newly created NavMeshLink should have the Walkable area type.");
Assert.That(link.agentTypeID, Is.EqualTo(0),
"Newly created NavMeshLink should have the Humanoid agent type ID.");
Assert.That(link.bidirectional, Is.True,
"Newly created NavMeshLink should have bidirectional switched on.");
Assert.That(link.costModifier, Is.Negative,
"Newly created NavMeshLink should have a negative cost modifier, which means the cost from area type is not overridden.");
Assert.That(link.width, Is.EqualTo(0f),
"Newly created NavMeshLink should have a width of zero.");
Assert.That(link.autoUpdate, Is.False,
"Newly created NavMeshLink should have autoUpdate switched off.");
Assert.That(link.activated, Is.True,
"Newly created NavMeshLink should be activated.");
}
[TestCase(0)]
[TestCase(1)]
[TestCase(-1)]
[TestCase(42)]
[TestCase(-42)]
public void CostModifier_SetValue_GetValueIsTheSame(int costModifier)
{
m_Link.costModifier = costModifier;
Assert.That(m_Link.costModifier, Is.EqualTo(costModifier));
}
[Test]
public void Link_WithValidParameters_ConnectsTwoSurfaces()
{
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_OnScaledObject_DoesNotScaleEndPoints()
{
m_Link.enabled = false;
m_Link.transform.localScale = new Vector3(1f, 1f, m_OffsetZ.z);
m_Link.startPoint = m_PlaneAtOrigin.transform.position + Vector3.forward;
m_Link.endPoint = m_PlaneOnTheSide.transform.position + Vector3.forward;
m_Link.enabled = true;
m_PathfindingStart.transform.position = m_Link.transform.TransformPoint(m_Link.startPoint);
m_PathfindingEnd.transform.position = m_Link.transform.TransformPoint(m_Link.endPoint);
VerifyLinkConnection(ResultsIn.NoPath);
m_PathfindingStart.transform.position = m_PlaneAtOrigin.transform.position;
m_PathfindingEnd.transform.position = m_PlaneOnTheSide.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_OnUpdateLinkWhileDisabled_DoesNotEnableConnection()
{
m_Link.gameObject.SetActive(false);
VerifyLinkConnection(ResultsIn.NoPath);
m_Link.UpdateLink();
VerifyLinkConnection(ResultsIn.NoPath);
}
[Test]
public void Link_OnUpdateLink_AppliesChangesImmediately()
{
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
m_Link.UpdateLink();
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward, m_Link.area, m_Link.agentTypeID);
}
[Test]
public void Link_WhenEnabled_AppliesChangesImmediately()
{
m_Link.enabled = false;
VerifyLinkConnection(ResultsIn.NoPath);
AddNavMeshForPandaAgent();
ReconfigureLinkForPandaAgent(m_Link);
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position;
m_PathfindingEnd.transform.position = m_PlaneAtOrigin.transform.position;
VerifyLinkConnection(ResultsIn.NoPath, m_Link.area, m_Link.agentTypeID);
m_Link.enabled = true;
VerifyLinkConnection(ResultsIn.PathOnlyForward, m_Link.area, m_Link.agentTypeID);
}
void ReconfigureLinkForPandaAgent(NavMeshLink link)
{
var wasEnabled = link.enabled;
link.enabled = false;
link.agentTypeID = m_PandaTypeID;
link.area = k_AreaTypeForPanda;
link.bidirectional = false;
link.costModifier = 3f;
link.width = 6f;
link.startTransform = m_ExtraNavMesh.transform;
link.endTransform = m_PlaneAtOrigin.transform;
link.startPoint = 3f * Vector3.forward;
link.endPoint = 3f * Vector3.forward + Vector3.right;
link.enabled = wasEnabled;
}
void AddNavMeshForPandaAgent()
{
m_NavMeshForPanda = NavMesh.AddNavMeshData(m_SurfaceForPanda.navMeshData,
m_ExtraNavMesh.transform.position + 0.05f * Vector3.up, Quaternion.Euler(0f, 90f, 0f));
}
[Test]
public void Link_WhenGameObjectTransformMoves_EndpointsMoveRelativeToLinkOnUpdate()
{
m_Link.transform.position += Vector3.forward;
Assert.IsFalse(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position + Vector3.forward));
Assert.IsFalse(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position + Vector3.forward));
m_Link.UpdateLink();
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position + Vector3.forward));
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position + Vector3.forward));
}
[Test]
public void Link_WhenPropertyAreaChanges_UpdatesConnectionImmediately()
{
Assume.That(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide), Is.True);
m_Link.area = 4;
var anyAreaExceptTheLinkArea = ~(1 << m_Link.area);
Assert.IsFalse(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide, anyAreaExceptTheLinkArea));
}
[Test]
public void Link_WhenPropertyBidirectionalSwitchedOff_UpdatesConnectionImmediatelyToOneWay()
{
Assume.That(HasPathConnecting(m_PlaneOnTheSide, m_PlaneAtOrigin), Is.True);
m_Link.bidirectional = false;
VerifyLinkConnection(ResultsIn.PathOnlyForward);
}
[Test]
public void Link_WhenPropertyBidirectionalSwitchedOn_UpdatesConnectionImmediatelyToBothWays()
{
m_Link.bidirectional = false;
Assume.That(HasPathConnecting(m_PlaneOnTheSide, m_PlaneAtOrigin), Is.False);
m_Link.bidirectional = true;
VerifyLinkConnection(ResultsIn.PathBothWays);
}
[Test]
public void Link_WhenPropertyCostModifierChanges_UpdatesConnectionImmediately()
{
m_Link.gameObject.SetActive(false);
var fartherLink = m_Link.gameObject.AddComponent<NavMeshLink>();
fartherLink.startPoint = m_PlaneAtOrigin.transform.position + Vector3.forward;
fartherLink.endPoint = m_PlaneOnTheSide.transform.position + Vector3.forward;
m_Link.gameObject.SetActive(true);
Assume.That(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_Link.endPoint), Is.True);
m_Link.costModifier = 1000f;
Assert.IsFalse(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_Link.endPoint),
"A path should not go through the connection with the higher cost, even if it's closer.");
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, fartherLink.endPoint),
"A path should go through the connection with the lower cost, even if it's farther away.");
}
[Test]
public void Link_WhenPropertyStartPointChanged_UpdatesConnectionImmediately()
{
m_Link.startPoint = m_OffsetZ;
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenPropertyEndPointChanged_UpdatesConnectionImmediately()
{
m_Link.endPoint = m_OffsetZ;
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenPropertyWidthChanges_UpdatesConnectionImmediately()
{
m_Link.transform.position = 3f * Vector3.forward;
m_Link.UpdateLink();
Assume.That(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide), Is.False);
m_Link.width = 6f;
Assert.IsTrue(HasPathConnecting(m_PlaneAtOrigin, m_PlaneOnTheSide));
}
[Test]
public void Link_WhenPropertyActivatedChanges_UpdatesConnectionImmediately()
{
m_Link.activated = false;
VerifyLinkConnection(ResultsIn.NoPath);
m_Link.activated = true;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenTransformAssignedAtStart_RetainsValueOfStartPoint()
{
m_TempGO = new GameObject("Link");
var link = m_TempGO.AddComponent<NavMeshLink>();
Assume.That(link.startPoint, Is.Not.EqualTo(Vector3.zero));
var startPointBefore = link.startPoint;
link.startTransform = m_ExtraNavMesh.transform;
link.UpdateLink();
Assert.That(link.startPoint, Is.EqualTo(startPointBefore),
"NavMeshLink should retain the start point after a start transform has been assigned to it.");
Assume.That(link.endPoint, Is.Not.EqualTo(Vector3.zero), "End point should retain a default offset.");
}
[Test]
public void Link_WhenTransformAssignedAtEnd_RetainsValueOfEndPoint()
{
m_TempGO = new GameObject("Link");
var link = m_TempGO.AddComponent<NavMeshLink>();
Assume.That(link.endPoint, Is.Not.EqualTo(Vector3.zero));
var endPointBefore = link.endPoint;
link.endTransform = m_ExtraNavMesh.transform;
link.UpdateLink();
Assert.That(link.endPoint, Is.EqualTo(endPointBefore),
"NavMeshLink should preserve the end point after an end transform has been assigned to it.");
Assume.That(link.startPoint, Is.Not.EqualTo(Vector3.zero), "Start point should keep a default offset.");
}
[Test]
public void Link_WhenTransformRemovedAtStart_KeepsStartPointUnchanged()
{
m_TempGO = new GameObject("Link");
var pointBefore = new Vector3(1f, 2f, 3f);
var link = m_TempGO.AddComponent<NavMeshLink>();
link.startTransform = m_TempGO.transform;
link.startPoint = pointBefore;
link.startTransform = null;
Assert.That(link.startPoint, Is.EqualTo(pointBefore),
"NavMeshLink should retain the same start point after the start transform has been unassigned.");
Assume.That(link.endPoint, Is.EqualTo(m_DefaultEndpointOffset),
"End point should keep a default offset.");
}
[Test]
public void Link_WhenTransformRemovedAtEnd_KeepsEndPointUnchanged()
{
m_TempGO = new GameObject("Link");
var pointBefore = new Vector3(1f, 2f, 3f);
var link = m_TempGO.AddComponent<NavMeshLink>();
link.endTransform = m_TempGO.transform;
link.endPoint = pointBefore;
link.endTransform = null;
Assert.That(link.endPoint, Is.EqualTo(pointBefore),
"NavMeshLink should retain the same end point after the end transform has been unassigned.");
Assume.That(link.startPoint, Is.EqualTo(-m_DefaultEndpointOffset),
"Start point should keep a default offset.");
}
[Test]
public void Link_WhenTransformAssignedAtStart_OverridesStartPointImmediately()
{
m_Link.startPoint = m_ExtraNavMesh.transform.position + m_OffsetX - m_Link.transform.position;
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position + m_OffsetX;
VerifyLinkConnection(ResultsIn.PathForward);
m_Link.startTransform = m_ExtraNavMesh.transform;
VerifyLinkConnection(ResultsIn.NoPath);
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenTransformAssignedAtEnd_OverridesEndPointImmediately()
{
m_Link.endPoint = m_ExtraNavMesh.transform.position + m_OffsetX - m_Link.transform.position;
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position + m_OffsetX;
VerifyLinkConnection(ResultsIn.PathForward);
m_Link.endTransform = m_ExtraNavMesh.transform;
VerifyLinkConnection(ResultsIn.NoPath);
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenTransformRemovedAtStart_FallsBackToStartPointImmediately()
{
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.enabled = false;
m_Link.startPoint = m_ExtraNavMesh.transform.position + m_OffsetX - m_Link.transform.position;
m_Link.startTransform = m_TempGO.transform;
m_Link.enabled = true;
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position + m_OffsetX;
VerifyLinkConnection(ResultsIn.NoPath);
m_Link.startTransform = null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenTransformRemovedAtEnd_FallsBackToEndPointImmediately()
{
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.enabled = false;
m_Link.endPoint = m_ExtraNavMesh.transform.position + m_OffsetX - m_Link.transform.position;
m_Link.endTransform = m_TempGO.transform;
m_Link.enabled = true;
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position + m_OffsetX;
VerifyLinkConnection(ResultsIn.NoPath);
m_Link.endTransform = null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenBothTransformAndPointChangeAtStart_AppliesOnlyStartTransform()
{
m_Link.startTransform = m_ExtraNavMesh.transform;
m_Link.startPoint = -2f * m_OffsetX;
m_PathfindingStart.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[Test]
public void Link_WhenBothTransformAndPointChangeAtEnd_AppliesOnlyEndTransform()
{
m_Link.endTransform = m_ExtraNavMesh.transform;
m_Link.endPoint = -2f * m_OffsetX;
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator Link_DuringAgentTraversal_ReportsIsOccupied_OtherwiseNotOccupied()
{
m_Agent.enabled = true;
m_Agent.SetDestination(m_PlaneAtOrigin.transform.position + Vector3.back);
while (!m_Agent.isOnOffMeshLink)
{
Assert.IsFalse(m_Link.occupied, "Link is occupied, but the agent hasn't arrived to the link yet.");
yield return null;
}
var framesUntilComplete = 3;
while (m_Agent.isOnOffMeshLink)
{
Assert.IsTrue(m_Link.occupied, "Link is not occupied, but the agent is on the link.");
if (--framesUntilComplete == 0)
m_Agent.CompleteOffMeshLink();
yield return null;
}
Assert.IsFalse(m_Link.occupied, "Link is occupied, but agent has left the link.");
}
[UnityTest]
public IEnumerator Link_WhenAutoUpdateSwitchedOn_UpdatesOnNextFrame_AndNotBefore()
{
Assume.That(m_Link.autoUpdate, Is.False);
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
m_Link.autoUpdate = true;
VerifyLinkConnection(ResultsIn.NoPath);
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator Link_WhenAutoUpdateSwitchedOff_DoesNotApplyQueuedOrFutureChanges()
{
m_Link.autoUpdate = true;
yield return null;
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
Assume.That(m_PathfindingEnd.transform.position, Is.EqualTo(m_PlaneOnTheSide.transform.position).Using(Vector3EqualityComparer.Instance));
VerifyLinkConnection(ResultsIn.PathForward);
m_Link.autoUpdate = false;
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
m_Link.transform.SetPositionAndRotation(m_ExtraNavMesh.transform.position, Quaternion.identity);
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenGameObjectMoves_UpdatesConnectionNextFrame()
{
m_Link.autoUpdate = true;
m_Link.transform.position += Vector3.forward;
Assert.IsFalse(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position + Vector3.forward));
Assert.IsFalse(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position + Vector3.forward));
yield return null;
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position + Vector3.forward));
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position + Vector3.forward));
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOff_WhenGameObjectMoves_KeepsConnectionUnchanged()
{
m_Link.autoUpdate = false;
Assume.That(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position), Is.True);
Assert.That(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position), Is.True);
m_Link.transform.position += Vector3.forward;
// Skip a few frames
yield return null;
yield return null;
yield return null;
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneAtOrigin.transform.position));
Assert.IsTrue(HasPathConnectingViaPoint(m_PlaneAtOrigin, m_PlaneOnTheSide, m_PlaneOnTheSide.transform.position));
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenGameObjectRotates_UpdatesConnectionNextFrame()
{
m_Link.autoUpdate = true;
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.NoPath);
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOff_WhenGameObjectRotates_KeepsConnectionUnchanged()
{
m_Link.autoUpdate = false;
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
// Skip a few frames
yield return null;
yield return null;
yield return null;
Assume.That(m_PathfindingEnd.transform.position, Is.EqualTo(m_PlaneOnTheSide.transform.position).Using(Vector3EqualityComparer.Instance));
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenTransformAtStartMoves_UpdatesConnectionNextFrame()
{
m_TempGO = Object.Instantiate(m_FarFromNavMesh);
m_Link.startTransform = m_TempGO.transform;
m_Link.UpdateLink();
m_Link.autoUpdate = true;
m_TempGO.transform.position += 2f * m_OffsetX;
m_PathfindingStart.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.NoPath);
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenTransformAtEndMoves_UpdatesConnectionNextFrame()
{
m_TempGO = Object.Instantiate(m_FarFromNavMesh);
m_Link.endTransform = m_TempGO.transform;
m_Link.UpdateLink();
m_Link.autoUpdate = true;
m_TempGO.transform.position += 2f * m_OffsetX;
m_PathfindingEnd.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.NoPath);
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenTransformAtStartDestroyed_AppliesStartPointNextFrame()
{
m_Link.autoUpdate = true;
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.enabled = false;
m_Link.startTransform = m_TempGO.transform;
m_Link.startPoint = m_PlaneOnTheSide.transform.position - m_Link.transform.position;
m_Link.endTransform = null;
m_Link.endPoint = Vector3.zero;
m_Link.bidirectional = false;
m_Link.enabled = true;
m_PathfindingStart.transform.position = m_TempGO.transform.position;
m_PathfindingEnd.transform.position = m_PlaneAtOrigin.transform.position;
Object.DestroyImmediate(m_TempGO);
VerifyLinkConnection(ResultsIn.PathForward);
yield return null;
VerifyLinkConnection(ResultsIn.NoPath);
m_PathfindingStart.transform.position = m_PlaneOnTheSide.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOn_WhenTransformAtEndDestroyed_AppliesEndPointNextFrame()
{
m_Link.autoUpdate = true;
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.enabled = false;
m_Link.startTransform = null;
m_Link.startPoint = m_PlaneOnTheSide.transform.position - m_Link.transform.position;
m_Link.endTransform = m_TempGO.transform;
m_Link.endPoint = Vector3.zero;
m_Link.bidirectional = false;
m_Link.enabled = true;
m_PathfindingStart.transform.position = m_PlaneOnTheSide.transform.position;
m_PathfindingEnd.transform.position = m_TempGO.transform.position;
Object.DestroyImmediate(m_TempGO);
VerifyLinkConnection(ResultsIn.PathForward);
yield return null;
VerifyLinkConnection(ResultsIn.NoPath);
m_PathfindingEnd.transform.position = m_PlaneAtOrigin.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOff_WhenTransformAtStartMoves_KeepsConnectionUnchanged()
{
m_Link.autoUpdate = false;
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.startTransform = m_TempGO.transform;
m_Link.UpdateLink();
m_PathfindingStart.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
m_TempGO.transform.position += m_OffsetX;
yield return null;
yield return null;
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
m_PathfindingStart.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.NoPath);
}
[UnityTest]
public IEnumerator LinkWithAutoUpdateOff_WhenTransformAtEndMoves_KeepsConnectionUnchanged()
{
m_Link.autoUpdate = false;
m_TempGO = Object.Instantiate(m_ExtraNavMesh);
m_Link.endTransform = m_TempGO.transform;
m_Link.UpdateLink();
m_PathfindingEnd.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.PathForward);
m_TempGO.transform.position += m_OffsetX;
yield return null;
yield return null;
yield return null;
VerifyLinkConnection(ResultsIn.PathForward);
m_PathfindingEnd.transform.position = m_TempGO.transform.position;
VerifyLinkConnection(ResultsIn.NoPath);
}
#pragma warning disable CS0618 // Test deprecated members
[Test]
public void Link_WhenCreated_HasTheExpectedValuesForDeprecatedProperties()
{
var link = m_Link.gameObject.AddComponent<NavMeshLink>();
Assert.That(link.biDirectional, Is.True,
"Newly created NavMeshLink should have deprecated biDirectional switched on.");
Assert.That(link.autoUpdatePositions, Is.False,
"Newly created NavMeshLink should have deprecated autoUpdatePositions switched off.");
Assert.That(link.costOverride, Is.Negative,
"Newly created NavMeshLink should have a negative deprecated cost override, which means the cost from area type is not overridden.");
}
[Test]
public void Link_WhenBidirectionalChanges_DeprecatedBiDirectionalChangesToo()
{
m_Link.bidirectional = false;
Assert.That(m_Link.biDirectional, Is.False);
m_Link.bidirectional = true;
Assert.That(m_Link.biDirectional, Is.True);
}
[Test]
public void Link_WhenDeprecatedBiDirectionalChanges_BidirectionalChangesToo()
{
m_Link.biDirectional = false;
Assert.That(m_Link.bidirectional, Is.False);
m_Link.biDirectional = true;
Assert.That(m_Link.bidirectional, Is.True);
}
[Test]
public void Link_WhenAutoUpdateChanges_DeprecatedAutoUpdatePositionsChangesToo()
{
m_Link.autoUpdate = true;
Assert.That(m_Link.autoUpdatePositions, Is.True);
m_Link.autoUpdate = false;
Assert.That(m_Link.autoUpdatePositions, Is.False);
}
[Test]
public void Link_WhenDeprecatedAutoUpdatePositionsChanges_AutoUpdateChangesToo()
{
m_Link.autoUpdatePositions = true;
Assert.That(m_Link.autoUpdate, Is.True);
m_Link.autoUpdatePositions = false;
Assert.That(m_Link.autoUpdate, Is.False);
}
[Test]
public void Link_WhenCostModifierChanges_DeprecatedCostOverrideChangesToo()
{
m_Link.costModifier = 1f;
Assert.That(m_Link.costOverride, Is.EqualTo(1f));
m_Link.costModifier = 0f;
Assert.That(m_Link.costOverride, Is.EqualTo(0f));
m_Link.costModifier = -4.123f;
Assert.That(m_Link.costOverride, Is.EqualTo(-4.123f));
}
[Test]
public void Link_WhenDeprecatedCostOverrideChanges_CostModifierChangesToo()
{
m_Link.costOverride = 1f;
Assert.That(m_Link.costModifier, Is.EqualTo(1f));
m_Link.costOverride = 0f;
Assert.That(m_Link.costModifier, Is.EqualTo(0f));
m_Link.costOverride = -4.123f;
Assert.That(m_Link.costModifier, Is.EqualTo(-4.123f));
}
[Test]
public void Link_OnDeprecatedUpdatePositions_AppliesChangesImmediately()
{
m_Link.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
m_Link.UpdatePositions();
m_PathfindingEnd.transform.position = m_ExtraNavMesh.transform.position;
VerifyLinkConnection(ResultsIn.PathForward, m_Link.area, m_Link.agentTypeID);
}
#pragma warning restore CS0618
internal enum ResultsIn
{
PathForward,
PathOnlyForward,
PathBothWays,
NoPath
}
void VerifyLinkConnection(ResultsIn expected,
int areaType = 0,
int agentTypeID = 0)
{
var forwardWanted = expected != ResultsIn.NoPath;
try
{
Assert.That(HasPathConnecting(m_PathfindingStart, m_PathfindingEnd, 1 << areaType, agentTypeID), Is.EqualTo(forwardWanted),
forwardWanted ? "The NavMesh patches should be connected." : "The NavMesh patches should not be connected.");
}
catch (AssertionException)
{
Debug.Log($"The NavMesh patches at {m_PathfindingStart.transform.position} and {m_PathfindingEnd.transform.position} should {(forwardWanted ? "" : "not ")}be connected (agent={NavMesh.GetSettingsNameFromID(agentTypeID)}, area={areaType}).");
throw;
}
if (expected != ResultsIn.PathForward && expected != ResultsIn.NoPath)
{
var backwardWanted = expected == ResultsIn.PathBothWays;
try
{
Assert.That(HasPathConnecting(m_PathfindingEnd, m_PathfindingStart, 1 << areaType, agentTypeID), Is.EqualTo(backwardWanted),
backwardWanted ? "The NavMesh patches should be connected backward." : "The NavMesh patches should not be connected backward.");
}
catch (AssertionException)
{
Debug.Log($"The NavMesh patches at {m_PathfindingStart.transform.position} and {m_PathfindingEnd.transform.position} should {(backwardWanted ? "" : "not ")}be connected backward (agent={NavMesh.GetSettingsNameFromID(agentTypeID)}, area={areaType}).");
throw;
}
}
}
static bool HasPathConnecting(GameObject a, GameObject b, int areaMask = NavMesh.AllAreas, int agentTypeID = 0)
{
var path = new NavMeshPath();
var filter = new NavMeshQueryFilter
{
areaMask = areaMask,
agentTypeID = agentTypeID
};
NavMesh.CalculatePath(a.transform.position, b.transform.position, filter, path);
return path.status == NavMeshPathStatus.PathComplete;
}
static bool HasPathConnectingViaPoint(GameObject a, GameObject b, Vector3 point, int areaMask = NavMesh.AllAreas, int agentTypeID = 0)
{
var path = new NavMeshPath();
var filter = new NavMeshQueryFilter
{
areaMask = areaMask,
agentTypeID = agentTypeID
};
NavMesh.CalculatePath(a.transform.position, b.transform.position, filter, path);
if (path.status != NavMeshPathStatus.PathComplete)
return false;
var pathCorners = path.corners;
for (var i = 1; i < pathCorners.Length - 1; i++)
{
var corner = pathCorners[i];
if (Vector3.Distance(corner, point) < 0.1f)
return true;
}
return false;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 264ca61ae1b6e4c16bcfc55f84300402
timeCreated: 1490606318
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
class NavMeshSurfaceModifierTests
{
NavMeshSurface m_Surface;
NavMeshModifier m_Modifier;
NavMeshSurfaceBuildFromAwake m_BuildOnAwake;
[SetUp]
public void CreatePlaneWithModifier()
{
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_Surface = plane.AddComponent<NavMeshSurface>();
m_Modifier = plane.AddComponent<NavMeshModifier>();
}
[TearDown]
public void DestroyPlaneWithModifier()
{
Object.DestroyImmediate(m_Modifier.gameObject);
}
[Test]
public void ModifierIgnoreAffectsSelf()
{
m_Modifier.ignoreFromBuild = true;
m_Surface.BuildNavMesh();
Assert.IsFalse(NavMeshSurfaceTests.HasNavMeshAtOrigin());
}
[Test]
public void ModifierIgnoreAffectsChild()
{
m_Modifier.ignoreFromBuild = true;
m_Modifier.GetComponent<MeshRenderer>().enabled = false;
var childPlane = GameObject.CreatePrimitive(PrimitiveType.Plane);
childPlane.transform.SetParent(m_Modifier.transform);
m_Surface.BuildNavMesh();
Assert.IsFalse(NavMeshSurfaceTests.HasNavMeshAtOrigin());
Object.DestroyImmediate(childPlane);
}
[Test]
public void ModifierIgnoreDoesNotAffectSibling()
{
m_Modifier.ignoreFromBuild = true;
m_Modifier.GetComponent<MeshRenderer>().enabled = false;
var siblingPlane = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_Surface.BuildNavMesh();
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin());
Object.DestroyImmediate(siblingPlane);
}
[Test]
public void ModifierOverrideAreaAffectsSelf()
{
m_Modifier.area = 4;
m_Modifier.overrideArea = true;
m_Surface.BuildNavMesh();
var expectedAreaMask = 1 << 4;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
}
[Test]
public void Modifier_WhenSurfaceBuiltOnAwake_OverridesArea()
{
m_Modifier.gameObject.SetActive(false);
m_BuildOnAwake = m_Modifier.gameObject.AddComponent<NavMeshSurfaceBuildFromAwake>();
m_BuildOnAwake.surface = m_Surface;
m_Modifier.area = 4;
m_Modifier.overrideArea = true;
// Enable the components to build the NavMesh on Awake
m_Modifier.gameObject.SetActive(true);
NavMesh.SamplePosition(Vector3.zero, out var hit, 0.1f, NavMesh.AllAreas);
Assume.That(hit.hit, Is.True, "There should be a NavMesh at position (0,0,0).");
Assert.That(hit.mask, Is.EqualTo(1 << m_Modifier.area),
"The NavMesh should have the Modifier area mask at position (0,0,0).");
}
[Test]
public void ModifierOverrideAreaAffectsChild()
{
m_Modifier.area = 4;
m_Modifier.overrideArea = true;
m_Modifier.GetComponent<MeshRenderer>().enabled = false;
var childPlane = GameObject.CreatePrimitive(PrimitiveType.Plane);
childPlane.transform.SetParent(m_Modifier.transform);
m_Surface.BuildNavMesh();
var expectedAreaMask = 1 << 4;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
Object.DestroyImmediate(childPlane);
}
[Test]
public void ModifierOverrideAreaDoesNotAffectSibling()
{
m_Modifier.area = 4;
m_Modifier.overrideArea = true;
m_Modifier.GetComponent<MeshRenderer>().enabled = false;
var siblingPlane = GameObject.CreatePrimitive(PrimitiveType.Plane);
m_Surface.BuildNavMesh();
var expectedAreaMask = 1;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
Object.DestroyImmediate(siblingPlane);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f9fe5cff05af44c0fb71a8dcc44efc41
timeCreated: 1490606318
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using NUnit.Framework;
using UnityEngine;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
class NavMeshSurfaceModifierVolumeTests
{
NavMeshSurface surface;
NavMeshModifierVolume modifier;
[SetUp]
public void CreatePlaneAndModifierVolume()
{
var go = GameObject.CreatePrimitive(PrimitiveType.Plane);
surface = go.AddComponent<NavMeshSurface>();
modifier = new GameObject().AddComponent<NavMeshModifierVolume>();
}
[TearDown]
public void DestroyPlaneAndModifierVolume()
{
GameObject.DestroyImmediate(surface.gameObject);
GameObject.DestroyImmediate(modifier.gameObject);
}
[Test]
public void AreaAffectsNavMeshOverlapping()
{
modifier.center = Vector3.zero;
modifier.size = Vector3.one;
modifier.area = 4;
surface.BuildNavMesh();
var expectedAreaMask = 1 << 4;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
}
[Test]
public void AreaDoesNotAffectsNavMeshWhenNotOverlapping()
{
modifier.center = 1.1f * Vector3.right;
modifier.size = Vector3.one;
modifier.area = 4;
surface.BuildNavMesh();
var expectedAreaMask = 1;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
}
[Test]
public void BuildUsesOnlyIncludedModifierVolume()
{
modifier.center = Vector3.zero;
modifier.size = Vector3.one;
modifier.area = 4;
modifier.gameObject.layer = 7;
surface.layerMask = ~(1 << 7);
surface.BuildNavMesh();
var expectedAreaMask = 1;
Assert.IsTrue(NavMeshSurfaceTests.HasNavMeshAtOrigin(expectedAreaMask));
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f5d873c779b014234ba92f3cb7476da6
timeCreated: 1490606318
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,267 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
class NavMeshSurfaceTests
{
GameObject plane;
NavMeshSurface surface;
[SetUp]
public void CreatePlaneWithSurface()
{
plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
surface = new GameObject().AddComponent<NavMeshSurface>();
Assert.IsFalse(HasNavMeshAtOrigin());
}
[TearDown]
public void DestroyPlaneWithSurface()
{
GameObject.DestroyImmediate(plane);
GameObject.DestroyImmediate(surface.gameObject);
Assert.IsFalse(HasNavMeshAtOrigin());
}
[Test]
public void NavMeshIsAvailableAfterBuild()
{
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin());
}
[Test]
public void NavMeshCanBeRemovedAndAdded()
{
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin());
surface.RemoveData();
Assert.IsFalse(HasNavMeshAtOrigin());
surface.AddData();
Assert.IsTrue(HasNavMeshAtOrigin());
}
[Test]
public void NavMeshIsNotAvailableWhenDisabled()
{
surface.BuildNavMesh();
surface.enabled = false;
Assert.IsFalse(HasNavMeshAtOrigin());
surface.enabled = true;
Assert.IsTrue(HasNavMeshAtOrigin());
}
[Test]
public void CanBuildWithCustomArea()
{
surface.defaultArea = 4;
var expectedAreaMask = 1 << 4;
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin(expectedAreaMask));
}
[Test]
public void CanBuildWithCustomAgentTypeID()
{
surface.agentTypeID = 1234;
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin(NavMesh.AllAreas, 1234));
}
[Test]
public void CanBuildCollidersAndIgnoreRenderMeshes()
{
plane.GetComponent<MeshRenderer>().enabled = false;
surface.useGeometry = NavMeshCollectGeometry.PhysicsColliders;
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin());
surface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
}
[Test]
#if !NMC_CAN_ACCESS_PHYSICS
[Ignore("This test requires the com.unity.modules.physics package in order to run. Make sure to reference it in the project.")]
#endif
public void CanBuildRenderMeshesAndIgnoreColliders()
{
#if NMC_CAN_ACCESS_PHYSICS
plane.GetComponent<Collider>().enabled = false;
surface.useGeometry = NavMeshCollectGeometry.PhysicsColliders;
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
surface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtOrigin());
#endif
}
[Test]
public void BuildIgnoresGeometryOutsideBounds()
{
surface.collectObjects = CollectObjects.Volume;
surface.center = new Vector3(20, 0, 0);
surface.size = new Vector3(10, 10, 10);
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
}
[Test]
public void BuildIgnoresGeometrySiblings()
{
surface.collectObjects = CollectObjects.Children;
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
}
[Test]
public void BuildDoesntCullAreaBiggerThanMinRegionArea()
{
// Move plane away from NavMesh tile's boundaries
plane.transform.localScale = new Vector3(0.25f, 0, 0.25f);
plane.transform.position = new Vector3(2.5f, 0, 7.5f);
surface.minRegionArea = 1f;
surface.BuildNavMesh();
Assert.IsTrue(HasNavMeshAtPosition(plane.transform.position));
}
[Test]
public void BuildCullsAreaSmallerThanMinRegionArea()
{
// Move plane away from NavMesh tile's boundaries
plane.transform.localScale = new Vector3(0.25f, 0, 0.25f);
plane.transform.position = new Vector3(2.5f, 0, 7.5f);
surface.minRegionArea = 5;
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtPosition(plane.transform.position));
}
[Test]
public void BuildUsesOnlyIncludedLayers()
{
plane.layer = 4;
surface.layerMask = ~(1 << 4);
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
}
[Test]
public void DefaultSettingsMatchBuiltinSettings()
{
var bs = surface.GetBuildSettings();
Assert.AreEqual(NavMesh.GetSettingsByIndex(0), bs);
}
[Test]
public void ActiveSurfacesContainsOnlyActiveAndEnabledSurface()
{
Assert.IsTrue(NavMeshSurface.activeSurfaces.Contains(surface));
Assert.AreEqual(1, NavMeshSurface.activeSurfaces.Count);
surface.enabled = false;
Assert.IsFalse(NavMeshSurface.activeSurfaces.Contains(surface));
Assert.AreEqual(0, NavMeshSurface.activeSurfaces.Count);
surface.enabled = true;
surface.gameObject.SetActive(false);
Assert.IsFalse(NavMeshSurface.activeSurfaces.Contains(surface));
Assert.AreEqual(0, NavMeshSurface.activeSurfaces.Count);
}
[UnityTest]
public IEnumerator NavMeshMovesToSurfacePositionNextFrame()
{
plane.transform.position = new Vector3(100, 0, 0);
surface.transform.position = new Vector3(100, 0, 0);
surface.BuildNavMesh();
Assert.IsFalse(HasNavMeshAtOrigin());
surface.transform.position = Vector3.zero;
Assert.IsFalse(HasNavMeshAtOrigin());
yield return null;
Assert.IsTrue(HasNavMeshAtOrigin());
}
[UnityTest]
public IEnumerator UpdatingAndAddingNavMesh()
{
var navMeshData = new NavMeshData();
var oper = surface.UpdateNavMesh(navMeshData);
Assert.IsFalse(HasNavMeshAtOrigin());
do
{
yield return null;
} while (!oper.isDone);
surface.RemoveData();
surface.navMeshData = navMeshData;
surface.AddData();
Assert.IsTrue(HasNavMeshAtOrigin());
}
[Test]
public void BuildTakesIntoAccountAdjacentWalkableSurfacesOutsideBounds()
{
surface.collectObjects = CollectObjects.Volume;
surface.center = new Vector3(0, 0, 0);
surface.size = new Vector3(10, 10, 10);
var adjacentPlane = GameObject.CreatePrimitive(PrimitiveType.Plane);
adjacentPlane.transform.position = new Vector3(10f, 0, 0);
surface.BuildNavMesh();
try
{
Assert.IsTrue(HasNavMeshAtPosition(new Vector3(surface.size.x / 2f, 0, 0)),
"A NavMesh should exists at the desired position.");
}
finally
{
Object.DestroyImmediate(adjacentPlane);
}
}
static bool HasNavMeshAtPosition(Vector3 position, int areaMask = NavMesh.AllAreas, int agentTypeID = 0)
{
var filter = new NavMeshQueryFilter {areaMask = areaMask, agentTypeID = agentTypeID};
return NavMesh.SamplePosition(position, out _, 0.1f, filter);
}
public static bool HasNavMeshAtOrigin(int areaMask = NavMesh.AllAreas, int agentTypeID = 0)
{
return HasNavMeshAtPosition(Vector3.zero, areaMask, agentTypeID);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 925111c157b314873b97f89e0f03d570
timeCreated: 1490606318
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c10f4966eb108848acf7461aaf6057e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
class AddDynamicOffMeshLinkWorks : OffMeshLinkTestBase
{
const string k_SceneName = "OffMeshLinkTwoPlanesScene";
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
}
[UnityTest]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXServer, RuntimePlatform.WindowsServer, RuntimePlatform.LinuxServer })] //MTT-4133 Fails on Dedicated Server
public IEnumerator OffMeshLink_WhenAddedToGameObject_BecomesUsableImmediately()
{
CreateBiDirectionalLink(true);
m_Agent.SetDestination(m_PlaneEnd.position);
yield return null;
Assert.That(m_Agent.pathStatus, Is.EqualTo(NavMeshPathStatus.PathComplete), "DynamicOffMeshLink has not been created.");
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,61 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[Category("RegressionTest")]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
class AgentCustomLinkMovement : OffMeshLinkTestBase
{
const string k_SceneName = "OffMeshLinkTwoPlanesScene";
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
}
[UnityTest]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXServer, RuntimePlatform.WindowsServer, RuntimePlatform.LinuxServer })] //MTT-4133 Fails on Dedicated Server
public IEnumerator Agent_WithoutAutoTraverseOnOffMeshLink_DoesNotMoveByItself()
{
var link = CreateBiDirectionalLink(true);
m_Agent.autoTraverseOffMeshLink = false;
m_Agent.baseOffset = 1.0f;
m_Agent.transform.position = link.startTransform.position;
var hasDestination = m_Agent.SetDestination(link.endTransform.position);
Assert.That(hasDestination, Is.True, "NavMeshAgent destination has not been set.");
yield return null;
Assert.That(m_Agent.isOnOffMeshLink, Is.True, "NavMeshAgent is currently not positioned on NavMeshLink.");
// Move to gap between the NavMeshes connected by the NavMeshLink
var midAirPosition = new Vector3(17.71f, 3.92f, -6.66f);
m_Agent.transform.position = midAirPosition;
yield return null;
// Ensure the agent stays at this position - as 'autoTraverseOffMeshLink' is false
Assert.That(m_Agent.transform.position, Is.EqualTo(midAirPosition).Using(new Vector3EqualityComparer(0.01f)), "NavMeshAgent should be at midAirPosition.");
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,66 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[Category("RegressionTest")]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
class AgentVelocityTestAfterOffMeshLink : OffMeshLinkTestBase
{
readonly string k_SceneName = "OffMeshLinkTwoPlanesScene";
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
}
[UnityTest]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXServer, RuntimePlatform.WindowsServer, RuntimePlatform.LinuxServer })]
public IEnumerator Agent_AfterTraversingOffMeshLink_HasVelocityAlignedWithTheLink()
{
var link = CreateBiDirectionalLink(true);
m_Agent.transform.position = link.startTransform.position + new Vector3(3, 0, 3);
m_Agent.SetDestination(link.endTransform.position + new Vector3(-3, 0, 3));
yield return null;
while (!m_Agent.isOnOffMeshLink)
yield return null;
while (m_Agent.isOnOffMeshLink)
yield return null;
yield return 0;
var agentMoveDir = m_Agent.velocity;
agentMoveDir.y = 0;
agentMoveDir = agentMoveDir.normalized;
var linkDir = link.endTransform.position - link.startTransform.position;
linkDir.y = 0;
linkDir = linkDir.normalized;
// Get the angle in degrees between the direction vectors.
var angle = Vector3.Angle(linkDir, agentMoveDir);
Assert.That(angle, Is.LessThan(5.0f), "Agent Velocity is not aligned with the off-mesh link.");
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,88 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(CurrentNextOffMeshLinkDataSetUp))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(CurrentNextOffMeshLinkDataSetUp))]
class CurrentNextOffMeshLinkData
{
const string k_SceneName = "OffMeshLinkTest";
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
}
[UnityTest]
[Explicit("Unstable test")]
public IEnumerator Agent_TraversingOffMeshLink_ReportsCorrectCurrentAndNextLink()
{
var agent = GameObject.Find("Agent").GetComponent<NavMeshAgent>();
var offMeshLink = GameObject.Find("Plane1").GetComponent<NavMeshLink>();
var target = GameObject.Find("Plane2").transform;
Assert.That(offMeshLink, Is.Not.Null, "Didn't find Off-mesh link");
Assert.That(agent, Is.Not.Null, "Didn't find NavMeshAgent");
var destinationSet = agent.SetDestination(target.position);
agent.speed *= 10;
Assert.That(destinationSet, Is.True, "NavMeshAgent's destination position is not set");
// Wait for path calculation
yield return null;
// Before link
while (!agent.isOnOffMeshLink)
{
Assert.That(agent.currentOffMeshLinkData.valid, Is.False, "Before link : agent.currentOffMeshLinkData is valid");
AssertValidOffMeshLinkData(agent.nextOffMeshLinkData, offMeshLink);
yield return null;
}
// On link
while (agent.isOnOffMeshLink)
{
Assert.That(agent.nextOffMeshLinkData.valid, Is.False, "On link : agent.nextOffMeshLinkData is valid");
AssertValidOffMeshLinkData(agent.currentOffMeshLinkData, offMeshLink);
yield return null;
}
// After link
Assert.That(agent.currentOffMeshLinkData.valid, Is.False, "After link : agent.currentOffMeshLinkData is valid");
Assert.That(agent.nextOffMeshLinkData.valid, Is.False, "After link : agent.nextOffMeshLinkData is valid");
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
static void AssertValidOffMeshLinkData(OffMeshLinkData offMeshLinkData, NavMeshLink offMeshLink)
{
// Double check to avoid spamming success in log-file (decreasing tests performance)
Assert.That(offMeshLinkData.valid, Is.True, "OffMeshLinkData should be valid.");
Assert.That(offMeshLinkData.activated, Is.True, "OffMeshLinkData should be activated.");
Assert.That(offMeshLinkData.linkType, Is.EqualTo(OffMeshLinkType.LinkTypeManual), "OffMeshLinkData's linkType should be Manual.");
Assert.That(offMeshLinkData.owner, Is.EqualTo(offMeshLink), "OffMeshLinkData should reference the NavMeshLink in the scene as the owner object.");
#pragma warning disable CS0618
Assert.That(offMeshLinkData.offMeshLink, Is.Null, "OffMeshLinkData should not reference any legacy OffMeshLink in the scene.");
#pragma warning restore CS0618
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,77 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
class OffMeshLinkGetSetNavMeshArea : OffMeshLinkTestBase
{
int m_AreaMask;
NavMeshLink m_Link;
readonly string k_SceneName = "OffMeshLinkTwoPlanesScene";
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
}
[UnityTest]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXServer, RuntimePlatform.WindowsServer, RuntimePlatform.LinuxServer })]
public IEnumerator OffMeshLink_WithCustomArea_AllowsThroughOnlyPathsWithMatchingMasks()
{
m_Link = CreateBiDirectionalLink(true);
yield return null;
var defaultArea = NavMesh.GetAreaFromName("Walkable");
var jumpArea = NavMesh.GetAreaFromName("Jump");
Assume.That(m_Link.area, Is.EqualTo(defaultArea), "Unexpected NavMesh area for NavMeshLink");
// Check we can pass 'default' with 'default' mask
m_AreaMask = 1 << defaultArea;
VerifyAreaPassing(true);
// Change oml area to 'jump'
m_Link.area = jumpArea;
Assume.That(m_Link.area, Is.EqualTo(jumpArea), "Unexpected NavMesh area for NavMeshLink");
// Check we cannot pass 'jump' with 'default' mask
VerifyAreaPassing(false);
// Check we can pass 'jump' with 'default' + 'jump' mask
m_AreaMask |= 1 << jumpArea;
VerifyAreaPassing(true);
}
void VerifyAreaPassing(bool expectToPass)
{
var path = new NavMeshPath();
NavMesh.CalculatePath(m_PlaneStart.position, m_PlaneEnd.position, m_AreaMask, path);
if (expectToPass)
Assert.That(path.status, Is.EqualTo(NavMeshPathStatus.PathComplete),
"Expected complete path; with NavMesh area mask " + m_AreaMask + " when NavMeshLink area is " + m_Link.area);
else
Assert.That(path.status, Is.EqualTo(NavMeshPathStatus.PathPartial),
"Expected partial path; with NavMesh area mask " + m_AreaMask + " when NavMeshLink area is " + m_Link.area);
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,85 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
[TestFixture]
[PrebuildSetup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
[PostBuildCleanup("Unity.AI.Navigation.Tests." + nameof(SimpleScene2PlanesNavigationSetup))]
class OffMeshLinkMultipleAddComponent
{
const string k_SceneName = "OffMeshLinkTwoPlanesScene";
GameObject m_LinkGO;
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return SceneManager.LoadSceneAsync(k_SceneName, LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(k_SceneName));
m_LinkGO = new GameObject("OffMeshLinkMultipleAddComponent");
}
[Test]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXServer, RuntimePlatform.WindowsServer, RuntimePlatform.LinuxServer })]
public void OffMeshLink_WhenMultipleAddedToGameObject_AreAllUsable()
{
var a = GameObject.Find("plane1").GetComponent<Transform>();
var b = GameObject.Find("plane2").GetComponent<Transform>();
Assert.That(a, Is.Not.Null, "Plane1 is missing.");
Assert.That(b, Is.Not.Null, "Plane2 is missing.");
var pathAB = new NavMeshPath();
var pathBA = new NavMeshPath();
var foundAB = NavMesh.CalculatePath(a.position, b.position, -1, pathAB);
var foundBA = NavMesh.CalculatePath(b.position, a.position, -1, pathBA);
Assert.That(foundAB, Is.True, "Found unexpected path A->B.");
Assert.That(foundBA, Is.True, "Found unexpected path B->A.");
// Create setup where one GO has two OffMeshLinks with 'Bi Directional' set to false
AddOneWayLink(a, b);
AddOneWayLink(b, a);
// Tests that path a->b and b->a are valid and have same end-points (mirrored).
foundAB = NavMesh.CalculatePath(a.position, b.position, -1, pathAB);
foundBA = NavMesh.CalculatePath(b.position, a.position, -1, pathBA);
Assert.That(foundAB, Is.True, "No path from A->B");
Assert.That(foundBA, Is.True, "No path from B->A");
var d1 = Vector3.Distance(pathAB.corners[0], pathBA.corners[pathBA.corners.Length - 1]);
var d2 = Vector3.Distance(pathAB.corners[pathAB.corners.Length - 1], pathBA.corners[0]);
Assert.That(d1, Is.EqualTo(0.0f).Within(1e-5f), "Endpoint mismatch: A start -> B end.");
Assert.That(d2, Is.EqualTo(0.0f).Within(1e-5f), "Endpoint mismatch: B start -> A end.");
}
void AddOneWayLink(Transform start, Transform end)
{
var offMeshLink = m_LinkGO.AddComponent<NavMeshLink>();
Assert.That(offMeshLink, Is.Not.Null, "Failed to create NavMeshLink.");
offMeshLink.bidirectional = false;
offMeshLink.startTransform = start;
offMeshLink.endTransform = end;
// we modified the endpoint references above - now explicitly update positions.
offMeshLink.UpdateLink();
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
Object.DestroyImmediate(m_LinkGO);
yield return SceneManager.UnloadSceneAsync(k_SceneName);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,41 @@
#if UNITY_EDITOR || UNITY_STANDALONE
using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
namespace Unity.AI.Navigation.Tests
{
class OffMeshLinkTestBase
{
protected Transform m_PlaneStart;
protected Transform m_PlaneEnd;
protected NavMeshAgent m_Agent;
public NavMeshLink CreateBiDirectionalLink(bool autoUpdatePositions)
{
var planeStartGO = GameObject.Find("plane1");
Assert.That(planeStartGO, Is.Not.Null, "Didn't find gameobject plane1");
m_PlaneStart = planeStartGO.transform;
var planeEndGO = GameObject.Find("plane2");
Assert.That(planeEndGO, Is.Not.Null, "Didn't find gameobject plane2");
m_PlaneEnd = planeEndGO.transform;
var agentGo = GameObject.Find("Agent");
Assert.That(agentGo, Is.Not.Null, "Didn't find gameobject Agent");
m_Agent = agentGo.GetComponent<NavMeshAgent>();
Assert.That(m_Agent, Is.Not.Null, "Didn't find component NavMeshAgent in gameobject Agent");
m_Agent.speed *= 10.0f;
m_Agent.acceleration *= 10.0f;
var linkGO = new GameObject("link");
var link = linkGO.AddComponent<NavMeshLink>();
Assert.That(link, Is.Not.Null, "Unable to add NavMeshLink component.");
link.startTransform = m_PlaneStart;
link.endTransform = m_PlaneEnd;
link.autoUpdate = autoUpdatePositions;
return link;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 028144d6018e97b4eabdb685c9fc01a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using Utils = UnityEngine.TestTools.Utils.Utils;
#pragma warning disable CS0618 // UnityEditor.AI.NavMeshBuilder is necessary in this implementation
namespace Unity.AI.Navigation.Tests
{
class CurrentNextOffMeshLinkDataSetUp : PrebuiltSceneSetup
{
protected override string GetSceneFile()
{
return "OffMeshLinkTest.unity";
}
protected override void SceneSetup()
{
#if UNITY_EDITOR
var myScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
SceneManager.SetActiveScene(myScene);
var plane1 = Utils.CreatePrimitive(PrimitiveType.Plane);
GameObjectUtility.SetStaticEditorFlags(plane1, StaticEditorFlags.NavigationStatic);
plane1.name = "Plane1";
plane1.transform.position = Vector3.zero;
var plane2 = Utils.CreatePrimitive(PrimitiveType.Plane);
GameObjectUtility.SetStaticEditorFlags(plane2, StaticEditorFlags.NavigationStatic);
plane2.name = "Plane2";
plane2.transform.position = new Vector3(0, 0, 15);
var offMeshLink = plane1.AddComponent<NavMeshLink>();
offMeshLink.startTransform = plane1.transform;
offMeshLink.endTransform = plane2.transform;
var cube = Utils.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = new Vector3(0, 0, -4);
cube.name = "Agent";
cube.AddComponent<NavMeshAgent>();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), pathToTestScene);
UnityEditor.AI.NavMeshBuilder.BuildNavMesh();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), pathToTestScene);
EditorSceneManager.CloseScene(myScene, true);
UnityEditor.AI.NavMeshBuilder.ClearAllNavMeshes();
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.TestTools;
namespace Unity.AI.Navigation.Tests
{
abstract class PrebuiltSceneSetup : IPrebuildSetup, IPostBuildCleanup
{
const string k_RootDir = "Assets";
const string k_TestDir = "TmpScenes";
string testDirectory { get; set; } = "";
protected string pathToTestScene { get; private set; } = "";
protected abstract string GetSceneFile();
protected abstract void SceneSetup();
public void Setup()
{
#if UNITY_EDITOR
testDirectory = Path.Combine(k_RootDir, k_TestDir);
pathToTestScene = Path.Combine(testDirectory, GetSceneFile());
AssetDatabase.Refresh();
if (!AssetDatabase.IsValidFolder(testDirectory))
testDirectory = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder(k_RootDir, k_TestDir));
AssetDatabase.Refresh();
SceneSetup();
var editorBuildSettingsScenes = new List<EditorBuildSettingsScene>(EditorBuildSettings.scenes)
{
new EditorBuildSettingsScene(pathToTestScene, true)
};
EditorBuildSettings.scenes = editorBuildSettingsScenes.ToArray();
#endif
}
public void Cleanup()
{
#if UNITY_EDITOR
AssetDatabase.Refresh();
testDirectory = Path.Combine(k_RootDir, k_TestDir);
pathToTestScene = Path.Combine(testDirectory, GetSceneFile());
var baseSceneGuidTxt = AssetDatabase.AssetPathToGUID(pathToTestScene);
if (AssetDatabase.IsValidFolder(testDirectory))
AssetDatabase.DeleteAsset(testDirectory);
if (GUID.TryParse(baseSceneGuidTxt, out var sceneGuid))
EditorBuildSettings.scenes = EditorBuildSettings.scenes.Where(scene => scene.guid != sceneGuid).ToArray();
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,50 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools.Utils;
#pragma warning disable CS0618 // UnityEditor.AI.NavMeshBuilder is necessary in this implementation
namespace Unity.AI.Navigation.Tests
{
class SimpleScene2PlanesNavigationSetup : PrebuiltSceneSetup
{
protected override string GetSceneFile()
{
return "OffMeshLinkTwoPlanesScene.unity";
}
protected override void SceneSetup()
{
#if UNITY_EDITOR
var myScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
SceneManager.SetActiveScene(myScene);
var plane1 = Utils.CreatePrimitive(PrimitiveType.Plane);
plane1.transform.position = new Vector3(10f, 0f, 0f);
plane1.name = "plane1";
GameObjectUtility.SetStaticEditorFlags(plane1, StaticEditorFlags.NavigationStatic);
var plane2 = Utils.CreatePrimitive(PrimitiveType.Plane);
plane2.transform.position = new Vector3(25f, 0f, 0f);
plane2.name = "plane2";
GameObjectUtility.SetStaticEditorFlags(plane2, StaticEditorFlags.NavigationStatic);
var capsule = Utils.CreatePrimitive(PrimitiveType.Capsule);
capsule.name = "Agent";
capsule.transform.position = new Vector3(6, 0, 0);
capsule.AddComponent<NavMeshAgent>();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), pathToTestScene);
UnityEditor.AI.NavMeshBuilder.BuildNavMesh();
EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), pathToTestScene);
EditorSceneManager.CloseScene(myScene, true);
UnityEditor.AI.NavMeshBuilder.ClearAllNavMeshes();
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,21 @@
{
"name": "Unity.AI.Navigation.LegacyOffMeshLink.Tests",
"rootNamespace": "",
"references": [
"GUID:8c4dd21966739024fbd72155091d199e",
"GUID:27619889b8ba8c24980f49ee34dbb44a"
],
"includePlatforms": [],
"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: 68d7ff144262c9148888e665efa8692c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
{
"name": "Unity.AI.Navigation.Tests",
"rootNamespace": "",
"references": [
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:8c4dd21966739024fbd72155091d199e"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
{
"name": "com.unity.modules.physics",
"expression": "1.0.0",
"define": "NMC_CAN_ACCESS_PHYSICS"
}
],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 48fe869bd7a418849ba6d20ca02072c7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
#if UNITY_EDITOR
namespace Unity.AI.Navigation.Editor.Tests.InPlaymode
{
[TestFixture]
[Explicit]
[UnityPlatform(include = new[] { RuntimePlatform.LinuxEditor, RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor })]
[Description("These tests run on demand and only in the editor playmode")]
public class NavMeshLinkEditorTestsInPlaymode : NavMeshLinkEditorTests { }
}
#endif

View File

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

View File

@@ -0,0 +1,30 @@
{
"name": "Unity.AI.Navigation.Tests.EditorInPlaymode",
"rootNamespace": "",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"Unity.AI.Navigation",
"Unity.AI.Navigation.Editor",
"Unity.AI.Navigation.Editor.Tests"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
{
"name": "com.unity.modules.physics",
"expression": "1.0.0",
"define": "NMC_CAN_ACCESS_PHYSICS"
}
],
"noEngineReferences": false
}

View File

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