static void MergeScenes() { Scene activeScene = new Scene(); AmsMultiSceneSetup activeSetup = null; List<AmsMultiSceneSetup.SceneEntry> bakedScenes = new List<AmsMultiSceneSetup.SceneEntry>(); GetCommonParameters( ref activeScene, ref activeSetup, bakedScenes ); if ( bakedScenes.Count < 1 ) return; AmsDebug.Log( null, "Running AMS MergeScenes on Scene {0} ({1})", activeScene.name, activeSetup.scenePath ); foreach( var entry in bakedScenes ) { if ( !entry.scene.isLoaded ) { AmsDebug.LogError( activeSetup, "Could not merge non-loaded scene: {0}", entry.scene.name ); continue; } // Merge the cross-scene references (and keep track of the merges) var bakedSceneSetup = GameObjectEx.GetSceneSingleton<AmsMultiSceneSetup>( entry.scene.scene, false ); if ( bakedSceneSetup ) AmsCrossSceneReferences.EditorBuildPipelineMergeScene( bakedSceneSetup, activeSetup ); AmsDebug.Log( null, "Running Unity MergeScenes for {0} into {1}", entry.scene.name, activeScene.name ); EditorSceneManager.MergeScenes( entry.scene.scene, activeScene ); } } // MergeScenes
static void RestoreCrossSceneReferences() { Scene activeScene = new Scene(); AmsMultiSceneSetup activeSetup = null; List<AmsMultiSceneSetup.SceneEntry> bakedScenes = new List<AmsMultiSceneSetup.SceneEntry>(); GetCommonParameters( ref activeScene, ref activeSetup, bakedScenes ); if ( bakedScenes.Count < 1 ) return; AmsDebug.Log( null, "Running RestoreCrossSceneReferences on Scene {0}", activeScene.name ); // Do the merge (bake) var targetCrossRefs = AmsCrossSceneReferences.GetSceneSingleton( activeScene, false ); if ( targetCrossRefs ) targetCrossRefs.ResolvePendingCrossSceneReferences(); foreach( var entry in bakedScenes ) { if ( !entry.scene.isLoaded ) { AmsDebug.LogError( activeSetup, "Could not restore cross-scene references for non-loaded scene: {0}", entry.scene.name ); continue; } var sourceCrossRefs = AmsCrossSceneReferences.GetSceneSingleton( entry.scene.scene, false ); if ( sourceCrossRefs ) sourceCrossRefs.ResolvePendingCrossSceneReferences(); } }
static void MergeScenes() { Scene activeScene = new Scene(); AmsMultiSceneSetup activeSetup = null; List<AmsMultiSceneSetup.SceneEntry> bakedScenes = new List<AmsMultiSceneSetup.SceneEntry>(); GetCommonParameters( ref activeScene, ref activeSetup, bakedScenes ); if ( bakedScenes.Count < 1 ) return; AmsDebug.Log( null, "Running MergeScenes on Scene {0}", activeScene.name ); foreach( var entry in bakedScenes ) { if ( !entry.scene.isLoaded ) { AmsDebug.LogError( activeSetup, "Could not merge non-loaded scene: {0}", entry.scene.name ); continue; } var sourceCrossRefs = AmsCrossSceneReferences.GetSceneSingleton( entry.scene.scene, false ); if ( sourceCrossRefs ) GameObject.DestroyImmediate( sourceCrossRefs.gameObject, false ); AmsDebug.Log( null, "Merging {0} into {1}", entry.scene.name, activeScene.name ); EditorSceneManager.MergeScenes( entry.scene.scene, activeScene ); } } // MergeScenes
private static void EditorApplication_playModeStateChanged() { bool isExitingEditMode = !EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode; #endif #if UNITY_5_6_OR_NEWER if ( EditorUtility.scriptCompilationFailed ) { AmsDebug.Log( null, "Skipping cross-scene references due to compilation errors" ); return; } #endif if ( isExitingEditMode ) { List<Scene> allScenes = new List<Scene>( EditorSceneManager.sceneCount ); for (int i = 0 ; i < EditorSceneManager.sceneCount ; ++i) { var scene = EditorSceneManager.GetSceneAt(i); if ( scene.IsValid() && scene.isLoaded ) allScenes.Add( scene ); } AmsDebug.Log( null, "Handling Cross-Scene Referencing for Playmode" ); AmsSaveProcessor.HandleCrossSceneReferences( allScenes ); } }
public static void HandleCrossSceneReferences( IList<Scene> scenes ) { // If we don't allow cross-scene references, then early return. var crossSceneReferenceBehaviour = AmsPreferences.CrossSceneReferencing; bool bSkipCrossSceneReferences = (crossSceneReferenceBehaviour == AmsPreferences.CrossSceneReferenceHandling.UnityDefault); bool bSaveCrossSceneReferences = (crossSceneReferenceBehaviour == AmsPreferences.CrossSceneReferenceHandling.Save); if ( bSkipCrossSceneReferences || scenes.Count < 1 ) return; // We need to create an AmsMultiSceneSetup singleton in every scene. This is how we keep track of Awake scenes and // it also allows us to use cross-scene references. foreach( var scene in scenes ) { if ( !scene.isLoaded ) continue; // Reset all of the cross-scene references for loaded scenes. var crossSceneRefBehaviour = AmsCrossSceneReferences.GetSceneSingleton( scene, true ); for (int i = 0 ; i < EditorSceneManager.sceneCount ; ++i) { var otherScene = EditorSceneManager.GetSceneAt(i); if ( otherScene.isLoaded ) crossSceneRefBehaviour.ResetCrossSceneReferences( otherScene ); } } var xSceneRefs = AmsCrossSceneReferenceProcessor.GetCrossSceneReferencesForScenes( scenes ); if ( bSaveCrossSceneReferences && xSceneRefs.Count > 0 ) { AmsDebug.LogWarning( null, "Ams Plugin: Found {0} Cross-Scene References. Saving them.", xSceneRefs.Count ); AmsCrossSceneReferenceProcessor.SaveCrossSceneReferences( xSceneRefs ); } // Zero-out these cross-scene references so we can save without pulling in those assets. for(int i = 0 ; i < xSceneRefs.Count ; ++i) { var xRef = xSceneRefs[i]; int refIdToRestore = xRef.fromProperty.objectReferenceInstanceIDValue; if ( !bSaveCrossSceneReferences ) Debug.LogWarningFormat( "Cross-Scene Reference {0} will become null", xRef ); // Set it to null. xRef.fromProperty.objectReferenceInstanceIDValue = 0; xRef.fromProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // Restore if we're not about to enter play mode if ( !EditorApplication.isPlayingOrWillChangePlaymode ) { EditorApplication.delayCall += () => { AmsDebug.Log( null, "Restoring Cross-Scene Ref (Post-Save): {0}", xRef ); xRef.fromProperty.objectReferenceInstanceIDValue = refIdToRestore; xRef.fromProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); }; } } }
static void PlayableDirector_SceneBindings(RuntimeCrossSceneReference xRef) { var data = xRef.data; UnityEngine.Playables.PlayableDirector playableDirector = xRef.fromObject as UnityEngine.Playables.PlayableDirector; for (int i = 0; i < data.Count; i += 2) { Object key = data[i].@object; AmsDebug.Log(xRef.fromObject, "Restoring PlayableDirector Scene Binding {0} = {1}", key, xRef.toObject); playableDirector.SetGenericBinding(key, xRef.toObject); } }
/// <summary> /// Save all of the passed-in cross-scene references. The entries in the passed-in list will be removed as they are properly accounted for. /// </summary> /// <param name="editorCrossSceneRefs"></param> public static void SaveCrossSceneReferences( List<EditorCrossSceneReference> editorCrossSceneRefs ) { // Save all of the cross-scene references, removing them from our input list as we receive them for( int i = editorCrossSceneRefs.Count-1 ; i >= 0 ; --i) { var xRef = editorCrossSceneRefs[i]; AmsDebug.Log( null, "Saving Cross-Scene Reference: {0}", xRef ); try { RuntimeCrossSceneReference serializedReference = xRef.ToSerializable(); try { // Save the object var initialObject = xRef.fromProperty.objectReferenceValue; // Resolve it (this can throw exceptions) AmsCrossSceneReferenceResolver.Resolve( serializedReference ); #if UNITY_5_6_OR_NEWER xRef.fromProperty.serializedObject.UpdateIfRequiredOrScript(); #else xRef.fromProperty.serializedObject.UpdateIfDirtyOrScript(); #endif // Check to make sure it resolved properly if ( initialObject && xRef.fromProperty.objectReferenceValue != initialObject ) throw new ResolveException( string.Format("Resolve should have pointed to {0} ({1}) but instead resolved to {2} ({3})", initialObject ? initialObject.ToString() : "(null)", initialObject ? initialObject.GetInstanceID() : 0, xRef.fromProperty.objectReferenceValue, xRef.fromProperty.objectReferenceInstanceIDValue) ); } catch ( System.Exception ex ) { AmsDebug.LogError( xRef.fromObject, "Could not perform a runtime resolve on cross-scene reference {0}.\nReason: {1}. Please review Documentation.", serializedReference, ex.Message ); continue; } // Record the cross-scene reference var crossSceneRefBehaviour = AmsCrossSceneReferences.GetSceneSingleton( xRef.fromScene, true ); crossSceneRefBehaviour.AddReference( serializedReference ); // Add an updated reference map value if ( _referenceMap != null ) _referenceMap.Add( new KeyValuePair<SerializedProperty, Object>(xRef.fromProperty, xRef.fromProperty.objectReferenceValue) ); } catch ( UnityException ex ) { Debug.LogException( ex ); } } }
static void PlayableDirector_ExposedReferences(RuntimeCrossSceneReference xRef) { var data = xRef.data; UnityEngine.Playables.PlayableDirector playableDirector = xRef.fromObject as UnityEngine.Playables.PlayableDirector; for (int i = 0; i < data.Count; i += 2) { string key = data[i].@string; AmsDebug.Log(xRef.fromObject, "Restoring PlayableDirector Exposed Binding {0} = {1}", key, xRef.toObject); playableDirector.ClearReferenceValue(key); playableDirector.SetReferenceValue(key, xRef.toObject); } }
/// <summary> /// Attempt to handle a cross-scene reference. /// </summary> static bool HandleCrossSceneReference( RuntimeCrossSceneReference xRef ) { MonoBehaviour cinemachineBehaviour = xRef.fromObject as MonoBehaviour; if ( !cinemachineBehaviour || !cinemachineBehaviour.isActiveAndEnabled ) return false; if ( !cinemachineBehaviour.GetType().Namespace.StartsWith( "Cinemachine" ) ) return false; AmsDebug.LogWarning( xRef.fromObject, "xSceneRef on Cinemachine Behaviour: {0}. Disabling/Enabling to ensure pipeline is up to date.", xRef ); cinemachineBehaviour.enabled = false; cinemachineBehaviour.enabled = true; return false; }
/// <summary> /// Attempt to handle a cross-scene reference. /// </summary> static bool HandleCrossSceneReference(RuntimeCrossSceneReference xRef) { if (!(xRef.fromObject is UnityEngine.Playables.PlayableDirector)) { return(false); } bool isDirty = false; string sourceField = xRef.sourceField; if (sourceField.StartsWith("m_SceneBindings")) { PlayableDirector_SceneBindings(xRef); isDirty = true; } if (sourceField.StartsWith("m_ExposedReferences")) { PlayableDirector_ExposedReferences(xRef); isDirty = true; } if (isDirty) { UnityEngine.Playables.PlayableDirector playableDirector = xRef.fromObject as UnityEngine.Playables.PlayableDirector; if (playableDirector) { #if UNITY_2017_3_OR_NEWER if (playableDirector.state == UnityEngine.Playables.PlayState.Playing) { AmsDebug.LogWarning(playableDirector, "To prevent issues, delay the PlayableDirector '{0}' until after cross-scene references are loaded. Cross-Scene Reference: {1}", playableDirector, xRef); playableDirector.RebuildGraph(); } #else if (playableDirector.gameObject.activeSelf) { AmsDebug.LogWarning(playableDirector, "Upgrade to Unity 2017.3 for proper Playables support. Hack work-around for 2017.1 and 2017.2: Disable/ReEnable the GameObject"); playableDirector.gameObject.SetActive(false); playableDirector.gameObject.SetActive(true); } #endif } } return(isDirty); }
/// <summary> /// Given a property, let's return a runtime serializeable field string. /// </summary> /// <param name="property">The property to capture the field from</param> /// <returns>The field string which can be parsed at runtime</returns> private string ToRuntimeSerializableField(SerializedProperty property) { const string ARRAY_INDICATOR = "@ArrayIndex["; int arrayIndicatorLength = ARRAY_INDICATOR.Length; // Give us an easy sentinel value to scan for in case of arrays string parseablePropertyPath = property.propertyPath.Replace(".Array.data[", "." + ARRAY_INDICATOR); var splitPaths = parseablePropertyPath.Split('.'); System.Text.StringBuilder sb = new System.Text.StringBuilder(); for (int i = 0; i < splitPaths.Length; ++i) { string pathPiece = splitPaths[i]; bool bIsArrayIndex = pathPiece.StartsWith(ARRAY_INDICATOR); if (!bIsArrayIndex) { // Append the . if we're a nested object if (i > 0) { sb.Append('.'); } sb.Append(pathPiece); } else { // It's an array, so we're doing the index portion of @ArrayIndex[index] string indexString = pathPiece.Substring(arrayIndicatorLength, pathPiece.Length - arrayIndicatorLength - 1); int arrayIndex = 0; if (int.TryParse(indexString, out arrayIndex)) { // Arrays are of the form fieldName,arrayIndex sb.Append(','); sb.Append(arrayIndex); } else { AmsDebug.LogError(null, "Could not parse array index for property path {0}", property.propertyPath); } } } return(sb.ToString()); }
private static void SaveCrossSceneReferencesBeforePlayInEditMode() { EditorApplication.playmodeStateChanged += () => { if ( !EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode ) { List<Scene> allScenes = new List<Scene>( EditorSceneManager.sceneCount ); for (int i = 0 ; i < EditorSceneManager.sceneCount ; ++i) { var scene = EditorSceneManager.GetSceneAt(i); if ( scene.IsValid() && scene.isLoaded ) allScenes.Add( scene ); } AmsDebug.Log( null, "Handling Cross-Scene Referencing for Playmode" ); AmsSaveProcessor.HandleCrossSceneReferences( allScenes ); } }; }
internal static void LoadScenesForMerging() { Scene activeScene = new Scene(); AmsMultiSceneSetup activeSetup = null; List<AmsMultiSceneSetup.SceneEntry> bakedScenes = new List<AmsMultiSceneSetup.SceneEntry>(); GetCommonParameters( ref activeScene, ref activeSetup, bakedScenes ); if ( bakedScenes.Count < 1 ) return; AmsDebug.Log( null, "Running LoadScenesForBaking on Scene {0}", activeScene.name ); // Now load all of the scenes foreach( var entry in bakedScenes ) { var realScene = entry.scene.scene; if ( !realScene.IsValid() ) { // This is good. This means it's not loaded yet. realScene = EditorSceneManager.OpenScene( entry.scene.editorPath, OpenSceneMode.Additive ); if ( !realScene.IsValid() ) { AmsDebug.LogError( activeSetup, "BakeScene: Scene {0} ({1}) referenced from Multi-Scene Setup in {2} is invalid.", entry.scene.editorPath, entry.scene.name, activeScene.name ); continue; } } // Let's catch this... if ( !realScene.isLoaded ) { realScene = EditorSceneManager.OpenScene( realScene.path, OpenSceneMode.Additive ); // if we're still not loaded, we're probably in trouble. if ( !realScene.isLoaded ) { AmsDebug.LogError( activeSetup, "BakeScene: Scene {0} ({1}) referenced from Multi-Scene Setup in {2} could not load.", entry.scene.editorPath, entry.scene.name, activeScene.name ); continue; } } } }
/// <summary> /// This will update the map of SerializedProperties to Object ID /// </summary> private static void UpdateReferencesMap() { if ( _referenceMap != null ) return; double startTime = EditorApplication.timeSinceStartup; // Get all of the valid MonoBehaviours or ScriptableObjects var allMonoBehaviourObjs = Resources.FindObjectsOfTypeAll<MonoBehaviour>().Where( x => !EditorUtility.IsPersistent(x) ).Cast<Object>(); var allScriptableObjs = Resources.FindObjectsOfTypeAll<ScriptableObject>().Where( x => !EditorUtility.IsPersistent(x) && !typeof(EditorWindow).IsAssignableFrom(x.GetType()) ).Cast<Object>(); // We assume we're up to date for a single frame... EditorApplication.delayCall += () => { _referenceMap = null; }; _referenceMap = new List<KeyValuePair<SerializedProperty, Object>>( allMonoBehaviourObjs.Count() + allScriptableObjs.Count() ); // Figure out what they're referencing PopulateReferenceMap( _referenceMap, allMonoBehaviourObjs ); PopulateReferenceMap( _referenceMap, allScriptableObjs ); // Now we have all of the custom data processors. Note that we don't want to run over-top of what we've already processed, so let's exclude all of those... foreach ( var customType in _customCrossSceneReferenceDataProcessors.Keys ) { // Handle the cases where we've already added these objects to the list... if ( typeof(MonoBehaviour).IsAssignableFrom(customType) ) continue; if ( typeof(ScriptableObject).IsAssignableFrom(customType) ) continue; // Now grab all of the objects and add them to our reference map var sceneCustomObjects = Resources.FindObjectsOfTypeAll( customType ).Where( x => !EditorUtility.IsPersistent(x) ); PopulateReferenceMap( _referenceMap, sceneCustomObjects ); } AmsDebug.LogPerf( null, "Cross-Scene Reference Map Update: {0}", (EditorApplication.timeSinceStartup - startTime) ); }
/// <summary> /// This will update the map of SerializedProperties to Object ID /// </summary> private static void UpdateReferencesMap() { if ( _referenceMap != null ) return; double startTime = EditorApplication.timeSinceStartup; // Get all of the valid MonoBehaviours or ScriptableObjects var allMonoBehaviourObjs = Resources.FindObjectsOfTypeAll<MonoBehaviour>().Where( x => !EditorUtility.IsPersistent(x) ).Cast<Object>(); var allScriptableObjs = Resources.FindObjectsOfTypeAll<ScriptableObject>().Where( x => !EditorUtility.IsPersistent(x) && !typeof(EditorWindow).IsAssignableFrom(x.GetType()) ).Cast<Object>(); // We assume we're up to date for a single frame... EditorApplication.delayCall += () => { _referenceMap = null; }; _referenceMap = new List<KeyValuePair<SerializedProperty, Object>>( allMonoBehaviourObjs.Count() + allScriptableObjs.Count() ); // Figure out what they're referencing PopulateReferenceMap( _referenceMap, allMonoBehaviourObjs ); PopulateReferenceMap( _referenceMap, allScriptableObjs ); AmsDebug.LogPerf( null, "Cross-Scene Reference Map Update: {0}", (EditorApplication.timeSinceStartup - startTime) ); }