Files
Bachelor-Arbeit-Adrian-Haefner/Library/PackageCache/com.unity.xr.arfoundation@ef86c118adc4/Editor/Simulation/SimulationEnvironmentAssetsManager.cs
adriadri6972 d3d9c5f833 upload project
2025-07-31 15:21:08 +02:00

406 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using Unity.XR.CoreUtils;
using Unity.XR.CoreUtils.Editor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.XR.Simulation;
namespace UnityEditor.XR.Simulation
{
/// <summary>
/// Manager that handles collection of simulation environment prefabs in the Editor.
/// </summary>
[ScriptableSettingsPath(SimulationConstants.userSettingsPath)]
class SimulationEnvironmentAssetsManager : EditorScriptableSettings<SimulationEnvironmentAssetsManager>
{
static readonly Comparer<string> k_PrefabPathsComparer = Comparer<string>.Default;
const string k_PrefabFilter = "t:prefab";
const string k_RefreshMenuItemCategory = "Assets/AR Foundation";
const string k_RefreshMenuItemName = "Refresh XR Environment List";
const string k_RefreshMenuItemPath = k_RefreshMenuItemCategory + "/" + k_RefreshMenuItemName;
/// <summary>
/// Default file name for a newly created environment asset.
/// </summary>
public const string newEnvironmentFileName = "Simulation Environment.prefab";
[SerializeField]
[HideInInspector]
List<string> m_EnvironmentPrefabPaths = new();
[SerializeField]
[HideInInspector]
bool m_FallbackAtEndOfList;
/// <summary>
/// Number of environments available.
/// </summary>
public int environmentsCount => m_EnvironmentPrefabPaths.Count;
/// <summary>
/// Whether there is currently an active environment.
/// </summary>
public bool activeEnvironmentExists => XRSimulationPreferences.Instance.activeEnvironmentPrefab != null;
public event Action activeEnvironmentChanged;
public event Action availableEnvironmentsChanged;
[MenuItem(k_RefreshMenuItemPath)]
static void RefreshEnvironmentListMenuItem()
{
Instance.CollectEnvironments();
}
/// <summary>
/// Gathers all environment assets handled by this manager and saves them to a list of available environments.
/// </summary>
public void CollectEnvironments()
{
var simulationPreferences = XRSimulationPreferences.Instance;
var fallbackEnvPrefab = simulationPreferences.fallbackEnvironmentPrefab;
var fallbackEnvPath = "";
m_EnvironmentPrefabPaths.Clear();
var prefabGuids = AssetDatabase.FindAssets(k_PrefabFilter);
foreach (var guid in prefabGuids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var simEnvironment = AssetDatabase.LoadAssetAtPath<SimulationEnvironment>(path);
if (simEnvironment == null)
continue;
if (simEnvironment.gameObject == fallbackEnvPrefab)
fallbackEnvPath = path;
else if (!simEnvironment.excludeFromSelectionUI)
m_EnvironmentPrefabPaths.Add(path);
}
m_EnvironmentPrefabPaths.Sort(k_PrefabPathsComparer);
// Show fallback environment at the bottom
m_FallbackAtEndOfList = !string.IsNullOrEmpty(fallbackEnvPath);
if (m_FallbackAtEndOfList)
m_EnvironmentPrefabPaths.Add(fallbackEnvPath);
EditorUtility.SetDirty(this);
availableEnvironmentsChanged?.Invoke();
// If no environment is available, even the fallback, we treat this as the active environment being changed so UI can update
if (m_EnvironmentPrefabPaths.Count == 0)
{
activeEnvironmentChanged?.Invoke();
return;
}
if (simulationPreferences.environmentPrefab == null)
{
// Ensure active environment is set if possible
SelectEnvironmentAtIndex(0);
}
}
/// <summary>
/// Sets the active environment to the one at the given index in the list of available environments.
/// </summary>
public void SelectEnvironmentAtIndex(int index)
{
var envCount = environmentsCount;
if (index < 0 || index >= envCount)
throw new IndexOutOfRangeException($"Cannot select environment at index {index} outside the range of available environments.");
var simulationPreferences = XRSimulationPreferences.Instance;
if (index == envCount - 1 && m_FallbackAtEndOfList)
{
// Ensure fallback is used by clearing out the environment prefab field
simulationPreferences.environmentPrefab = null;
}
else
{
var environmentPath = m_EnvironmentPrefabPaths[index];
var selectedEnvironmentPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(environmentPath);
if (selectedEnvironmentPrefab == null)
{
Debug.LogError($"Failed to load environment prefab '{environmentPath}'. " +
$"Try refreshing environments by going to {k_RefreshMenuItemCategory} > {k_RefreshMenuItemName}.");
return;
}
simulationPreferences.environmentPrefab = selectedEnvironmentPrefab;
}
EditorUtility.SetDirty(simulationPreferences);
activeEnvironmentChanged?.Invoke();
}
/// <summary>
/// Gets the file path of the active environment asset.
/// </summary>
public string GetActiveEnvironmentPath()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
return activeEnvironment != null ? AssetDatabase.GetAssetPath(activeEnvironment) : null;
}
/// <summary>
/// Gets the name of the active environment for displaying in UI.
/// </summary>
public string GetActiveEnvironmentDisplayName()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
return activeEnvironment != null ? activeEnvironment.name : null;
}
/// <summary>
/// Gets the index of the active environment in the list of available environments.
/// </summary>
public int GetActiveEnvironmentIndex()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
if (activeEnvironment == null)
return -1;
var environmentPath = AssetDatabase.GetAssetPath(activeEnvironment);
return m_EnvironmentPrefabPaths.IndexOf(environmentPath);
}
/// <summary>
/// Fills out the given list with names for dropdown menu items corresponding to each environment in the list of available environments.
/// </summary>
public void GetAllEnvironmentMenuItemNames(List<string> names)
{
var count = environmentsCount;
if (count == 0)
return;
for (var i = 0; i < count - 1; i++)
{
names.Add(GetDirectoryAndFile(m_EnvironmentPrefabPaths[i]));
}
// Fallback environment is a special case - just show the file name
var lastPath = m_EnvironmentPrefabPaths[count - 1];
names.Add(m_FallbackAtEndOfList ? Path.GetFileNameWithoutExtension(lastPath) : GetDirectoryAndFile(lastPath));
}
static string GetDirectoryAndFile(string path)
{
if (string.IsNullOrEmpty(path))
return "";
var length = path.Length;
var startIndex = length - 1;
var lastIndex = length;
var slashCount = 0;
for (; startIndex >= 0 && slashCount <= 1; --startIndex)
{
var c = path[startIndex];
if (c == '/')
slashCount++;
else if (lastIndex == length && c == '.')
lastIndex = startIndex;
}
if (startIndex < 0)
{
startIndex = 0;
}
else
{
// +1 to ignore last for loop iteration and +1 to ignore last '/'
startIndex += 2;
}
return path.Substring(startIndex, lastIndex - startIndex);
}
/// <summary>
/// Tries to create a new environment asset at the given path.
/// </summary>
/// <param name="assetPath">File path where the new environment should be created.</param>
/// <param name="newEnvironmentIndex">Resulting index of the new environment in the list of available environments.</param>
/// <returns>True if creation was successful, false otherwise.</returns>
public bool TryCreateEnvironment(string assetPath, out int newEnvironmentIndex)
{
var newEnvironmentPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
GameObject newEnvironmentGameObject = null;
var defaultEnvironment = XRSimulationPreferences.Instance.fallbackEnvironmentPrefab;
if (defaultEnvironment != null && defaultEnvironment.GetComponent<SimulationEnvironment>() != null)
{
var defaultEnvironmentPath = AssetDatabase.GetAssetPath(defaultEnvironment);
if (AssetDatabase.CopyAsset(defaultEnvironmentPath, newEnvironmentPath))
{
var newEnvironment = AssetDatabase.LoadAssetAtPath<SimulationEnvironment>(newEnvironmentPath);
newEnvironment.excludeFromSelectionUI = false;
EditorUtility.SetDirty(newEnvironment);
newEnvironmentGameObject = newEnvironment.gameObject;
}
else
Debug.LogWarning($"Failed to copy {defaultEnvironmentPath}. Creating blank environment.");
}
if (newEnvironmentGameObject == null)
{
var newEnvironmentInstance = new GameObject();
newEnvironmentInstance.AddComponent<SimulationEnvironment>();
var newPrefab = PrefabUtility.SaveAsPrefabAsset(newEnvironmentInstance, newEnvironmentPath, out var creationSuccess);
if (creationSuccess)
newEnvironmentGameObject = newPrefab;
DestroyImmediate(newEnvironmentInstance);
}
if (newEnvironmentGameObject != null)
{
ProjectWindowUtil.ShowCreatedAsset(newEnvironmentGameObject);
newEnvironmentIndex = AddEnvironment(newEnvironmentPath);
return true;
}
Debug.LogError($"Failed to create simulation environment at path {newEnvironmentPath}.");
newEnvironmentIndex = -1;
return false;
}
/// <summary>
/// Tries to create a duplicate of the active environment asset at the given path.
/// </summary>
/// <param name="assetPath">File path where the new environment should be created.</param>
/// <param name="newEnvironmentIndex">Resulting index of the new environment in the list of available environments.</param>
/// <returns>True if duplication was successful, false otherwise.</returns>
public bool TryDuplicateActiveEnvironment(string assetPath, out int newEnvironmentIndex)
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
if (activeEnvironment == null)
{
Debug.LogError("No active environment available to duplicate.");
newEnvironmentIndex = -1;
return false;
}
var activeEnvironmentPath = AssetDatabase.GetAssetPath(activeEnvironment);
var newEnvironmentPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
if (AssetDatabase.CopyAsset(activeEnvironmentPath, newEnvironmentPath))
{
var newEnvironmentGameObject = AssetDatabase.LoadAssetAtPath<GameObject>(newEnvironmentPath);
ProjectWindowUtil.ShowCreatedAsset(newEnvironmentGameObject);
newEnvironmentIndex = AddEnvironment(newEnvironmentPath);
return true;
}
Debug.LogError($"Failed to duplicate simulation environment at path {activeEnvironmentPath}.");
newEnvironmentIndex = -1;
return false;
}
/// <summary>
/// Adds an environment to the list of available environments.
/// </summary>
/// <param name="environmentAssetPath">File path of the environment asset to add.</param>
/// <returns>The index of the new environment in the list of available environments.</returns>
int AddEnvironment(string environmentAssetPath)
{
// The environment might already exist in the list, if it was caught by the asset postprocessor first
var existingIndex = m_EnvironmentPrefabPaths.IndexOf(environmentAssetPath);
if (existingIndex >= 0)
return existingIndex;
var envCount = environmentsCount;
var countMinusFallback = m_FallbackAtEndOfList && envCount > 0 ? envCount - 1 : envCount;
var environmentIndex = countMinusFallback;
for (var i = 0; i < countMinusFallback; i++)
{
if (k_PrefabPathsComparer.Compare(environmentAssetPath, m_EnvironmentPrefabPaths[i]) > 0)
continue;
environmentIndex = i;
break;
}
m_EnvironmentPrefabPaths.Insert(environmentIndex, environmentAssetPath);
EditorUtility.SetDirty(this);
availableEnvironmentsChanged?.Invoke();
return environmentIndex;
}
/// <summary>
/// Is the active environment asset editable?
/// </summary>
public bool IsActiveEnvironmentEditable()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
return activeEnvironment != null && !PrefabUtility.IsPartOfImmutablePrefab(activeEnvironment);
}
/// <summary>
/// Opens the active environment asset for editing.
/// </summary>
public void OpenActiveEnvironmentForEditing()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
if (activeEnvironment != null)
PrefabStageUtility.OpenPrefab(AssetDatabase.GetAssetPath(activeEnvironment));
}
public static GUID GetActiveEnvironmentAssetGuid()
{
var activeEnvironment = XRSimulationPreferences.Instance.activeEnvironmentPrefab;
if (activeEnvironment == null)
return default;
var environmentPath = AssetDatabase.GetAssetPath(activeEnvironment);
return AssetDatabase.GUIDFromAssetPath(environmentPath);
}
class EnvironmentAssetPostprocessor : AssetPostprocessor
{
static bool s_NeedsRefresh;
void OnPostprocessPrefab(GameObject g)
{
if (g.GetComponent<SimulationEnvironment>() != null)
s_NeedsRefresh = true;
// Settings instance may not exist on first import, and we should not try to create one
if (BaseInstance == null)
return;
if (BaseInstance.m_EnvironmentPrefabPaths.Contains(AssetDatabase.GetAssetPath(g)))
s_NeedsRefresh = true;
}
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets,
string[] movedFromAssetPaths)
{
if (!s_NeedsRefresh)
{
if (BaseInstance == null)
return;
var environmentPaths = BaseInstance.m_EnvironmentPrefabPaths;
foreach (var deletedAssetPath in deletedAssets)
{
if (environmentPaths.Contains(deletedAssetPath))
{
s_NeedsRefresh = true;
break;
}
}
}
if (s_NeedsRefresh && BaseInstance != null)
BaseInstance.CollectEnvironments();
s_NeedsRefresh = false;
}
}
}
}