mixedreality/com.microsoft.mixedreality..../Core/Utilities/Scenes/EditorSceneUtils.cs

512 lines
18 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if UNITY_EDITOR
using Microsoft.MixedReality.Toolkit.SceneSystem;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Microsoft.MixedReality.Toolkit.Utilities
{
/// <summary>
/// Utilities for loading / saving scenes in editor via SceneInfo.
/// Because SceneInfo is defined in MixedRealityToolkit, this can't be kept in Editor utilities.
/// </summary>
public static class EditorSceneUtils
{
/// <summary>
/// Enum used by this class to specify build settings order
/// </summary>
public enum BuildIndexTarget
{
First,
None,
Last,
}
/// <summary>
/// Creates a new scene with sceneName and saves to path.
/// </summary>
public static SceneInfo CreateAndSaveScene(string sceneName, string path = null)
{
SceneInfo sceneInfo = default(SceneInfo);
if (!EditorSceneManager.EnsureUntitledSceneHasBeenSaved("Save untitled scene before proceeding?"))
{
return sceneInfo;
}
Scene newScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
if (string.IsNullOrEmpty(path))
{
path = "Assets/" + sceneName + ".unity";
}
if (!EditorSceneManager.SaveScene(newScene, path))
{
Debug.LogError("Couldn't create and save scene " + sceneName + " at path " + path);
return sceneInfo;
}
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
sceneInfo.Asset = sceneAsset;
sceneInfo.Name = sceneAsset.name;
sceneInfo.Path = path;
return sceneInfo;
}
/// <summary>
/// Adds scene to build settings.
/// </summary>
/// <param name="sceneObject">Scene object reference.</param>
/// <param name="setAsFirst">Sets as first scene to be loaded.</param>
public static bool AddSceneToBuildSettings(
SceneInfo scene,
EditorBuildSettingsScene[] scenes,
BuildIndexTarget buildIndexTarget = BuildIndexTarget.None)
{
if (scene.IsEmpty)
{ // Can't add a null scene to build settings
return false;
}
long localID;
string managerGuidString;
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(scene.Asset, out managerGuidString, out localID);
GUID sceneGuid = new GUID(managerGuidString);
List<EditorBuildSettingsScene> newScenes = new List<EditorBuildSettingsScene>(scenes);
// See if / where the scene exists in build settings
int buildIndex = EditorSceneUtils.GetSceneBuildIndex(sceneGuid, newScenes);
if (buildIndex < 0)
{
// It doesn't exist in the build settings, add it now
switch (buildIndexTarget)
{
case BuildIndexTarget.First:
// Add it to index 0
newScenes.Insert(0, new EditorBuildSettingsScene(sceneGuid, true));
break;
case BuildIndexTarget.None:
default:
// Just add it to the end
newScenes.Add(new EditorBuildSettingsScene(sceneGuid, true));
break;
}
EditorBuildSettings.scenes = newScenes.ToArray();
return true;
}
else
{
switch (buildIndexTarget)
{
// If it does exist, but isn't in the right spot, move it now
case BuildIndexTarget.First:
if (buildIndex != 0)
{
Debug.LogWarning("Scene '" + scene.Name + "' was not first in build order. Changing build settings now.");
newScenes.RemoveAt(buildIndex);
newScenes.Insert(0, new EditorBuildSettingsScene(sceneGuid, true));
EditorBuildSettings.scenes = newScenes.ToArray();
}
return true;
case BuildIndexTarget.Last:
if (buildIndex != EditorSceneManager.sceneCountInBuildSettings - 1)
{
newScenes.RemoveAt(buildIndex);
newScenes.Insert(newScenes.Count - 1, new EditorBuildSettingsScene(sceneGuid, true));
EditorBuildSettings.scenes = newScenes.ToArray();
}
return true;
case BuildIndexTarget.None:
default:
// Do nothing
return false;
}
}
}
/// <summary>
/// Gets the build index for a scene GUID.
/// There are many ways to do this in Unity but this is the only 100% reliable method I know of.
/// </summary>
public static int GetSceneBuildIndex(GUID sceneGUID, List<EditorBuildSettingsScene> scenes)
{
int buildIndex = -1;
int sceneCount = 0;
for (int i = 0; i < scenes.Count; i++)
{
if (scenes[i].guid == sceneGUID)
{
buildIndex = sceneCount;
break;
}
if (scenes[i].enabled)
{
sceneCount++;
}
}
return buildIndex;
}
/// <summary>
/// Attempts to load scene in editor using a scene object reference.
/// </summary>
/// <param name="sceneObject">Scene object reference.</param>
/// <param name="setAsFirst">Whether to set first in the hierarchy window.</param>
/// <param name="editorScene">The loaded scene.</param>
/// <returns>True if successful.</returns>
public static bool LoadScene(SceneInfo sceneInfo, bool setAsFirst, out Scene editorScene)
{
editorScene = default(Scene);
try
{
editorScene = SceneManager.GetSceneByName(sceneInfo.Name);
if (editorScene.isLoaded)
{ // Already open - no need to do anything!
return true;
}
string scenePath = AssetDatabase.GetAssetOrScenePath(sceneInfo.Asset);
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
if (setAsFirst
#if UNITY_2022_2_OR_NEWER
&& SceneManager.loadedSceneCount >= 1)
#else
&& EditorSceneManager.loadedSceneCount >= 1)
#endif
{ // Move the scene to first in order in the hierarchy
Scene nextScene = SceneManager.GetSceneAt(0);
EditorSceneManager.MoveSceneBefore(editorScene, nextScene);
}
}
catch (InvalidOperationException)
{
// This can happen if we're trying to load immediately upon recompilation.
return false;
}
catch (ArgumentException)
{
// This can happen if the scene is an invalid scene and we try to SetActive.
return false;
}
catch (NullReferenceException)
{
// This can happen if the scene object is null.
return false;
}
catch (MissingReferenceException)
{
// This can happen if the scene object is null.
return false;
}
return true;
}
/// <summary>
/// Finds the scene if loaded.
/// </summary>
/// <returns>True if scene is loaded</returns>
public static bool GetSceneIfLoaded(SceneInfo sceneInfo, out Scene editorScene)
{
editorScene = default(Scene);
editorScene = EditorSceneManager.GetSceneByName(sceneInfo.Name);
return editorScene.IsValid() && editorScene.isLoaded;
}
/// <summary>
/// Returns all root GameObjects in all open scenes.
/// </summary>
public static IEnumerable<GameObject> GetRootGameObjectsInLoadedScenes()
{
for (int i = 0; i < EditorSceneManager.sceneCount; i++)
{
Scene openScene = EditorSceneManager.GetSceneAt(i);
if (!openScene.isLoaded)
{ // Oh, Unity.
continue;
}
foreach (GameObject rootGameObject in openScene.GetRootGameObjects())
yield return rootGameObject;
}
yield break;
}
/// <summary>
/// Returns true if user is currently editing a prefab.
/// </summary>
public static bool IsEditingPrefab()
{
#if UNITY_2021_2_OR_NEWER
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#else
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#endif
return prefabStage != null;
}
/// <summary>
/// Unloads a scene in the editor and catches any errors that can happen along the way.
/// </summary>
public static bool UnloadScene(SceneInfo sceneInfo, bool removeFromHeirarchy)
{
Scene editorScene = default(Scene);
try
{
editorScene = EditorSceneManager.GetSceneByName(sceneInfo.Name);
if (editorScene.isLoaded)
{
EditorSceneManager.CloseScene(editorScene, removeFromHeirarchy);
}
}
catch (InvalidOperationException)
{
// This can happen if we're trying to load immediately upon recompilation.
return false;
}
catch (ArgumentException)
{
// This can happen if the scene is an invalid scene and we try to SetActive.
return false;
}
catch (NullReferenceException)
{
// This can happen if the scene object is null.
return false;
}
catch (MissingReferenceException)
{
// This can happen if the scene object is null.
return false;
}
return true;
}
/// <summary>
/// Attempts to set the active scene and catches all the various ways it can go wrong.
/// Returns true if successful.
/// </summary>
public static bool SetActiveScene(Scene scene)
{
try
{
EditorSceneManager.SetActiveScene(scene);
}
catch (InvalidOperationException)
{
// This can happen if we're trying to load immediately upon recompilation.
return false;
}
catch (ArgumentException)
{
// This can happen if the scene is an invalid scene and we try to SetActive.
return false;
}
catch (NullReferenceException)
{
// This can happen if the scene object is null.
return false;
}
catch (MissingReferenceException)
{
// This can happen if the scene object is null.
return false;
}
return true;
}
/// <summary>
/// Copies the lighting settings from the lighting scene to the active scene
/// </summary>
public static void CopyLightingSettingsToActiveScene(Scene lightingScene)
{
// Store the active scene on entry
Scene activeSceneOnEnter = EditorSceneManager.GetActiveScene();
// No need to do anything
if (activeSceneOnEnter == lightingScene)
return;
SerializedObject sourceLightmapSettings;
SerializedObject sourceRenderSettings;
// Set the active scene to the lighting scene
SetActiveScene(lightingScene);
// If we can't get the source settings for some reason, abort
if (!GetLightingAndRenderSettings(out sourceLightmapSettings, out sourceRenderSettings))
{
return;
}
bool madeChanges = false;
// Set active scene back to the active scene on enter
if (SetActiveScene(activeSceneOnEnter))
{
SerializedObject targetLightmapSettings;
SerializedObject targetRenderSettings;
if (GetLightingAndRenderSettings(out targetLightmapSettings, out targetRenderSettings))
{
string[] propsToIgnore = new string[] { "m_IndirectSpecularColor" };
madeChanges |= SerializedObjectUtils.CopySerializedObject(sourceLightmapSettings, targetLightmapSettings, propsToIgnore);
madeChanges |= SerializedObjectUtils.CopySerializedObject(sourceRenderSettings, targetRenderSettings, propsToIgnore);
}
}
if (madeChanges)
{
Debug.LogWarning("Changed lighting settings in scene " + activeSceneOnEnter.name + " to match lighting scene " + lightingScene.name);
EditorSceneManager.MarkSceneDirty(activeSceneOnEnter);
}
}
/// <summary>
/// Goes through a scene's objects and checks for components that aren't found in permittedComponentTypes
/// If any are found, they're added to the violations list.
/// </summary>
public static bool EnforceSceneComponents(Scene scene, IEnumerable<Type> permittedComponentTypes, List<Component> violations)
{
if (!scene.IsValid() || !scene.isLoaded)
{
return false;
}
int typesEvaluated = 0;
bool foundAtLeastOneViolation = false;
try
{
foreach (GameObject rootGameObject in scene.GetRootGameObjects())
{
foreach (Component component in rootGameObject.GetComponentsInChildren<Component>())
{
bool componentIsPermitted = false;
foreach (Type type in permittedComponentTypes)
{
if (type.IsAssignableFrom(component.GetType()))
{
componentIsPermitted = true;
break;
}
typesEvaluated++;
}
if (!componentIsPermitted)
{
foundAtLeastOneViolation = true;
violations.Add(component);
}
}
}
}
catch (Exception)
{
// This can go wrong if GetRootSceneObjects fails.
}
if (typesEvaluated == 0)
{
Debug.LogError("Permitted component types must include at least one type.");
}
return foundAtLeastOneViolation;
}
/// <summary>
/// Gets serialized objects for lightmap and render settings from active scene.
/// </summary>
public static bool GetLightingAndRenderSettings(out SerializedObject lightmapSettings, out SerializedObject renderSettings)
{
lightmapSettings = null;
renderSettings = null;
UnityEngine.Object lightmapSettingsObject = null;
UnityEngine.Object renderSettingsObject = null;
try
{
// Use reflection to get the serialized objects of lightmap settings and render settings
Type lightmapSettingsType = typeof(LightmapEditorSettings);
Type renderSettingsType = typeof(RenderSettings);
lightmapSettingsObject = (UnityEngine.Object)lightmapSettingsType.GetMethod("GetLightmapSettings", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
renderSettingsObject = (UnityEngine.Object)renderSettingsType.GetMethod("GetRenderSettings", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
}
catch (Exception)
{
Debug.LogWarning("Couldn't get lightmap or render settings. This version of Unity may not support this operation.");
return false;
}
if (lightmapSettingsObject == null)
{
Debug.LogWarning("Couldn't get lightmap settings object");
return false;
}
if (renderSettingsObject == null)
{
Debug.LogWarning("Couldn't get render settings object");
return false;
}
// Store the settings in serialized objects
lightmapSettings = new SerializedObject(lightmapSettingsObject);
renderSettings = new SerializedObject(renderSettingsObject);
return true;
}
/// <summary>
/// Checks build settings for possible errors and displays warnings.
/// </summary>
public static bool CheckBuildSettingsForDuplicates(List<SceneInfo> allScenes, Dictionary<string, List<int>> duplicates)
{
duplicates.Clear();
List<int> indexes = null;
bool foundDuplicate = false;
foreach (SceneInfo sceneInfo in allScenes)
{
if (duplicates.TryGetValue(sceneInfo.Name, out indexes))
{
indexes.Add(sceneInfo.BuildIndex);
foundDuplicate = true;
}
else
{
duplicates.Add(sceneInfo.Name, new List<int> { sceneInfo.BuildIndex });
}
}
return foundDuplicate;
}
}
}
#endif