238 lines
7.8 KiB
C#
238 lines
7.8 KiB
C#
|
// Author: JohannesMP (2018-08-12)
|
||
|
//
|
||
|
// A wrapper that provides the means to safely serialize Scene Asset References.
|
||
|
//
|
||
|
// Internally we serialize an Object to the SceneAsset which only exists at editor time.
|
||
|
// Any time the object is serialized, we store the path provided by this Asset (assuming it was valid).
|
||
|
//
|
||
|
// This means that, come build time, the string path of the scene asset is always already stored, which if
|
||
|
// the scene was added to the build settings means it can be loaded.
|
||
|
//
|
||
|
// It is up to the user to ensure the scene exists in the build settings so it is loadable at runtime.
|
||
|
// To help with this, a custom PropertyDrawer displays the scene build settings state.
|
||
|
//
|
||
|
// Known issues:
|
||
|
// - When reverting back to a prefab which has the asset stored as null, Unity will show the property
|
||
|
// as modified despite having just reverted. This only happens the fist time, and reverting again
|
||
|
//
|
||
|
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using System.Linq;
|
||
|
using System.IO;
|
||
|
#if UNITY_EDITOR
|
||
|
using UnityEditor;
|
||
|
#endif
|
||
|
|
||
|
[System.Serializable]
|
||
|
public class SceneReferenceLite : ISerializationCallbackReceiver
|
||
|
{
|
||
|
#if UNITY_EDITOR
|
||
|
// What we use in editor to select the scene
|
||
|
[SerializeField] private Object sceneAsset = null;
|
||
|
bool IsValidSceneAsset
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (sceneAsset == null)
|
||
|
return false;
|
||
|
return sceneAsset.GetType().Equals(typeof(SceneAsset));
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// This should only ever be set during serialization/deserialization!
|
||
|
[SerializeField]
|
||
|
private string scenePath = string.Empty;
|
||
|
|
||
|
// Use this when you want to actually have the scene path
|
||
|
public string ScenePath
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#if UNITY_EDITOR
|
||
|
// In editor we always use the asset's path
|
||
|
return GetScenePathFromAsset();
|
||
|
#else
|
||
|
// At runtime we rely on the stored path value which we assume was serialized correctly at build time.
|
||
|
// See OnBeforeSerialize and OnAfterDeserialize
|
||
|
return scenePath;
|
||
|
#endif
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
scenePath = value;
|
||
|
#if UNITY_EDITOR
|
||
|
sceneAsset = GetSceneAssetFromPath();
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Use this when you want to have the scene name
|
||
|
public string SceneName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return Path.GetFileNameWithoutExtension(ScenePath);
|
||
|
// At runtime we rely on the stored path value which we assume was serialized correctly at build time.
|
||
|
// See OnBeforeSerialize and OnAfterDeserialize
|
||
|
//return scenePath;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static implicit operator string(SceneReferenceLite sceneReference)
|
||
|
{
|
||
|
return sceneReference.ScenePath;
|
||
|
}
|
||
|
|
||
|
// Called to prepare this data for serialization. Stubbed out when not in editor.
|
||
|
public void OnBeforeSerialize()
|
||
|
{
|
||
|
#if UNITY_EDITOR
|
||
|
HandleBeforeSerialize();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Called to set up data for deserialization. Stubbed out when not in editor.
|
||
|
public void OnAfterDeserialize()
|
||
|
{
|
||
|
#if UNITY_EDITOR
|
||
|
// We sadly cannot touch assetdatabase during serialization, so defer by a bit.
|
||
|
EditorApplication.update += HandleAfterDeserialize;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
private SceneAsset GetSceneAssetFromPath()
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(scenePath))
|
||
|
return null;
|
||
|
return AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
|
||
|
}
|
||
|
|
||
|
private string GetScenePathFromAsset()
|
||
|
{
|
||
|
if (sceneAsset == null)
|
||
|
return string.Empty;
|
||
|
return AssetDatabase.GetAssetPath(sceneAsset);
|
||
|
}
|
||
|
|
||
|
private void HandleBeforeSerialize()
|
||
|
{
|
||
|
// Asset is invalid but have Path to try and recover from
|
||
|
if (IsValidSceneAsset == false && string.IsNullOrEmpty(scenePath) == false)
|
||
|
{
|
||
|
sceneAsset = GetSceneAssetFromPath();
|
||
|
if (sceneAsset == null)
|
||
|
scenePath = string.Empty;
|
||
|
|
||
|
UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
|
||
|
}
|
||
|
// Asset takes precendence and overwrites Path
|
||
|
else
|
||
|
{
|
||
|
scenePath = GetScenePathFromAsset();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void HandleAfterDeserialize()
|
||
|
{
|
||
|
EditorApplication.update -= HandleAfterDeserialize;
|
||
|
// Asset is valid, don't do anything - Path will always be set based on it when it matters
|
||
|
if (IsValidSceneAsset)
|
||
|
return;
|
||
|
|
||
|
// Asset is invalid but have path to try and recover from
|
||
|
if (string.IsNullOrEmpty(scenePath) == false)
|
||
|
{
|
||
|
sceneAsset = GetSceneAssetFromPath();
|
||
|
// No asset found, path was invalid. Make sure we don't carry over the old invalid path
|
||
|
if (sceneAsset == null)
|
||
|
scenePath = string.Empty;
|
||
|
|
||
|
if (Application.isPlaying == false)
|
||
|
UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
/// <summary>
|
||
|
/// Display a Scene Reference object in the editor.
|
||
|
/// If scene is valid, provides basic buttons to interact with the scene's role in Build Settings.
|
||
|
/// </summary>
|
||
|
[CustomPropertyDrawer(typeof(SceneReferenceLite))]
|
||
|
public class SceneReferenceLitePropertyDrawer : PropertyDrawer
|
||
|
{
|
||
|
// The exact name of the asset Object variable in the SceneReference object
|
||
|
const string sceneAssetPropertyString = "sceneAsset";
|
||
|
// The exact name of the scene Path variable in the SceneReference object
|
||
|
const string scenePathPropertyString = "scenePath";
|
||
|
|
||
|
static readonly RectOffset boxPadding = EditorStyles.helpBox.padding;
|
||
|
static readonly float padSize = 2f;
|
||
|
static readonly float lineHeight = EditorGUIUtility.singleLineHeight;
|
||
|
static readonly float paddedLine = lineHeight + padSize;
|
||
|
static readonly float footerHeight = 10f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Drawing the 'SceneReference' property
|
||
|
/// </summary>
|
||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||
|
{
|
||
|
var sceneAssetProperty = GetSceneAssetProperty(property);
|
||
|
|
||
|
// Draw the Box Background
|
||
|
position.height -= footerHeight;
|
||
|
//GUI.Box(EditorGUI.IndentedRect(position), GUIContent.none, EditorStyles.helpBox);
|
||
|
position = boxPadding.Remove(position);
|
||
|
position.height = lineHeight;
|
||
|
|
||
|
// Draw the main Object field
|
||
|
//label.tooltip = "The actual Scene Asset reference.\nOn serialize this is also stored as the asset's path.";
|
||
|
|
||
|
EditorGUI.BeginProperty(position, GUIContent.none, property);
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
int sceneControlID = GUIUtility.GetControlID(FocusType.Passive);
|
||
|
var selectedObject = EditorGUI.ObjectField(position, label, sceneAssetProperty.objectReferenceValue, typeof(SceneAsset), false);
|
||
|
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
sceneAssetProperty.objectReferenceValue = selectedObject;
|
||
|
}
|
||
|
position.y += paddedLine;
|
||
|
|
||
|
EditorGUI.EndProperty();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Ensure that what we draw in OnGUI always has the room it needs
|
||
|
/// </summary>
|
||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||
|
{
|
||
|
int lines = 2;
|
||
|
SerializedProperty sceneAssetProperty = GetSceneAssetProperty(property);
|
||
|
if (sceneAssetProperty.objectReferenceValue == null)
|
||
|
lines = 1;
|
||
|
|
||
|
return boxPadding.vertical + lineHeight * lines + padSize * (lines - 1) + footerHeight;
|
||
|
}
|
||
|
|
||
|
|
||
|
static SerializedProperty GetSceneAssetProperty(SerializedProperty property)
|
||
|
{
|
||
|
return property.FindPropertyRelative(sceneAssetPropertyString);
|
||
|
}
|
||
|
|
||
|
static SerializedProperty GetScenePathProperty(SerializedProperty property)
|
||
|
{
|
||
|
return property.FindPropertyRelative(scenePathPropertyString);
|
||
|
}
|
||
|
}
|
||
|
#endif
|