#if UNITY_EDITOR using UnityEditor; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using System.Reflection; using System.Collections.Generic; using System; using Object = UnityEngine.Object; namespace Framework { namespace Utils { namespace Editor { [InitializeOnLoad] public static class UnityPlayModeSaver { #region Constants private const string kSaveMenuString = "\U0001F5AB Save Play Mode Changes"; private const string kSaveSnapshotMenuString = "\U0001F5AB Save Play Mode Snapshot"; private const string kRevertMenuString = "\U0001F5D9 Forget Play Mode Changes"; private const int kSaveComponentMenuPriority = 12; private const string kSaveComponentMenuString = "CONTEXT/Component/" + kSaveMenuString; private const string kSaveComponentSnapshotMenuString = "CONTEXT/Component/" + kSaveSnapshotMenuString; private const string kRevertComponentMenuString = "CONTEXT/Component/" + kRevertMenuString; private const int kSaveGameObjectMenuPriority = -100; private const string kSaveGameObjectMenuString = "GameObject/" + kSaveMenuString; private const string kSaveGameObjectSnapshotMenuString = "GameObject/" + kSaveSnapshotMenuString; private const string kRevertGameObjectMenuString = "GameObject/" + kRevertMenuString; private const string kWindowMenuString = "Window/Play Mode Saver"; private const string kUndoText = "Play Mode Changes"; private const string kEditorPrefsKey = "UnityPlayModeSaver."; private const string kEditorPrefsObjectCountKey = kEditorPrefsKey + "SavedObjects"; private const string kEditorPrefsObjectDeleted = ".Deleted"; private const string kEditorPrefsObjectScene = ".Scene"; private const string kEditorPrefsObjectSceneId = ".SceneId"; private const string kEditorPrefsSceneObjectChildren = ".Children"; private const string kEditorPrefsScenePrefabInstanceId = ".PrefabInstId"; private const string kEditorPrefsScenePrefabInstanceChildPath = ".PrefabInstPath"; private const string kEditorPrefsRuntimeObjectId = ".RuntimeId"; private const string kEditorPrefsRuntimeObjectType = ".RuntimeType"; private const string kEditorPrefsRuntimeObjectParentId = ".RuntimeParentId"; private const string kEditorPrefsRuntimeObjectSceneRootIndex = ".RuntimeSceneRootIndex"; private const string kEditorPrefsRuntimeObjectPrefab = ".Prefab"; private const string kEditorPrefsRuntimeObjectPrefabObjIndex = ".PrefabObjIndex"; private const string kEditorPrefsObjectJson = ".Json"; private const string kEditorPrefsObjectRefs = ".ObjRefs"; private const string kEditorPrefsObjectMaterialRefs = ".Materials"; private const string kEditorPrefsRuntimeObjectRefs = ".RuntimeRefs"; private const char kItemSplitChar = '?'; private const char kObjectPathSplitChar = ':'; private const string kIdentifierProperty = "m_LocalIdentfierInFile"; //note the misspelling! private const string kInspectorProperty = "inspectorMode"; private const string kPrefabUnpackWarningTitle = "Play Mode Saver"; private const string kPrefabUnpackWarningMsg = "You removed a Component or child GameObject from a prefab during Play Mode which can't be applied without unpacking the prefab.\nDo you want to do this or ignore this change?"; private const string kPrefabUnpackWarningUnpack = "Unpack"; private const string kPrefabUnpackWarningIgnore = "Ignore"; #endregion #region Interanl Properties (For Editor Window) internal static PlayModeStateChange CurrentPlayModeState { get { return _currentPlayModeState; } } internal static List SavedObjects { get { return _savedObjects; } } #endregion #region Helper Structs internal struct SavedObject { public Object _object; public int _sceneIdentifier; public string _scenePath; public string _name; public Type _type; public string _path; public bool _hasSnapshot; public string _snapshotEditorPrefKey; } private struct RestoredObjectData { public Object _object; public Type _createdObjectType; public Object _parentObject; public GameObject _rootGameObject; public bool _deleted; public string _json; public string _scenePath; public string _missingObjectRefs; public string _missingMaterials; public string _runtimeInternalRefs; } private struct MaterialRef { public string _propertyPath; public Material _material; } #endregion #region Private Data private enum State { Idle, Busy, } private static State _state; private static PlayModeStateChange _currentPlayModeState; private static List _savedObjects = new List(); #endregion #region Constructor static UnityPlayModeSaver() { _currentPlayModeState = Application.isPlaying ? PlayModeStateChange.EnteredPlayMode : PlayModeStateChange.EnteredEditMode; EditorApplication.playModeStateChanged += OnModeChanged; EditorSceneManager.sceneSaving += OnSceneSaved; SceneManager.sceneUnloaded += delegate (Scene scene) { OnSceneUnload(scene); }; ClearCache(); } #endregion #region Menu Functions [MenuItem(kSaveComponentMenuString, false, kSaveComponentMenuPriority)] public static void SaveComponent(MenuCommand command) { Component component = command.context as Component; if (Application.isPlaying && component != null) { if (!IsObjectRegistered(component, out _)) { RegisterSavedObject(component); } UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kSaveComponentSnapshotMenuString, false, kSaveComponentMenuPriority)] public static void SaveComponentSnapshot(MenuCommand command) { Component component = command.context as Component; if (Application.isPlaying && component != null) { if (!IsObjectRegistered(component, out int index)) { index = RegisterSavedObject(component); } SaveSnapshot(index); UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kSaveComponentMenuString, true)] [MenuItem(kSaveComponentSnapshotMenuString, true)] public static bool ValidateSaveComponent(MenuCommand command) { Component component = command.context as Component; if (Application.isPlaying && component != null) return !IsObjectRegistered(component, out _); return false; } [MenuItem(kRevertComponentMenuString, false, kSaveComponentMenuPriority)] public static void RevertComponent(MenuCommand command) { Component component = command.context as Component; if (Application.isPlaying && component != null) { UnregisterObject(component); UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kRevertComponentMenuString, true)] public static bool ValidateRevertComponent(MenuCommand command) { Component component = command.context as Component; if (Application.isPlaying && component != null) return IsObjectRegistered(component, out _); return false; } [MenuItem(kSaveGameObjectMenuString, false, kSaveGameObjectMenuPriority)] public static void SaveGameObject() { if (Application.isPlaying && Selection.gameObjects != null) { foreach (GameObject gameObject in Selection.gameObjects) { if (!IsObjectRegistered(gameObject, out _)) { RegisterSavedObject(gameObject); } } UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kSaveGameObjectSnapshotMenuString, false, kSaveGameObjectMenuPriority)] public static void SaveGameObjectSnapshot() { if (Application.isPlaying && Selection.gameObjects != null) { foreach (GameObject gameObject in Selection.gameObjects) { if (!IsObjectRegistered(gameObject, out int index)) { index = RegisterSavedObject(gameObject); } SaveSnapshot(index); } UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kSaveGameObjectMenuString, true)] [MenuItem(kSaveGameObjectSnapshotMenuString, true)] public static bool ValidateSaveGameObject() { if (Application.isPlaying && Selection.gameObjects != null) { foreach (GameObject go in Selection.gameObjects) { if (!IsObjectRegistered(go, out _)) return true; } } return false; } [MenuItem(kRevertGameObjectMenuString, false, kSaveGameObjectMenuPriority)] public static void RevertGameObject() { if (Application.isPlaying && Selection.gameObjects != null) { foreach (GameObject go in Selection.gameObjects) { UnregisterObject(go); } UnityPlayModeSaverWindow.Open(false); } } [MenuItem(kRevertGameObjectMenuString, true)] public static bool ValidateRevertGameObject() { if (Application.isPlaying && Selection.gameObjects != null) { foreach (GameObject go in Selection.gameObjects) { if (IsObjectRegistered(go, out _)) return true; } } return false; } [MenuItem(kWindowMenuString)] public static void OpenWindow() { UnityPlayModeSaverWindow.Open(true); } #endregion #region Internal Interface (For Editor Window) internal static string GetObjectName(Object obj) { string objectName = null; if (obj is GameObject gameObject) { objectName = gameObject.name; } else if (obj is Component component) { objectName = component.gameObject.name + '.' + component.GetType().Name; } return objectName; } internal static bool IsObjectRegistered(Object obj, out int saveObjIndex) { saveObjIndex = _savedObjects.FindIndex(x => x._object == obj); return saveObjIndex != -1; } internal static int RegisterSavedObject(Object obj) { SavedObject savedObject = new SavedObject { _object = obj, _sceneIdentifier = GetSceneIdentifier(obj), _scenePath = GetScenePath(obj), _name = GetObjectName(obj), _type = obj.GetType(), _path = GetObjectPath(obj), }; _savedObjects.Add(savedObject); return _savedObjects.Count - 1; } internal static void UnregisterObject(Object obj) { if (IsObjectRegistered(obj, out int index)) { ClearSavedObject(index); } } internal static void ClearSavedObject(int saveObjIndex) { //Remove values from prefs if (_savedObjects[saveObjIndex]._hasSnapshot) { ClearSnapshot(saveObjIndex); } _savedObjects.RemoveAt(saveObjIndex); } internal static void ClearCache() { _savedObjects.Clear(); int numSavedObjects = EditorPrefs.GetInt(kEditorPrefsObjectCountKey, 0); for (int i = 0; i < numSavedObjects; i++) { string editorPrefKey = kEditorPrefsKey + Convert.ToString(i); DeleteObjectEditorPrefs(editorPrefKey); } SafeDeleteEditorPref(kEditorPrefsObjectCountKey); _state = State.Idle; } internal static void SaveSnapshot(int saveObjIndex) { SavedObject savedObject = _savedObjects[saveObjIndex]; if (savedObject._object != null) { //Delete current snapshot if (savedObject._hasSnapshot) { DeleteObjectEditorPrefs(savedObject._snapshotEditorPrefKey); } //Save values to prefs string editorPrefKey = SaveObjectValues(savedObject); savedObject._hasSnapshot = true; savedObject._snapshotEditorPrefKey = editorPrefKey; _savedObjects[saveObjIndex] = savedObject; } } internal static void ClearSnapshot(int saveObjIndex) { SavedObject savedObject = _savedObjects[saveObjIndex]; DeleteObjectEditorPrefs(savedObject._snapshotEditorPrefKey); savedObject._hasSnapshot = false; savedObject._snapshotEditorPrefKey = null; _savedObjects[saveObjIndex] = savedObject; } #endregion #region Private Functions #region Editor Functions private static void OnModeChanged(PlayModeStateChange state) { _currentPlayModeState = state; switch (state) { case PlayModeStateChange.ExitingEditMode: { //If restore or save failed, delete the cache if (_state != State.Idle) { ClearCache(); } CacheScenePrefabs(); } break; case PlayModeStateChange.ExitingPlayMode: { SaveObjectValues(); } break; case PlayModeStateChange.EnteredEditMode: { //If save completed ok, restore objects if (_state == State.Idle) { RestoreSavedObjects(); } //otherwise delete the cache else { ClearCache(); } RepaintEditorWindows(); } break; default: break; } } private static void OnSceneSaved(Scene scene, string path) { UnityPlayModeSaverSceneUtils.CacheScenePrefabInstances(scene); } private static void OnSceneUnload(Scene scene) { //Remove all saved objects from this scene that don't have snapshots for (int i = 0; i < _savedObjects.Count;) { if (!_savedObjects[i]._hasSnapshot && _savedObjects[i]._scenePath == scene.path) { _savedObjects.RemoveAt(i); } else { i++; } } } private static void RepaintEditorWindows() { SceneView.RepaintAll(); Type inspectorWindowType = Type.GetType("UnityEditor.InspectorWindow, UnityEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); if (inspectorWindowType != null) { EditorWindow.GetWindow(inspectorWindowType).Repaint(); } Type gameViewType = Type.GetType("UnityEditor.GameView, UnityEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); if (gameViewType != null) { BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; MethodInfo methodInfo = gameViewType.GetMethod("RepaintAll", bindingFlags, null, new Type[] { }, null); if (methodInfo != null) { methodInfo.Invoke(null, null); } } } #endregion #region Object Registering private static int AddSaveObject() { int objectCount = EditorPrefs.GetInt(kEditorPrefsObjectCountKey); int saveObjIndex = objectCount; objectCount++; EditorPrefs.SetInt(kEditorPrefsObjectCountKey, objectCount); return saveObjIndex; } private static int AddChildSaveObject(string parentEditorPrefKey) { int childCount = EditorPrefs.GetInt(parentEditorPrefKey + kEditorPrefsSceneObjectChildren, 0); int saveObjIndex = childCount; childCount++; EditorPrefs.SetInt(parentEditorPrefKey + kEditorPrefsSceneObjectChildren, childCount); return saveObjIndex; } #region Scene Objects private static string RegisterSceneObject(Object obj, string scenePath, int identifier) { //Check scene object is a prefab instance... if (IsScenePrefab(obj, scenePath, out GameObject prefabInstance, out int prefabSceneId)) { return RegisterScenePrefabObject(scenePath, identifier, prefabInstance, prefabSceneId, obj); } else { int saveObjIndex = GetSavedSceneObjectIndex(identifier, scenePath); if (saveObjIndex == -1) { saveObjIndex = AddSaveObject(); } string editorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectScene, scenePath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsObjectSceneId, identifier); return editorPrefKey; } } private static string RegisterChildSceneObject(string parentEditorPrefKey, Object obj, string scenePath, int identifier) { //Check scene object is a prefab instance... if (IsScenePrefab(obj, scenePath, out GameObject prefabInstance, out int prefabSceneId)) { string prefabObjPath = GetScenePrefabChildObjectPath(prefabInstance, obj); //First check object is already saved int saveObjIndex = GetSavedScenePrefabObjectIndex(identifier, scenePath, prefabObjPath, prefabSceneId); if (saveObjIndex != -1) { //If so delete the origanal saved object (save in heirachy instead) string origEditorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); DeleteObjectEditorPrefs(origEditorPrefKey); } int childObjIndex = AddChildSaveObject(parentEditorPrefKey); string editorPrefKey = parentEditorPrefKey + '.' + Convert.ToString(childObjIndex); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsObjectSceneId, identifier); EditorPrefs.SetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath, prefabObjPath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, prefabSceneId); return editorPrefKey; } else { //First check object is already saved int saveObjIndex = GetSavedSceneObjectIndex(identifier, scenePath); if (saveObjIndex != -1) { //If so delete the origanal saved object (save in heirachy instead) string origEditorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); DeleteObjectEditorPrefs(origEditorPrefKey); } int childObjIndex = AddChildSaveObject(parentEditorPrefKey); string editorPrefKey = parentEditorPrefKey + '.' + Convert.ToString(childObjIndex); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsObjectSceneId, identifier); return editorPrefKey; } } private static string RegisterDeletedSceneObject(string scenePath, int identifier) { int saveObjIndex = GetSavedSceneObjectIndex(identifier, scenePath); if (saveObjIndex == -1) { saveObjIndex = AddSaveObject(); } string editorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectScene, scenePath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsObjectSceneId, identifier); EditorPrefs.SetBool(editorPrefKey + kEditorPrefsObjectDeleted, true); return editorPrefKey; } private static int GetSavedSceneObjectIndex(int localIdentifier, string scenePath) { int numSavedObjects = EditorPrefs.GetInt(kEditorPrefsObjectCountKey, 0); for (int i = 0; i < numSavedObjects; i++) { string editorPrefKey = kEditorPrefsKey + Convert.ToString(i); string sceneStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectScene); int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsObjectSceneId, -1); if (sceneStr == scenePath && localIdentifier == identifier) { return i; } } return -1; } #endregion #region Scene Prefab Objects private static bool IsScenePrefab(Object obj, string scenePath, out GameObject prefab, out int prefabSceneId) { if (GetActiveScene(scenePath, out Scene scene)) { return UnityPlayModeSaverSceneUtils.IsScenePrefabInstance(obj, scene, out prefab, out prefabSceneId); } prefab = null; prefabSceneId = -1; return false; } private static string RegisterScenePrefabObject(string scenePath, int identifier, GameObject prefabInstance, int prefabSceneId, Object obj) { string prefabObjPath = GetScenePrefabChildObjectPath(prefabInstance, obj); int saveObjIndex = GetSavedScenePrefabObjectIndex(identifier, scenePath, prefabObjPath, prefabSceneId); if (saveObjIndex == -1) { saveObjIndex = AddSaveObject(); } string editorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectScene, scenePath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsObjectSceneId, identifier); EditorPrefs.SetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath, prefabObjPath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, prefabSceneId); return editorPrefKey; } private static int GetSavedScenePrefabObjectIndex(int localIdentifier, string scenePath, string prefabPath, int prefabId) { int numSavedObjects = EditorPrefs.GetInt(kEditorPrefsObjectCountKey, 0); for (int i = 0; i < numSavedObjects; i++) { string editorPrefKey = kEditorPrefsKey + Convert.ToString(i); string sceneStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectScene); int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsObjectSceneId, -1); string prefabObjPathStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath); int prefabObjId = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, -1); if (sceneStr == scenePath && localIdentifier == identifier && prefabObjPathStr == prefabPath && prefabId == prefabObjId) { return i; } } return -1; } #endregion #region Runtime Objects private static string RegisterRuntimeObject(string scenePath, int instanceId) { int saveObjIndex = GetSavedRuntimeObjectIndex(instanceId, scenePath); if (saveObjIndex == -1) { saveObjIndex = AddSaveObject(); } string editorPrefKey = kEditorPrefsKey + Convert.ToString(saveObjIndex); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectScene, scenePath); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsRuntimeObjectId, instanceId); return editorPrefKey; } private static int GetSavedRuntimeObjectIndex(int instanceId, string scenePath) { int numSavedObjects = EditorPrefs.GetInt(kEditorPrefsObjectCountKey, 0); for (int i = 0; i < numSavedObjects; i++) { string editorPrefKey = kEditorPrefsKey + Convert.ToString(i); string sceneStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectScene); int instance = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsRuntimeObjectId, -1); if (sceneStr == scenePath && instanceId == instance) { return i; } } return -1; } #endregion #endregion #region Object Saving private static void SaveObjectValues() { _state = State.Busy; //Save all saved objects into editor prefs (might also add children) foreach (SavedObject obj in _savedObjects) { if (!obj._hasSnapshot) { SaveObjectValues(obj); } } _savedObjects.Clear(); _state = State.Idle; } private static string SaveObjectValues(SavedObject obj) { string editorPrefKey = null; //Save scene object if (obj._sceneIdentifier != -1) { //Object is still valid if (obj._object != null) { editorPrefKey = RegisterSceneObject(obj._object, obj._scenePath, obj._sceneIdentifier); SaveObjectValues(editorPrefKey, obj._object); //If its a GameObject then add its components and child GameObjects (some of which might be runtime) if (obj._object is GameObject gameObject) { AddSceneGameObjectChildObjectValues(editorPrefKey, gameObject); } } //Object has been deleted else { RegisterDeletedSceneObject(obj._scenePath, obj._sceneIdentifier); } } //Save runtime object else { //Object is still valid if (obj._object != null) { int instanceId = GetInstanceId(obj._object); editorPrefKey = RegisterRuntimeObject(obj._scenePath, instanceId); GameObject sceneParent; GameObject topOfHieracy; if (obj._object is Component component) { topOfHieracy = component.gameObject; FindRuntimeObjectParent(component.gameObject, out sceneParent, ref topOfHieracy); //If the new component belongs to a scene object, just save the new component if (component.gameObject == sceneParent) { SaveRuntimeComponent(editorPrefKey, component, sceneParent, sceneParent); } //Otherwise need to save the whole new gameobject hierarchy else { SaveRuntimeGameObject(editorPrefKey, topOfHieracy, topOfHieracy, sceneParent, null); } } else if (obj._object is GameObject gameObject) { topOfHieracy = gameObject; FindRuntimeObjectParent(gameObject, out sceneParent, ref topOfHieracy); if (topOfHieracy != gameObject) { EditorPrefs.SetInt(editorPrefKey + kEditorPrefsRuntimeObjectId, GetInstanceId(topOfHieracy)); } SaveRuntimeGameObject(editorPrefKey, topOfHieracy, topOfHieracy, sceneParent, null); } } } return editorPrefKey; } private static void SaveObjectValues(string editorPrefKey, Object obj, bool runtimeObject = false, GameObject runtimeObjectTopOfHeirachy = null) { RestoredObjectData data = GetObjectData(obj, runtimeObject, runtimeObjectTopOfHeirachy); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectJson, data._json); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectRefs, data._missingObjectRefs); EditorPrefs.SetString(editorPrefKey + kEditorPrefsObjectMaterialRefs, data._missingMaterials); EditorPrefs.SetString(editorPrefKey + kEditorPrefsRuntimeObjectRefs, data._runtimeInternalRefs); } private static bool ShouldUseEditorSerialiser(Object obj) { if ((obj is Component && !(obj is MonoBehaviour)) || (obj is GameObject)) return true; return false; } private static RestoredObjectData GetObjectData(Object obj, bool runtimeObject, GameObject runtimeObjectTopOfHierarchy) { RestoredObjectData data = new RestoredObjectData(); bool unityType = ShouldUseEditorSerialiser(obj); SerializedObject serializedObject = new SerializedObject(obj); SerializedProperty propertry = serializedObject.GetIterator(); while (propertry.NextVisible(true)) { //Store material properties that now point at a runtime instance of a material (they will get reverted to original values) if (propertry.type == "PPtr") { if (propertry.objectReferenceValue != null && propertry.objectReferenceValue.name.EndsWith("(Instance)")) { if (!string.IsNullOrEmpty(data._missingMaterials)) data._missingMaterials += kItemSplitChar; data._missingMaterials += propertry.propertyPath; } } //Save any object ptr properties that point at scene objects else if (propertry.type.StartsWith("PPtr<") && propertry.objectReferenceValue != null) { if (unityType) { //Only store the object if the reference is within the same scene Scene objScne = GetObjectScene(obj); if (objScne.IsValid() && objScne == GetObjectScene(propertry.objectReferenceValue)) { int objId = GetSceneIdentifier(propertry.objectReferenceValue); if (objId != -1) { if (!string.IsNullOrEmpty(data._missingObjectRefs)) data._missingObjectRefs += kItemSplitChar; data._missingObjectRefs += Convert.ToString(objId) + kObjectPathSplitChar + propertry.propertyPath; } } } if (runtimeObject) { //If object ref is part of hierachy then need to tsav if (GetChildObjectPath(string.Empty, runtimeObjectTopOfHierarchy, propertry.objectReferenceValue, false, out string path)) { if (!string.IsNullOrEmpty(data._runtimeInternalRefs)) data._runtimeInternalRefs += kItemSplitChar; data._runtimeInternalRefs += path + kObjectPathSplitChar + propertry.propertyPath; } } } } //If Object is a Unity builtin Component we have to restore any scene links as they won't be serialized by EditorJsonUtility if (unityType) { data._json = EditorJsonUtility.ToJson(obj); } else { data._json = JsonUtility.ToJson(obj); } return data; } #region Scene Objects private static Object FindSceneObject(string scenePath, int localIdentifier, bool loadSceneIfNeeded = false) { if (localIdentifier != -1) { if (GetActiveScene(scenePath, out Scene scene, loadSceneIfNeeded)) { foreach (GameObject rootObject in scene.GetRootGameObjects()) { Object obj = FindSceneObject(rootObject, localIdentifier); if (obj != null) { return obj; } } } } return null; } private static Object FindSceneObject(GameObject gameObject, int localIdentifier) { if (gameObject != null && localIdentifier != -1) { //Check game object if (GetSceneIdentifier(gameObject) == localIdentifier) return gameObject; //Check components Component[] components = gameObject.GetComponents(); foreach (Component component in components) { if (GetSceneIdentifier(component) == localIdentifier) return component; } //Check children foreach (Transform child in gameObject.transform) { Object obj = FindSceneObject(child.gameObject, localIdentifier); if (obj != null) return obj; } } return null; } private static void AddSceneGameObjectChildObjectValues(string parentEditorPrefKey, GameObject gameObject) { Component[] components = gameObject.GetComponents(); //Save each component for (int i = 0; i < components.Length; i++) { int identifier = GetSceneIdentifier(components[i]); //Scene component if (identifier != -1) { string editorPrefKey = RegisterChildSceneObject(parentEditorPrefKey, components[i], gameObject.scene.path, identifier); SaveObjectValues(editorPrefKey, components[i]); } //Runtime component else { int instanceId = GetInstanceId(components[i]); string scenePath = gameObject.scene.path; string editorPrefKey = RegisterRuntimeObject(scenePath, instanceId); SaveRuntimeComponent(editorPrefKey, components[i], gameObject, gameObject); } } //Save each child object foreach (Transform child in gameObject.transform) { int identifier = GetSceneIdentifier(child.gameObject); //Scene gameobject if (identifier != -1) { string editorPrefKey = RegisterChildSceneObject(parentEditorPrefKey, child.gameObject, child.gameObject.scene.path, identifier); SaveObjectValues(editorPrefKey, child.gameObject); AddSceneGameObjectChildObjectValues(parentEditorPrefKey, child.gameObject); } //Runtime gameobject else { int instanceId = GetInstanceId(child.gameObject); string scenePath = gameObject.scene.path; string editorPrefKey = RegisterRuntimeObject(scenePath, instanceId); SaveRuntimeGameObject(editorPrefKey, child.gameObject, child.gameObject, gameObject, null); } } } #endregion #region Scene Prefab Objects private static Object FindScenePrefabObject(string scenePath, int instanceId, string prefabObjPath, bool loadSceneIfNeeded = false) { if (GetActiveScene(scenePath, out Scene scene, loadSceneIfNeeded)) { GameObject prefabInstance = UnityPlayModeSaverSceneUtils.GetScenePrefabInstance(scene, instanceId); if (prefabInstance != null) { return GetChildObject(prefabInstance, prefabObjPath); } } return null; } #endregion #region Runtime Objects private static Object FindRuntimeObject(string scenePath, int instanceId) { if (GetActiveScene(scenePath, out Scene scene)) { foreach (GameObject rootObject in scene.GetRootGameObjects()) { Object obj = FindRuntimeObject(rootObject, instanceId); if (obj != null) { return obj; } } } return null; } private static Object FindRuntimeObject(GameObject gameObject, int instanceId) { //Check game object if (GetInstanceId(gameObject) == instanceId) return gameObject; //Check components Component[] components = gameObject.GetComponents(); foreach (Component component in components) { if (GetInstanceId(component) == instanceId) return component; } //Check children foreach (Transform child in gameObject.transform) { Object obj = FindRuntimeObject(child.gameObject, instanceId); if (obj != null) return obj; } return null; } private static void SaveRuntimeGameObject(string editorPrefKey, GameObject gameObject, GameObject topOfHieracy, GameObject parentSceneObject, GameObject parentPrefab) { EditorPrefs.SetString(editorPrefKey + kEditorPrefsRuntimeObjectType, GetTypeString(gameObject.GetType())); SaveObjectValues(editorPrefKey, gameObject, true, topOfHieracy); SaveRuntimeGameObjectParent(editorPrefKey, gameObject, parentSceneObject); //Check if this game object is a prefab GameObject prefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(gameObject); if (prefabRoot == gameObject) { Object parentObject = PrefabUtility.GetCorrespondingObjectFromSource(prefabRoot); string prefabPath = AssetDatabase.GetAssetPath(parentObject); EditorPrefs.SetString(editorPrefKey + kEditorPrefsRuntimeObjectPrefab, prefabPath); parentPrefab = prefabRoot; } //Save all components and child GameObjects int childObjectIndex = 0; Component[] components = gameObject.GetComponents(); for (int i = 0; i < components.Length; i++) { bool isPartOfCurrentPrefabHieracy = parentPrefab != null && PrefabUtility.GetNearestPrefabInstanceRoot(components[i]) == parentPrefab; SaveRuntimeComponent(editorPrefKey + "." + Convert.ToString(childObjectIndex), components[i], topOfHieracy, null); if (isPartOfCurrentPrefabHieracy) EditorPrefs.SetInt(editorPrefKey + "." + Convert.ToString(childObjectIndex) + kEditorPrefsRuntimeObjectPrefabObjIndex, GetPrefabComponentIndex(parentPrefab, components[i])); childObjectIndex++; } foreach (Transform child in gameObject.transform) { bool isPartOfCurrentPrefabHieracy = parentPrefab != null && PrefabUtility.GetNearestPrefabInstanceRoot(child.gameObject) == parentPrefab; SaveRuntimeGameObject(editorPrefKey + "." + Convert.ToString(childObjectIndex), child.gameObject, topOfHieracy, null, isPartOfCurrentPrefabHieracy ? parentPrefab : null); if (isPartOfCurrentPrefabHieracy) EditorPrefs.SetInt(editorPrefKey + "." + Convert.ToString(childObjectIndex) + kEditorPrefsRuntimeObjectPrefabObjIndex, GetPrefabChildIndex(parentPrefab, child.gameObject)); childObjectIndex++; } //If a component contains object ref and ref is part of this runtime objects heiracy then save a ref to it somehow? } private static void SaveRuntimeComponent(string editorPrefKey, Component component, GameObject topOfHieracy, GameObject parentSceneObject) { EditorPrefs.SetString(editorPrefKey + kEditorPrefsRuntimeObjectType, GetTypeString(component.GetType())); SaveObjectValues(editorPrefKey, component, true, topOfHieracy); SaveRuntimeGameObjectParent(editorPrefKey, component.gameObject, parentSceneObject); } private static void SaveRuntimeGameObjectParent(string editorPrefKey, GameObject gameObject, GameObject parentSceneObject) { if (parentSceneObject != null) { int identifier = GetSceneIdentifier(parentSceneObject); if (identifier != -1) { if (UnityPlayModeSaverSceneUtils.IsScenePrefabInstance(parentSceneObject, parentSceneObject.scene, out GameObject prefabInstance, out int prefabSceneId)) { string prefabPath = GetScenePrefabChildObjectPath(prefabInstance, parentSceneObject); EditorPrefs.SetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, prefabSceneId); EditorPrefs.SetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath, prefabPath); } EditorPrefs.SetInt(editorPrefKey + kEditorPrefsRuntimeObjectParentId, identifier); } } else { EditorPrefs.SetInt(editorPrefKey + kEditorPrefsRuntimeObjectSceneRootIndex, gameObject.transform.GetSiblingIndex()); } } private static void FindRuntimeObjectParent(GameObject gameObject, out GameObject sceneParent, ref GameObject topOfHieracy) { if (GetSceneIdentifier(gameObject) != -1) { sceneParent = gameObject; } else if (gameObject.transform.parent == null) { sceneParent = null; topOfHieracy = gameObject; } else { topOfHieracy = gameObject; FindRuntimeObjectParent(gameObject.transform.parent.gameObject, out sceneParent, ref topOfHieracy); } } private static GameObject GetRuntimeObjectParent(string editorPrefKey, string sceneStr, bool loadSceneIfNeeded = false) { //Parent is Scene Prefab Instance if (EditorPrefs.HasKey(editorPrefKey + kEditorPrefsScenePrefabInstanceId)) { //Get prefab from scene int prefabId = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, -1); string prefabObjPathStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath); Object obj = FindScenePrefabObject(sceneStr, prefabId, prefabObjPathStr, loadSceneIfNeeded); return obj as GameObject; } //Parent is Scene object else { int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsRuntimeObjectParentId, -1); Object obj = FindSceneObject(sceneStr, identifier, loadSceneIfNeeded); return obj as GameObject; } } #endregion #endregion #region Object Restoring private static void RestoreSavedObjects() { _state = State.Busy; int numSavedObjects = EditorPrefs.GetInt(kEditorPrefsObjectCountKey, 0); List restoredObjects = new List(); List restoredObjectsData = new List(); for (int i = 0; i < numSavedObjects; i++) { string editorPrefKey = kEditorPrefsKey + Convert.ToString(i); string sceneStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectScene, null); //Scene object if (EditorPrefs.HasKey(editorPrefKey + kEditorPrefsObjectSceneId)) { //Has object been deleted? if (EditorPrefs.GetBool(editorPrefKey + kEditorPrefsObjectDeleted, false)) { int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsObjectSceneId, -1); Object obj = FindSceneObject(sceneStr, identifier, true); if (obj != null) { restoredObjects.Add(obj); restoredObjectsData.Add(CreateDeletedSceneObjectRestoredData(obj, sceneStr)); } } //Otherwise restore as normal else { //Scene Prefab object if (EditorPrefs.HasKey(editorPrefKey + kEditorPrefsScenePrefabInstanceId)) { //Get prefab from scene int prefabId = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, -1); string prefabObjPathStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath); Object obj = FindScenePrefabObject(sceneStr, prefabId, prefabObjPathStr, true); if (obj != null) { restoredObjects.Add(obj); restoredObjectsData.Add(CreateSceneObjectRestoredData(editorPrefKey, obj, sceneStr)); //If its a game object also restore any saved child objects if (obj is GameObject gameObject) { RestoreSavedObjectChildren(editorPrefKey, gameObject, sceneStr, ref restoredObjects, ref restoredObjectsData); } } } //Normal Scene object else { int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsObjectSceneId, -1); Object obj = FindSceneObject(sceneStr, identifier, true); if (obj != null) { restoredObjects.Add(obj); restoredObjectsData.Add(CreateSceneObjectRestoredData(editorPrefKey, obj, sceneStr)); //If its a game object also restore any saved child objects if (obj is GameObject gameObject) { RestoreSavedObjectChildren(editorPrefKey, gameObject, sceneStr, ref restoredObjects, ref restoredObjectsData); } } } } } //Runtime Object else { string typeStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsRuntimeObjectType); Type objType = GetType(typeStr); GameObject parentObj = GetRuntimeObjectParent(editorPrefKey, sceneStr, true) as GameObject; //New runtime gameobject hierachy if (objType == typeof(GameObject)) { GameObject gameObject = null; Scene scene; bool isPrefab = false; //If the object is a prefab, instantiate it if (EditorPrefs.HasKey(editorPrefKey + kEditorPrefsRuntimeObjectPrefab)) { string prefabPath = EditorPrefs.GetString(editorPrefKey + kEditorPrefsRuntimeObjectPrefab); GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath); if (prefab != null) { gameObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject; isPrefab = gameObject != null; } } //Otherwise create blank game object if (!isPrefab) { gameObject = new GameObject(); } //If we have a parent scene object, move it to become a child if (parentObj != null) { gameObject.transform.parent = parentObj.transform; } //otherwise make sure its in the correct scene else if (GetActiveScene(sceneStr, out scene, true)) { SceneManager.MoveGameObjectToScene(gameObject, scene); int sceneRootIndex = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsRuntimeObjectSceneRootIndex, 0); gameObject.transform.SetSiblingIndex(sceneRootIndex); } List runtimeRestoredObjects = new List { CreateRuntimeObjectRestoredData(editorPrefKey, gameObject, gameObject, sceneStr) }; //Create the new objects heirachy gameObject = RestoreRuntimeGameObject(gameObject, editorPrefKey, sceneStr, gameObject, runtimeRestoredObjects); //Then restore all the new objects data for (int j = 0; j < runtimeRestoredObjects.Count; j++) { RestoreObjectFromData(runtimeRestoredObjects[j]); } Undo.RegisterCreatedObjectUndo(gameObject, kUndoText); } //New runtime component on an existing scene object else if (typeof(Component).IsAssignableFrom(objType)) { if (parentObj != null) { restoredObjectsData.Add(CreateRuntimeCopmonentRestoredData(editorPrefKey, objType, parentObj, sceneStr)); } } } DeleteObjectEditorPrefs(editorPrefKey); } if (restoredObjectsData.Count > 0) { Undo.RecordObjects(restoredObjects.ToArray(), kUndoText); for (int i = 0; i < restoredObjectsData.Count; i++) { RestoreObjectFromData(restoredObjectsData[i]); } } SafeDeleteEditorPref(kEditorPrefsObjectCountKey); _state = State.Idle; } private static void RestoreObjectFromData(RestoredObjectData data) { if (data._object == null) { GameObject parent = data._parentObject as GameObject; if (typeof(Component).IsAssignableFrom(data._createdObjectType) && parent != null) { data._object = Undo.AddComponent(parent, data._createdObjectType); } } if (data._object != null) { if (data._deleted) { //If object is part of prefab instance then cant destory it without breaking prefab instance GameObject prefabInstance = PrefabUtility.GetNearestPrefabInstanceRoot(data._object); if (prefabInstance != null) { if (EditorUtility.DisplayDialog(kPrefabUnpackWarningTitle, kPrefabUnpackWarningMsg, kPrefabUnpackWarningUnpack, kPrefabUnpackWarningIgnore)) { //Unpack prefab. PrefabUtility.UnpackPrefabInstance(prefabInstance, PrefabUnpackMode.OutermostRoot, InteractionMode.UserAction); //Destory object Undo.DestroyObjectImmediate(data._object); } } else { Undo.DestroyObjectImmediate(data._object); } } else { bool unityType = ShouldUseEditorSerialiser(data._object); //Find any lost material refs List materialRefs = FindOriginalMaterials(data._object, data._missingMaterials); if (unityType) { EditorJsonUtility.FromJsonOverwrite(data._json, data._object); ApplyObjectRefs(data._object, data._scenePath, data._missingObjectRefs); } else { JsonUtility.FromJsonOverwrite(data._json, data._object); } //Apply runtime internal refs ApplyRuntimeObjectRefs(data._object, data._rootGameObject, data._runtimeInternalRefs); //Revert any lost material refs ApplyMaterialsRefs(data._object, materialRefs); //Refresh Canvas renderers DirtyCanvasRenderers(data._object); EditorUtility.SetDirty(data._object); } } } private static void RestoreSavedObjectChildren(string parentEditorPrefKey, GameObject gameObject, string sceneStr, ref List restoredObjects, ref List restoredObjectsData) { //First build a dictionary of saved object data for child objects (components / gameobjects) Dictionary childrenData = new Dictionary(); int children = EditorPrefs.GetInt(parentEditorPrefKey + kEditorPrefsSceneObjectChildren, 0); for (int i = 0; i < children; i++) { string editorPrefKey = parentEditorPrefKey + '.' + Convert.ToString(i); //Scene Prefab object if (EditorPrefs.HasKey(editorPrefKey + kEditorPrefsScenePrefabInstanceId)) { //Get prefab from scene int prefabId = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsScenePrefabInstanceId, -1); string prefabObjPathStr = EditorPrefs.GetString(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath); Object obj = FindScenePrefabObject(sceneStr, prefabId, prefabObjPathStr, true); if (obj != null) { childrenData.Add(obj, CreateSceneObjectRestoredData(editorPrefKey, obj, sceneStr)); } } //Normal Scene object else { int identifier = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsObjectSceneId, -1); Object obj = FindSceneObject(sceneStr, identifier, true); if (obj != null) { childrenData.Add(obj, CreateSceneObjectRestoredData(editorPrefKey, obj, sceneStr)); } } } //Then go through heirachy and restore all child components / gameobejcts from this dictionary RestoreChildSavedObject(gameObject, sceneStr, childrenData, ref restoredObjects, ref restoredObjectsData); } private static void RestoreChildSavedObject(GameObject gameObject, string sceneStr, Dictionary childrenData, ref List restoredObjects, ref List restoredObjectsData) { Component[] components = gameObject.GetComponents(); //Save each component for (int i = 0; i < components.Length; i++) { restoredObjects.Add(components[i]); if (childrenData.TryGetValue(components[i], out RestoredObjectData componentRestoredObjectData)) { restoredObjectsData.Add(componentRestoredObjectData); } //If don't have saved data for the component then delete it (unless its a Transform which can't be deleted) else if (components[i].GetType() != typeof(Transform)) { restoredObjectsData.Add(CreateDeletedSceneObjectRestoredData(components[i], sceneStr)); } } //Save each child object foreach (Transform child in gameObject.transform) { restoredObjects.Add(child.gameObject); if (childrenData.TryGetValue(child.gameObject, out RestoredObjectData restoredObjectData)) { restoredObjectsData.Add(restoredObjectData); RestoreChildSavedObject(child.gameObject, sceneStr, childrenData, ref restoredObjects, ref restoredObjectsData); } //If don't have saved data for the child GameObject then delete it else { restoredObjectsData.Add(CreateDeletedSceneObjectRestoredData(child.gameObject, sceneStr)); } } } private static List FindOriginalMaterials(Object obj, string materialStr) { List materialRefs = new List(); SerializedObject serializedObject = new SerializedObject(obj); string[] materials = materialStr.Split(kItemSplitChar); foreach (string material in materials) { SerializedProperty materialProp = serializedObject.FindProperty(material); if (materialProp != null) { MaterialRef materialRef = new MaterialRef { _material = materialProp.objectReferenceValue as Material, _propertyPath = material }; materialRefs.Add(materialRef); } } return materialRefs; } private static void ApplyObjectRefs(Object obj, string sceneStr, string objectRefStr) { if (objectRefStr != null) { SerializedObject serializedObject = new SerializedObject(obj); string[] objectRefs = objectRefStr.Split(kItemSplitChar); foreach (string objectRef in objectRefs) { int split = objectRef.IndexOf(kObjectPathSplitChar); if (split != -1) { int id = SafeConvertToInt(objectRef.Substring(0, split)); if (id != -1) { string objPath = objectRef.Substring(split + 1, objectRef.Length - split - 1); SerializedProperty localIdProp = serializedObject.FindProperty(objPath); if (localIdProp != null) { localIdProp.objectReferenceValue = FindSceneObject(sceneStr, id); } } } } serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } private static void ApplyRuntimeObjectRefs(Object obj, GameObject rootGameObject, string objectRefStr) { if (objectRefStr != null) { SerializedObject serializedObject = new SerializedObject(obj); string[] objectRefs = objectRefStr.Split(kItemSplitChar); foreach (string objectRef in objectRefs) { int split = objectRef.IndexOf(kObjectPathSplitChar); if (split != -1) { string runtimeHierarchyPath = objectRef.Substring(0, split); string objPath = objectRef.Substring(split + 1, objectRef.Length - split - 1); SerializedProperty localIdProp = serializedObject.FindProperty(objPath); if (localIdProp != null) { localIdProp.objectReferenceValue = GetChildObject(rootGameObject, runtimeHierarchyPath); } } } serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } private static void ApplyMaterialsRefs(Object obj, List materialRefs) { SerializedObject serializedObject = new SerializedObject(obj); foreach (MaterialRef materialRef in materialRefs) { SerializedProperty materialProp = serializedObject.FindProperty(materialRef._propertyPath); if (materialProp != null) { materialProp.objectReferenceValue = materialRef._material; } } serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void DirtyCanvasRenderers(Object obj) { if (obj is Graphic graphic) { graphic.SetAllDirty(); } else if (obj is GameObject gameObject) { DirtyCanvasRenderers(gameObject); } } private static void DirtyCanvasRenderers(GameObject gameObject) { Graphic[] graphicComponents = gameObject.GetComponents(); for (int i = 0; i < graphicComponents.Length; i++) { graphicComponents[i].SetAllDirty(); } foreach (Transform child in gameObject.transform) { DirtyCanvasRenderers(child.gameObject); } } #region Scene Objects private static RestoredObjectData CreateSceneObjectRestoredData(string editorPrefKey, Object obj, string sceneStr) { return new RestoredObjectData { _object = obj, _createdObjectType = null, _parentObject = null, _rootGameObject = null, _json = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectJson), _scenePath = sceneStr, _missingObjectRefs = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectRefs), _missingMaterials = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectMaterialRefs), _runtimeInternalRefs = null }; } private static RestoredObjectData CreateDeletedSceneObjectRestoredData(Object obj, string sceneStr) { return new RestoredObjectData { _object = obj, _deleted = true, _createdObjectType = null, _parentObject = null, _rootGameObject = null, _json = null, _scenePath = sceneStr, _missingObjectRefs = null, _missingMaterials = null, _runtimeInternalRefs = null }; } #endregion #region Runtime Objects private static RestoredObjectData CreateRuntimeObjectRestoredData(string editorPrefKey, Object obj, GameObject rootObject, string sceneStr) { return new RestoredObjectData { _object = obj, _createdObjectType = null, _parentObject = null, _rootGameObject = rootObject, _json = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectJson), _scenePath = sceneStr, _missingObjectRefs = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectRefs), _missingMaterials = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectMaterialRefs), _runtimeInternalRefs = EditorPrefs.GetString(editorPrefKey + kEditorPrefsRuntimeObjectRefs) }; } private static RestoredObjectData CreateRuntimeCopmonentRestoredData(string editorPrefKey, Type componentType, GameObject parentGameObject, string sceneStr) { return new RestoredObjectData { _object = null, _createdObjectType = componentType, _parentObject = parentGameObject, _rootGameObject = null, _json = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectJson), _scenePath = sceneStr, _missingObjectRefs = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectRefs), _missingMaterials = EditorPrefs.GetString(editorPrefKey + kEditorPrefsObjectMaterialRefs), _runtimeInternalRefs = null }; } private static GameObject RestoreRuntimeGameObject(GameObject gameObject, string editorPrefKey, string sceneStr, GameObject runtimeObjectRoot, List restoredObjectsData) { int childIndex = 0; string childeditorPrefKey; while (EditorPrefs.HasKey((childeditorPrefKey = editorPrefKey + "." + Convert.ToString(childIndex)) + kEditorPrefsRuntimeObjectType)) { string typeStr = EditorPrefs.GetString(childeditorPrefKey + kEditorPrefsRuntimeObjectType); Type objType = GetType(typeStr); Object obj = null; if (objType == typeof(Transform)) { obj = gameObject.transform; } else if (typeof(Component).IsAssignableFrom(objType)) { //If a prefab component.. if (EditorPrefs.HasKey(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefabObjIndex)) { int componentIndex = EditorPrefs.GetInt(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefabObjIndex, -1); obj = GetPrefabComponent(runtimeObjectRoot, gameObject, objType, componentIndex); } if (obj == null) { obj = gameObject.AddComponent(objType); } } else if(objType == typeof(GameObject)) { GameObject childGameObject = null; //Check is a new prefab instance if (EditorPrefs.HasKey(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefab)) { string prefabPath = EditorPrefs.GetString(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefab); GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath); if (prefab != null) { childGameObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject; childGameObject.transform.parent = gameObject.transform; runtimeObjectRoot = childGameObject; } } //If a prefab child.. if (EditorPrefs.HasKey(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefabObjIndex)) { int childGameObjectIndex = EditorPrefs.GetInt(childeditorPrefKey + kEditorPrefsRuntimeObjectPrefabObjIndex, -1); childGameObject = GetPrefabChild(runtimeObjectRoot, gameObject, childGameObjectIndex); } if (childGameObject == null) { childGameObject = new GameObject(); childGameObject.transform.parent = gameObject.transform; } obj = childGameObject; RestoreRuntimeGameObject(childGameObject, childeditorPrefKey, sceneStr, runtimeObjectRoot, restoredObjectsData); } restoredObjectsData.Add(CreateRuntimeObjectRestoredData(childeditorPrefKey, obj, runtimeObjectRoot, sceneStr)); DeleteObjectEditorPrefs(childeditorPrefKey); childIndex++; } return gameObject; } private static void DeleteObjectEditorPrefs(string editorPrefKey) { SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectScene); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectSceneId); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectDeleted); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsScenePrefabInstanceId); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsScenePrefabInstanceChildPath); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectId); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectParentId); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectSceneRootIndex); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectType); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectPrefab); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectPrefabObjIndex); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectJson); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectRefs); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsObjectMaterialRefs); SafeDeleteEditorPref(editorPrefKey + kEditorPrefsRuntimeObjectRefs); int children = EditorPrefs.GetInt(editorPrefKey + kEditorPrefsSceneObjectChildren, 0); for (int i=0; i(); int index = 0; for (int i = 0; i < components.Length; i++) { if (components[i] == component) { return index; } if (components[i].GetType() == component.GetType() && PrefabUtility.GetNearestPrefabInstanceRoot(components[i]) == prefabRoot) { index++; } } return index; } private static Component GetPrefabComponent(GameObject prefabRoot, GameObject gameObject, Type type, int index) { if (index != -1) { Component[] components = gameObject.GetComponents(); int count = 0; for (int i = 0; i < components.Length; i++) { if (PrefabUtility.GetNearestPrefabInstanceRoot(components[i]) == prefabRoot && components[i].GetType() == type) { if (count == index) { return components[i]; } count++; } } } return null; } private static GameObject GetPrefabChild(GameObject prefabRoot, GameObject gameObject, int index) { if (index != -1) { int count = 0; foreach (Transform child in gameObject.transform) { if (PrefabUtility.GetNearestPrefabInstanceRoot(child) == prefabRoot) { if (count == index) { return child.gameObject; } count++; } } } return null; } private static bool GetChildObjectPath(string path, GameObject rootObject, Object obj, bool sceneObjectsOnly, out string fullPath) { //Check gameobject itself matches object if (rootObject == obj) { fullPath = path; return true; } //Check any of the gameobjects components matches object Component[] components = rootObject.GetComponents(); int componentIndex = 0; for (int i = 0; i < components.Length; i++) { if (!sceneObjectsOnly || GetSceneIdentifier(obj) != -1) { if (components[i] == obj) { fullPath = path + '.' + Convert.ToString(componentIndex); return true; } componentIndex++; } } //Check any of the child's children matches object int childIndex = 0; foreach (Transform child in rootObject.transform) { //Only index scene gameobjects (not ones created at runtime) if (!sceneObjectsOnly || GetSceneIdentifier(obj) != -1) { string childPath = path + '[' + Convert.ToString(childIndex) + ']'; //Check this child's children if (GetChildObjectPath(childPath, child.gameObject, obj, sceneObjectsOnly, out fullPath)) return true; childIndex++; } } fullPath = string.Empty; return false; } private static Object GetChildObject(GameObject rootObject, string path) { if (string.IsNullOrEmpty(path)) return rootObject; GameObject gameObject = rootObject; int j = 0; while (j < path.Length) { int i = path.IndexOf('[', j); if (i == -1) break; j = path.IndexOf(']', i); if (j == -1) break; string childIndexStr = path.Substring(i + 1, j - i - 1); int childIndex = SafeConvertToInt(childIndexStr); if (childIndex >= 0 && childIndex < gameObject.transform.childCount) { gameObject = gameObject.transform.GetChild(childIndex).gameObject; } else { return null; } } int dotIndex = path.LastIndexOf('.'); if (dotIndex != -1) { int componentIndex = SafeConvertToInt(path.Substring(dotIndex + 1)); Component[] components = gameObject.GetComponents(); if (componentIndex >= 0 && componentIndex < components.Length) { return components[componentIndex]; } else { return null; } } else { return gameObject; } } #endregion #endregion } } } } #endif