Firstborn/Assets/RPG Creation Kit/Scripts/Cells System/Additional/SceneDrawer/SceneReferenceLite.cs

238 lines
7.8 KiB
C#
Raw Normal View History

// 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