#if UNITY_EDITOR using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEditor; namespace Normal.Realtime { public class RealtimeViewConfiguration { // RealtimeView Management private static Dictionary _rootSceneViewUUIDMap = new Dictionary(); public static void _UpdateRealtimeViewSceneViewUUID(RealtimeView realtimeView, byte[] sceneViewUUID) { _rootSceneViewUUIDMap[realtimeView] = sceneViewUUID; } public static byte[] GetSceneViewUUIDAsByteArray(SerializedProperty property) { byte[] sceneViewUUID = new byte[property.arraySize]; for (int i = 0; i < property.arraySize; i++) { sceneViewUUID[i] = (byte)property.GetArrayElementAtIndex(i).intValue; } return sceneViewUUID; } public static byte[] SetSceneViewUUIDUsingByteArray(SerializedProperty property, byte[] sceneViewUUID) { property.ClearArray(); for (int i = 0; i < sceneViewUUID.Length; i++) { property.InsertArrayElementAtIndex(i); property.GetArrayElementAtIndex(i).intValue = sceneViewUUID[i]; } return sceneViewUUID; } public static void _HierarchyDidChange() { if (Application.isPlaying) return; GameObject[] sceneRootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); Realtime[] realtimeInstances = sceneRootGameObjects.SelectMany(i => i.GetComponentsInChildren()).ToArray(); bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = false; // Automatically configure scene RealtimeViews RealtimeView[] realtimeViews = Resources.FindObjectsOfTypeAll(); foreach (RealtimeView realtimeView in realtimeViews) { if (realtimeView == null) continue; ConfigureRealtimeView(realtimeView, realtimeInstances, ref didWarnAboutRealtimeInstancesBeingTooFewOrTooMany); } } public static void _ConfigureRealtimeView(RealtimeView realtimeView) { GameObject[] sceneRootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); Realtime[] realtimeInstances = sceneRootGameObjects.SelectMany(i => i.GetComponentsInChildren()).ToArray(); bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = false; ConfigureRealtimeView(realtimeView, realtimeInstances, ref didWarnAboutRealtimeInstancesBeingTooFewOrTooMany); } private static void ConfigureRealtimeView(RealtimeView realtimeView, Realtime[] realtimeInstances, ref bool didWarnAboutRealtimeInstancesBeingTooFewOrTooMany) { SerializedObject realtimeViewSerializedObject = new SerializedObject(realtimeView); realtimeViewSerializedObject.Update(); SerializedProperty realtimeProperty = realtimeViewSerializedObject.FindProperty("_realtime"); SerializedProperty sceneViewUUIDProperty = realtimeViewSerializedObject.FindProperty("_sceneViewUUID"); SerializedProperty isRootPrefabViewProperty = realtimeViewSerializedObject.FindProperty("_isRootPrefabView"); // Realtime Instance Realtime realtime = realtimeProperty.objectReferenceValue as Realtime; bool prefab = EditorUtility.IsPersistent(realtimeView.gameObject); if (prefab) { if (realtime != null) { realtimeProperty.objectReferenceValue = null; } } else { if (realtime == null) { if (realtimeInstances.Length == 1) { realtimeProperty.objectReferenceValue = realtimeInstances[0]; } else if (!didWarnAboutRealtimeInstancesBeingTooFewOrTooMany) { if (realtimeInstances.Length == 0) { Debug.LogWarning("RealtimeView: No instances of Realtime exist in the scene. Make sure to create an instance of Realtime otherwise this scene view will not work!"); } else if (realtimeInstances.Length > 1) { Debug.LogWarning("RealtimeView: There are multiple instances of Realtime in the scene. If you plan to use this as a scene view, wire up a reference to Realtime manually under the Advanced Settings panel on the RealtimeView."); } didWarnAboutRealtimeInstancesBeingTooFewOrTooMany = true; } } } // Add realtime components RealtimeComponent[] components = realtimeView.GetComponents(); foreach (RealtimeComponent component in components) { AddComponentToViewListIfNeeded(realtimeView, component); } // Add to parent RealtimeView if this is a child view bool isRoot = AddChildViewToParentViewIfNeeded(realtimeView, realtimeViewSerializedObject); byte[] sceneViewUUID = GetSceneViewUUIDAsByteArray(sceneViewUUIDProperty); if (isRoot && !prefab) { // Root scene view // Make sure this root scene view exists in our map. If it does, verify the UUID is set properly. byte[] previouslyAssignedUUID; if (_rootSceneViewUUIDMap.TryGetValue(realtimeView, out previouslyAssignedUUID)) { // If previously assigned UUID doesn't match, reset it. This can happen when clicking Apply on a prefab. // This is because the UUID gets stored on the prefab and cleared on the scene view because it inherits the value from the prefab. // Then this script comes in and clears the UUID on the prefab, which then means the scene view will have no UUID because it's inheriting // from the prefab. We'll detect that here and set it back on the scene view. if (!previouslyAssignedUUID.SequenceEqual(sceneViewUUID)) { // Reset scene view UUID sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, previouslyAssignedUUID); } } else { // Set scene UUID if needed if (sceneViewUUID == null || sceneViewUUID.Length == 0) { sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, Guid.NewGuid().ToByteArray()); } else { // If this view doesn't exist in the UUID map, but it has a scene view UUID, it's possible it's been copy & pasted. Check the map to see if another scene view has the same UUID. If it does, reset it and log a warning. foreach (KeyValuePair viewUUIDPair in _rootSceneViewUUIDMap) { RealtimeView view = viewUUIDPair.Key; byte[] viewUUID = viewUUIDPair.Value; if (sceneViewUUID.SequenceEqual(viewUUID) && realtimeView != view && realtimeView.gameObject.scene == view.gameObject.scene) { // If we enter this block, it means there's already a realtime view with this UUID loaded, /and/ it exists in the same scene. // As far as I know, the only way for that to happen is to copy & paste a root scene realtime view. In that case, I think it's ok to reset // the UUID on the copy. And since the original is going to be the one that already exists in the map, that means this one is the copy. // For root scene views that have the same UUID as a view in another scene, we'll log an error below. That can happen when a scene is saved // as a copy, and the copy is additively loaded. In that case, we don't know which view the developer will want to keep so we log the error. Debug.LogWarning("Realtime: Found a RealtimeView in scene with a duplicate UUID. Resetting the UUID on the copy."); sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, Guid.NewGuid().ToByteArray()); break; } } } // Add to the map _rootSceneViewUUIDMap[realtimeView] = sceneViewUUID; } } else { // Not root scene view // Clear scene UUID if (sceneViewUUID == null || sceneViewUUID.Length != 0) { // Clear the UUID sceneViewUUID = SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, new byte[0]); // Remove from map _rootSceneViewUUIDMap.Remove(realtimeView); } } if (isRoot && prefab) { // Root prefab view // Set isRootPrefabView property if (!isRootPrefabViewProperty.boolValue) isRootPrefabViewProperty.boolValue = true; } else { // Not root prefab view // Clear isRootPrefabView property if (isRootPrefabViewProperty.boolValue) isRootPrefabViewProperty.boolValue = false; } realtimeViewSerializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void AddComponentToViewListIfNeeded(RealtimeView view, RealtimeComponent componentToAdd) { // Add to view's components list. SerializedObject viewSerializedObject = new SerializedObject(view); viewSerializedObject.Update(); SerializedProperty componentsProperty = viewSerializedObject.FindProperty("_components"); int numberOfComponents = componentsProperty.arraySize; // Check for component in the list int largestComponentID = 0; for (int i = 0; i < numberOfComponents; i++) { SerializedProperty componentProperty = componentsProperty.GetArrayElementAtIndex(i); SerializedProperty componentComponentIDProperty = componentProperty.FindPropertyRelative("componentID"); SerializedProperty componentComponentProperty = componentProperty.FindPropertyRelative("component"); MonoBehaviour component = componentComponentProperty.objectReferenceValue as MonoBehaviour; // If the component exists, we're done. if (component == componentToAdd) return; int componentID = componentComponentIDProperty.intValue; if (componentID > largestComponentID) largestComponentID = componentID; } // Not found. Add to list. int componentIndex = numberOfComponents; int nextAvailableComponentID = largestComponentID + 1; componentsProperty.InsertArrayElementAtIndex(componentIndex); SerializedProperty newComponentProperty = componentsProperty.GetArrayElementAtIndex(componentIndex); newComponentProperty.FindPropertyRelative("componentID").intValue = nextAvailableComponentID; newComponentProperty.FindPropertyRelative("component").objectReferenceValue = componentToAdd; newComponentProperty.FindPropertyRelative("componentIDHasBeenUsed").boolValue = true; viewSerializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static bool AddChildViewToParentViewIfNeeded(RealtimeView childView, SerializedObject childViewSerializedObject) { if (childView == null) { Debug.LogError("Attempting to add null child view to parent view. This is a bug! Bailing..."); return false; } // Recursively trace up to the parent RealtimeView parentView = FindParentRealtimeViewForTransform(childView.transform); // Remove from previous parentView and update _parentView property. SerializedProperty parentViewSerializedProperty = childViewSerializedObject.FindProperty("_parentView"); RealtimeView oldParentView = parentViewSerializedProperty.objectReferenceValue as RealtimeView; if (parentView != oldParentView) { // We're going to change the parent below. Remove the child view from the old parent view's child view list. if (oldParentView != null) RemoveChildViewFromParentViewList(oldParentView, childView); // Set parent property on child view parentViewSerializedProperty.objectReferenceValue = parentView; } // No parent found, this is a root view. if (parentView == null) return true; // Add to parent view's childViews list. SerializedObject parentViewSerializedObject = new SerializedObject(parentView); parentViewSerializedObject.Update(); SerializedProperty childViewsProperty = parentViewSerializedObject.FindProperty("_childViews"); int numberOfChildViews = childViewsProperty.arraySize; // Check for child view in the list int largestViewID = 0; for (int i = 0; i < numberOfChildViews; i++) { SerializedProperty childViewProperty = childViewsProperty.GetArrayElementAtIndex(i); SerializedProperty childViewViewIDProperty = childViewProperty.FindPropertyRelative("viewID"); SerializedProperty childViewViewProperty = childViewProperty.FindPropertyRelative("view"); SerializedProperty childViewViewToUseIfMovedBackProperty = childViewProperty.FindPropertyRelative("viewToUseIfMovedBack"); RealtimeView view = childViewViewProperty.objectReferenceValue as RealtimeView; RealtimeView viewToUseIfMovedBack = childViewViewToUseIfMovedBackProperty.objectReferenceValue as RealtimeView; // If the view exists, we're done. if (view == childView) return false; // If the view has been assigned to this property before, re-assign it and we're done. if (viewToUseIfMovedBack == childView) { childViewViewProperty.objectReferenceValue = childView; parentViewSerializedProperty.objectReferenceValue = parentView; parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo(); return false; } int viewID = childViewViewIDProperty.intValue; if (viewID > largestViewID) largestViewID = viewID; } // Not found. Add to list. int viewIndex = numberOfChildViews; int nextAvailableViewID = largestViewID + 1; childViewsProperty.InsertArrayElementAtIndex(viewIndex); SerializedProperty newChildViewProperty = childViewsProperty.GetArrayElementAtIndex(viewIndex); newChildViewProperty.FindPropertyRelative("viewID").intValue = nextAvailableViewID; newChildViewProperty.FindPropertyRelative("view").objectReferenceValue = childView; newChildViewProperty.FindPropertyRelative("viewToUseIfMovedBack").objectReferenceValue = childView; newChildViewProperty.FindPropertyRelative("viewIDHasBeenUsed").boolValue = true; parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo(); return false; } private static RealtimeView FindParentRealtimeViewForTransform(Transform realtimeViewTransform) { Transform parent = realtimeViewTransform.parent; if (parent == null) return null; RealtimeView realtimeView = parent.GetComponent(); if (realtimeView != null) return realtimeView; return FindParentRealtimeViewForTransform(parent); } private static void RemoveChildViewFromParentViewList(RealtimeView parentView, RealtimeView childView) { // Remove from parent view's childViews list. SerializedObject parentViewSerializedObject = new SerializedObject(parentView); parentViewSerializedObject.Update(); SerializedProperty childViewsProperty = parentViewSerializedObject.FindProperty("_childViews"); int numberOfChildViews = childViewsProperty.arraySize; // Check for child view in the list for (int i = 0; i < numberOfChildViews; i++) { SerializedProperty childViewProperty = childViewsProperty.GetArrayElementAtIndex(i); SerializedProperty childViewViewProperty = childViewProperty.FindPropertyRelative("view"); SerializedProperty childViewViewToUseIfMovedBackProperty = childViewProperty.FindPropertyRelative("viewToUseIfMovedBack"); RealtimeView view = childViewViewProperty.objectReferenceValue as RealtimeView; // If the view exists, clear it out. if (view == childView) { // Clear view property childViewViewProperty.objectReferenceValue = null; // Store a reference so if the view is moved back, it gets assigned to the same View ID childViewViewToUseIfMovedBackProperty.objectReferenceValue = childView; } } parentViewSerializedObject.ApplyModifiedPropertiesWithoutUndo(); } } } #endif