617 lines
24 KiB
C#
617 lines
24 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Reflection;
|
||
|
using UnityEditor;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Assertions;
|
||
|
|
||
|
namespace SaveDuringPlay
|
||
|
{
|
||
|
/// <summary>A collection of tools for finding objects</summary>
|
||
|
static class ObjectTreeUtil
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Get the full name of an object, travelling up the transform parents to the root.
|
||
|
/// </summary>
|
||
|
public static string GetFullName(GameObject current)
|
||
|
{
|
||
|
if (current == null)
|
||
|
return "";
|
||
|
if (current.transform.parent == null)
|
||
|
return "/" + current.name;
|
||
|
return GetFullName(current.transform.parent.gameObject) + "/" + current.name;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Will find the named object, active or inactive, from the full path.
|
||
|
/// </summary>
|
||
|
public static GameObject FindObjectFromFullName(string fullName, GameObject[] roots)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(fullName) || roots == null)
|
||
|
return null;
|
||
|
|
||
|
string[] path = fullName.Split('/');
|
||
|
if (path.Length < 2) // skip leading '/'
|
||
|
return null;
|
||
|
|
||
|
Transform root = null;
|
||
|
for (int i = 0; root == null && i < roots.Length; ++i)
|
||
|
if (roots[i].name == path[1])
|
||
|
root = roots[i].transform;
|
||
|
|
||
|
if (root == null)
|
||
|
return null;
|
||
|
|
||
|
for (int i = 2; i < path.Length; ++i) // skip root
|
||
|
{
|
||
|
bool found = false;
|
||
|
for (int c = 0; c < root.childCount; ++c)
|
||
|
{
|
||
|
Transform child = root.GetChild(c);
|
||
|
if (child.name == path[i])
|
||
|
{
|
||
|
found = true;
|
||
|
root = child;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!found)
|
||
|
return null;
|
||
|
}
|
||
|
return root.gameObject;
|
||
|
}
|
||
|
|
||
|
/// <summary>Finds all the root objects in a scene, active or not</summary>
|
||
|
public static GameObject[] FindAllRootObjectsInScene()
|
||
|
{
|
||
|
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// This finds all the behaviours in scene, active or inactive, excluding prefabs
|
||
|
/// </summary>
|
||
|
public static T[] FindAllBehavioursInScene<T>() where T : MonoBehaviour
|
||
|
{
|
||
|
List<T> objectsInScene = new List<T>();
|
||
|
foreach (T b in Resources.FindObjectsOfTypeAll<T>())
|
||
|
{
|
||
|
if (b == null)
|
||
|
continue; // object was deleted
|
||
|
GameObject go = b.gameObject;
|
||
|
if (go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave)
|
||
|
continue;
|
||
|
if (EditorUtility.IsPersistent(go.transform.root.gameObject))
|
||
|
continue;
|
||
|
objectsInScene.Add(b);
|
||
|
}
|
||
|
return objectsInScene.ToArray();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class GameObjectFieldScanner
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Called for each leaf field. Return value should be true if action was taken.
|
||
|
/// It will be propagated back to the caller.
|
||
|
/// </summary>
|
||
|
public OnLeafFieldDelegate OnLeafField;
|
||
|
public delegate bool OnLeafFieldDelegate(string fullName, Type type, ref object value);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called for each field node, if and only if OnLeafField() for it or one
|
||
|
/// of its leaves returned true.
|
||
|
/// </summary>
|
||
|
public OnFieldValueChangedDelegate OnFieldValueChanged;
|
||
|
public delegate bool OnFieldValueChangedDelegate(
|
||
|
string fullName, FieldInfo fieldInfo, object fieldOwner, object value);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called for each field, to test whether to proceed with scanning it. Return true to scan.
|
||
|
/// </summary>
|
||
|
public FilterFieldDelegate FilterField;
|
||
|
public delegate bool FilterFieldDelegate(string fullName, FieldInfo fieldInfo);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called for each behaviour, to test whether to proceed with scanning it. Return true to scan.
|
||
|
/// </summary>
|
||
|
public FilterComponentDelegate FilterComponent;
|
||
|
public delegate bool FilterComponentDelegate(MonoBehaviour b);
|
||
|
|
||
|
/// <summary>
|
||
|
/// The leafmost UnityEngine.Object
|
||
|
/// </summary>
|
||
|
public UnityEngine.Object LeafObject { get; private set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Which fields will be scanned
|
||
|
/// </summary>
|
||
|
const BindingFlags kBindingFlags = BindingFlags.Public | BindingFlags.Instance;
|
||
|
|
||
|
bool ScanFields(string fullName, Type type, ref object obj)
|
||
|
{
|
||
|
bool doneSomething = false;
|
||
|
|
||
|
// Check if it's a complex type
|
||
|
bool isLeaf = true;
|
||
|
if (obj != null
|
||
|
&& !typeof(Component).IsAssignableFrom(type)
|
||
|
&& !typeof(ScriptableObject).IsAssignableFrom(type)
|
||
|
&& !typeof(GameObject).IsAssignableFrom(type))
|
||
|
{
|
||
|
if (type.IsArray)
|
||
|
{
|
||
|
isLeaf = false;
|
||
|
var array = obj as Array;
|
||
|
object arrayLength = array.Length;
|
||
|
if (OnLeafField != null && OnLeafField(
|
||
|
fullName + ".Length", arrayLength.GetType(), ref arrayLength))
|
||
|
{
|
||
|
Array newArray = Array.CreateInstance(
|
||
|
array.GetType().GetElementType(), Convert.ToInt32(arrayLength));
|
||
|
Array.Copy(array, 0, newArray, 0, Math.Min(array.Length, newArray.Length));
|
||
|
array = newArray;
|
||
|
doneSomething = true;
|
||
|
}
|
||
|
for (int i = 0; i < array.Length; ++i)
|
||
|
{
|
||
|
object element = array.GetValue(i);
|
||
|
if (ScanFields(fullName + "[" + i + "]", array.GetType().GetElementType(), ref element))
|
||
|
{
|
||
|
array.SetValue(element, i);
|
||
|
doneSomething = true;
|
||
|
}
|
||
|
}
|
||
|
if (doneSomething)
|
||
|
obj = array;
|
||
|
}
|
||
|
else if (typeof(IList).IsAssignableFrom(type))
|
||
|
{
|
||
|
isLeaf = false;
|
||
|
var list = obj as IList;
|
||
|
object length = list.Count;
|
||
|
|
||
|
// restore list size
|
||
|
if (OnLeafField != null && OnLeafField(
|
||
|
fullName + ".Length", length.GetType(), ref length))
|
||
|
{
|
||
|
var newLength = (int)length;
|
||
|
var currentLength = list.Count;
|
||
|
for (int i = 0; i < currentLength - newLength; ++i)
|
||
|
{
|
||
|
list.RemoveAt(currentLength - i - 1); // make list shorter if needed
|
||
|
}
|
||
|
for (int i = 0; i < newLength - currentLength; ++i)
|
||
|
{
|
||
|
list.Add(GetValue(type.GetGenericArguments()[0])); // make list longer if needed
|
||
|
}
|
||
|
doneSomething = true;
|
||
|
}
|
||
|
|
||
|
// restore values
|
||
|
for (int i = 0; i < list.Count; ++i)
|
||
|
{
|
||
|
var c = list[i];
|
||
|
if (ScanFields(fullName + "[" + i + "]", c.GetType(), ref c))
|
||
|
{
|
||
|
list[i] = c;
|
||
|
doneSomething = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (doneSomething)
|
||
|
obj = list;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Check if it's a complex type
|
||
|
FieldInfo[] fields = obj.GetType().GetFields(kBindingFlags);
|
||
|
if (fields.Length > 0)
|
||
|
{
|
||
|
isLeaf = false;
|
||
|
for (int i = 0; i < fields.Length; ++i)
|
||
|
{
|
||
|
string name = fullName + "." + fields[i].Name;
|
||
|
if (FilterField == null || FilterField(name, fields[i]))
|
||
|
{
|
||
|
object fieldValue = fields[i].GetValue(obj);
|
||
|
if (ScanFields(name, fields[i].FieldType, ref fieldValue))
|
||
|
{
|
||
|
doneSomething = true;
|
||
|
if (OnFieldValueChanged != null)
|
||
|
OnFieldValueChanged(name, fields[i], obj, fieldValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// If it's a leaf field then call the leaf handler
|
||
|
if (isLeaf && OnLeafField != null)
|
||
|
if (OnLeafField(fullName, type, ref obj))
|
||
|
doneSomething = true;
|
||
|
|
||
|
return doneSomething;
|
||
|
}
|
||
|
|
||
|
static object GetValue(Type type)
|
||
|
{
|
||
|
Assert.IsNotNull(type);
|
||
|
return Activator.CreateInstance(type);
|
||
|
}
|
||
|
|
||
|
bool ScanFields(string fullName, MonoBehaviour b)
|
||
|
{
|
||
|
bool doneSomething = false;
|
||
|
LeafObject = b;
|
||
|
|
||
|
FieldInfo[] fields = b.GetType().GetFields(kBindingFlags);
|
||
|
if (fields.Length > 0)
|
||
|
{
|
||
|
for (int i = 0; i < fields.Length; ++i)
|
||
|
{
|
||
|
string name = fullName + "." + fields[i].Name;
|
||
|
if (FilterField == null || FilterField(name, fields[i]))
|
||
|
{
|
||
|
object fieldValue = fields[i].GetValue(b);
|
||
|
if (ScanFields(name, fields[i].FieldType, ref fieldValue))
|
||
|
doneSomething = true;
|
||
|
|
||
|
// If leaf action was taken, propagate it up to the parent node
|
||
|
if (doneSomething && OnFieldValueChanged != null)
|
||
|
OnFieldValueChanged(fullName, fields[i], b, fieldValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return doneSomething;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Recursively scan [SaveDuringPlay] MonoBehaviours of a GameObject and its children.
|
||
|
/// For each leaf field found, call the OnFieldValue delegate.
|
||
|
/// </summary>
|
||
|
public bool ScanFields(GameObject go, string prefix = null)
|
||
|
{
|
||
|
bool doneSomething = false;
|
||
|
if (prefix == null)
|
||
|
prefix = "";
|
||
|
else if (prefix.Length > 0)
|
||
|
prefix += ".";
|
||
|
|
||
|
MonoBehaviour[] components = go.GetComponents<MonoBehaviour>();
|
||
|
for (int i = 0; i < components.Length; ++i)
|
||
|
{
|
||
|
MonoBehaviour c = components[i];
|
||
|
if (c == null || (FilterComponent != null && !FilterComponent(c)))
|
||
|
continue;
|
||
|
if (ScanFields(prefix + c.GetType().FullName + i, c))
|
||
|
doneSomething = true;
|
||
|
}
|
||
|
return doneSomething;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// <summary>
|
||
|
/// Using reflection, this class scans a GameObject (and optionally its children)
|
||
|
/// and records all the field settings. This only works for "nice" field settings
|
||
|
/// within MonoBehaviours. Changes to the behaviour stack made between saving
|
||
|
/// and restoring will fool this class.
|
||
|
/// </summary>
|
||
|
class ObjectStateSaver
|
||
|
{
|
||
|
string mObjectFullPath;
|
||
|
|
||
|
Dictionary<string, string> mValues = new Dictionary<string, string>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Recursively collect all the field values in the MonoBehaviours
|
||
|
/// owned by this object and its descendants. The values are stored
|
||
|
/// in an internal dictionary.
|
||
|
/// </summary>
|
||
|
public void CollectFieldValues(GameObject go)
|
||
|
{
|
||
|
mObjectFullPath = ObjectTreeUtil.GetFullName(go);
|
||
|
GameObjectFieldScanner scanner = new GameObjectFieldScanner();
|
||
|
scanner.FilterField = FilterField;
|
||
|
scanner.FilterComponent = HasSaveDuringPlay;
|
||
|
scanner.OnLeafField = (string fullName, Type type, ref object value) =>
|
||
|
{
|
||
|
// Save the value in the dictionary
|
||
|
mValues[fullName] = StringFromLeafObject(value);
|
||
|
//Debug.Log(mObjectFullPath + "." + fullName + " = " + mValues[fullName]);
|
||
|
return false;
|
||
|
};
|
||
|
scanner.ScanFields(go);
|
||
|
}
|
||
|
|
||
|
public GameObject FindSavedGameObject(GameObject[] roots)
|
||
|
{
|
||
|
return ObjectTreeUtil.FindObjectFromFullName(mObjectFullPath, roots);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Recursively scan the MonoBehaviours of a GameObject and its children.
|
||
|
/// For each field found, look up its value in the internal dictionary.
|
||
|
/// If it's present and its value in the dictionary differs from the actual
|
||
|
/// value in the game object, Set the GameObject's value using the value
|
||
|
/// recorded in the dictionary.
|
||
|
/// </summary>
|
||
|
public bool PutFieldValues(GameObject go, GameObject[] roots)
|
||
|
{
|
||
|
GameObjectFieldScanner scanner = new GameObjectFieldScanner();
|
||
|
scanner.FilterField = FilterField;
|
||
|
scanner.FilterComponent = HasSaveDuringPlay;
|
||
|
scanner.OnLeafField = (string fullName, Type type, ref object value) =>
|
||
|
{
|
||
|
// Lookup the value in the dictionary
|
||
|
if (mValues.TryGetValue(fullName, out string savedValue)
|
||
|
&& StringFromLeafObject(value) != savedValue)
|
||
|
{
|
||
|
//Debug.Log("Put " + mObjectFullPath + "." + fullName + " = " + mValues[fullName]);
|
||
|
value = LeafObjectFromString(type, mValues[fullName].Trim(), roots);
|
||
|
return true; // changed
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
scanner.OnFieldValueChanged = (fullName, fieldInfo, fieldOwner, value) =>
|
||
|
{
|
||
|
fieldInfo.SetValue(fieldOwner, value);
|
||
|
if (PrefabUtility.GetPrefabInstanceStatus(go) != PrefabInstanceStatus.NotAPrefab)
|
||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(scanner.LeafObject);
|
||
|
return true;
|
||
|
};
|
||
|
return scanner.ScanFields(go);
|
||
|
}
|
||
|
|
||
|
/// Ignore fields marked with the [NoSaveDuringPlay] attribute
|
||
|
static bool FilterField(string fullName, FieldInfo fieldInfo)
|
||
|
{
|
||
|
var attrs = fieldInfo.GetCustomAttributes(false);
|
||
|
foreach (var attr in attrs)
|
||
|
if (attr.GetType().Name.Equals("NoSaveDuringPlayAttribute"))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// Only process components with the [SaveDuringPlay] attribute
|
||
|
public static bool HasSaveDuringPlay(MonoBehaviour b)
|
||
|
{
|
||
|
var attrs = b.GetType().GetCustomAttributes(true);
|
||
|
foreach (var attr in attrs)
|
||
|
if (attr.GetType().Name.Equals("SaveDuringPlayAttribute"))
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parse a string to generate an object.
|
||
|
/// Only very limited primitive object types are supported.
|
||
|
/// Enums, Vectors and most other structures are automatically supported,
|
||
|
/// because the reflection system breaks them down into their primitive components.
|
||
|
/// You can add more support here, as needed.
|
||
|
/// </summary>
|
||
|
static object LeafObjectFromString(Type type, string value, GameObject[] roots)
|
||
|
{
|
||
|
if (type == typeof(Single))
|
||
|
return float.Parse(value);
|
||
|
if (type == typeof(Double))
|
||
|
return double.Parse(value);
|
||
|
if (type == typeof(Boolean))
|
||
|
return Boolean.Parse(value);
|
||
|
if (type == typeof(string))
|
||
|
return value;
|
||
|
if (type == typeof(Int32))
|
||
|
return Int32.Parse(value);
|
||
|
if (type == typeof(UInt32))
|
||
|
return UInt32.Parse(value);
|
||
|
if (typeof(Component).IsAssignableFrom(type))
|
||
|
{
|
||
|
// Try to find the named game object
|
||
|
GameObject go = ObjectTreeUtil.FindObjectFromFullName(value, roots);
|
||
|
return (go != null) ? go.GetComponent(type) : null;
|
||
|
}
|
||
|
if (typeof(GameObject).IsAssignableFrom(type))
|
||
|
{
|
||
|
// Try to find the named game object
|
||
|
return GameObject.Find(value);
|
||
|
}
|
||
|
if (typeof(ScriptableObject).IsAssignableFrom(type))
|
||
|
{
|
||
|
return AssetDatabase.LoadAssetAtPath(value, type);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
static string StringFromLeafObject(object obj)
|
||
|
{
|
||
|
if (obj == null)
|
||
|
return string.Empty;
|
||
|
|
||
|
if (typeof(Component).IsAssignableFrom(obj.GetType()))
|
||
|
{
|
||
|
Component c = (Component)obj;
|
||
|
if (c == null) // Component overrides the == operator, so we have to check
|
||
|
return string.Empty;
|
||
|
return ObjectTreeUtil.GetFullName(c.gameObject);
|
||
|
}
|
||
|
if (typeof(GameObject).IsAssignableFrom(obj.GetType()))
|
||
|
{
|
||
|
GameObject go = (GameObject)obj;
|
||
|
if (go == null) // GameObject overrides the == operator, so we have to check
|
||
|
return string.Empty;
|
||
|
return ObjectTreeUtil.GetFullName(go);
|
||
|
}
|
||
|
if (typeof(ScriptableObject).IsAssignableFrom(obj.GetType()))
|
||
|
{
|
||
|
return AssetDatabase.GetAssetPath(obj as ScriptableObject);
|
||
|
}
|
||
|
return obj.ToString();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// For all registered object types, record their state when exiting Play Mode,
|
||
|
/// and restore that state to the objects in the scene. This is a very limited
|
||
|
/// implementation which has not been rigorously tested with many objects types.
|
||
|
/// It's quite possible that not everything will be saved.
|
||
|
///
|
||
|
/// This class is expected to become obsolete when Unity implements this functionality
|
||
|
/// in a more general way.
|
||
|
///
|
||
|
/// To use this class,
|
||
|
/// drop this script into your project, and add the [SaveDuringPlay] attribute to your class.
|
||
|
///
|
||
|
/// Note: if you want some specific field in your class NOT to be saved during play,
|
||
|
/// add a property attribute whose class name contains the string "NoSaveDuringPlay"
|
||
|
/// and the field will not be saved.
|
||
|
/// </summary>
|
||
|
[InitializeOnLoad]
|
||
|
public class SaveDuringPlay
|
||
|
{
|
||
|
/// <summary>Editor preferences key for SaveDuringPlay enabled</summary>
|
||
|
public static string kEnabledKey = "SaveDuringPlay_Enabled";
|
||
|
|
||
|
/// <summary>Enabled status for SaveDuringPlay.
|
||
|
/// This is a global setting, saved in Editor Prefs</summary>
|
||
|
public static bool Enabled
|
||
|
{
|
||
|
get => EditorPrefs.GetBool(kEnabledKey, false);
|
||
|
set
|
||
|
{
|
||
|
if (value != Enabled)
|
||
|
{
|
||
|
EditorPrefs.SetBool(kEnabledKey, value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static SaveDuringPlay()
|
||
|
{
|
||
|
// Install our callbacks
|
||
|
#if UNITY_2017_2_OR_NEWER
|
||
|
EditorApplication.playModeStateChanged += OnPlayStateChanged;
|
||
|
#else
|
||
|
EditorApplication.update += OnEditorUpdate;
|
||
|
EditorApplication.playmodeStateChanged += OnPlayStateChanged;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if UNITY_2017_2_OR_NEWER
|
||
|
static void OnPlayStateChanged(PlayModeStateChange pmsc)
|
||
|
{
|
||
|
if (Enabled)
|
||
|
{
|
||
|
switch (pmsc)
|
||
|
{
|
||
|
// If exiting playmode, collect the state of all interesting objects
|
||
|
case PlayModeStateChange.ExitingPlayMode:
|
||
|
SaveAllInterestingStates();
|
||
|
break;
|
||
|
case PlayModeStateChange.EnteredEditMode when sSavedStates != null:
|
||
|
RestoreAllInterestingStates();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
static void OnPlayStateChanged()
|
||
|
{
|
||
|
// If exiting playmode, collect the state of all interesting objects
|
||
|
if (Enabled)
|
||
|
{
|
||
|
if (!EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying)
|
||
|
SaveAllInterestingStates();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static float sWaitStartTime = 0;
|
||
|
static void OnEditorUpdate()
|
||
|
{
|
||
|
if (Enabled && sSavedStates != null && !Application.isPlaying)
|
||
|
{
|
||
|
// Wait a bit for things to settle before applying the saved state
|
||
|
const float WaitTime = 1f; // GML todo: is there a better way to do this?
|
||
|
float time = Time.realtimeSinceStartup;
|
||
|
if (sWaitStartTime == 0)
|
||
|
sWaitStartTime = time;
|
||
|
else if (time - sWaitStartTime > WaitTime)
|
||
|
{
|
||
|
RestoreAllInterestingStates();
|
||
|
sWaitStartTime = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// If you need to get notified before state is collected for hotsave, this is the place
|
||
|
/// </summary>
|
||
|
public static OnHotSaveDelegate OnHotSave;
|
||
|
|
||
|
/// <summary>Delegate for HotSave notification</summary>
|
||
|
public delegate void OnHotSaveDelegate();
|
||
|
|
||
|
/// Collect all relevant objects, active or not
|
||
|
static HashSet<GameObject> FindInterestingObjects()
|
||
|
{
|
||
|
var objects = new HashSet<GameObject>();
|
||
|
MonoBehaviour[] everything = ObjectTreeUtil.FindAllBehavioursInScene<MonoBehaviour>();
|
||
|
foreach (var b in everything)
|
||
|
{
|
||
|
if (!objects.Contains(b.gameObject) && ObjectStateSaver.HasSaveDuringPlay(b))
|
||
|
{
|
||
|
//Debug.Log("Found " + ObjectTreeUtil.GetFullName(b.gameObject) + " for hot-save");
|
||
|
objects.Add(b.gameObject);
|
||
|
}
|
||
|
}
|
||
|
return objects;
|
||
|
}
|
||
|
|
||
|
static List<ObjectStateSaver> sSavedStates = null;
|
||
|
|
||
|
static void SaveAllInterestingStates()
|
||
|
{
|
||
|
//Debug.Log("Exiting play mode: Saving state for all interesting objects");
|
||
|
if (OnHotSave != null)
|
||
|
OnHotSave();
|
||
|
|
||
|
sSavedStates = new List<ObjectStateSaver>();
|
||
|
var objects = FindInterestingObjects();
|
||
|
foreach (var obj in objects)
|
||
|
{
|
||
|
var saver = new ObjectStateSaver();
|
||
|
saver.CollectFieldValues(obj);
|
||
|
sSavedStates.Add(saver);
|
||
|
}
|
||
|
if (sSavedStates.Count == 0)
|
||
|
sSavedStates = null;
|
||
|
}
|
||
|
|
||
|
static void RestoreAllInterestingStates()
|
||
|
{
|
||
|
//Debug.Log("Updating state for all interesting objects");
|
||
|
bool dirty = false;
|
||
|
GameObject[] roots = ObjectTreeUtil.FindAllRootObjectsInScene();
|
||
|
foreach (ObjectStateSaver saver in sSavedStates)
|
||
|
{
|
||
|
GameObject go = saver.FindSavedGameObject(roots);
|
||
|
if (go != null)
|
||
|
{
|
||
|
Undo.RegisterFullObjectHierarchyUndo(go, "SaveDuringPlay");
|
||
|
if (saver.PutFieldValues(go, roots))
|
||
|
{
|
||
|
//Debug.Log("SaveDuringPlay: updated settings of " + saver.ObjetFullPath);
|
||
|
EditorUtility.SetDirty(go);
|
||
|
dirty = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (dirty)
|
||
|
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||
|
sSavedStates = null;
|
||
|
}
|
||
|
}
|
||
|
}
|