private void ConditionalResolveReferences(List <RuntimeCrossSceneReference> references) { for (int i = references.Count - 1; i >= 0; --i) { var xRef = references[i]; try { var fromScene = xRef.fromScene; var toScene = xRef.toScene; bool bIsReady = fromScene.isLoaded && toScene.isLoaded; AmsDebug.Log(this, "{0}.ConditionalResolveReferences() Scene: {1}. xRef: {2}. isReady: {3}. fromSceneLoaded: {4}. toSceneLoaded: {5}.", GetType().Name, gameObject.scene.name, xRef, bIsReady, fromScene.isLoaded, toScene.isLoaded); if (bIsReady) { // Remove it from our list (assuming it goes through) references.RemoveAt(i); AmsDebug.Log(this, "Restoring Cross-Scene Reference {0}", xRef); xRef.Resolve(); // Notify any listeners if (CrossSceneReferenceRestored != null) { CrossSceneReferenceRestored(xRef); } } } catch (System.Exception ex) { Debug.LogException(ex, this); } } }
/// <summary> /// Start is executed just before the first Update (a frame after Awake/OnEnable). /// We execute the Scene loading here because Unity has issues loading scenes during the initial Awake/OnEnable calls. /// </summary> IEnumerator Start() { if (Application.isPlaying) { yield return(new WaitForEndOfFrame()); // This will always wait for at least one frame before control is returned. } AmsDebug.Log(this, "{0}.Start() Scene: {1}. Frame: {2}", GetType().Name, gameObject.scene.name, Time.frameCount); // Second chance at loading scenes if (_isMainScene) { LoadSceneSetup(); } #if UNITY_EDITOR // Make sure we update the settings every time we unload/load a Scene // This is strategically placed after the LoadSceneSetup(). if (!EditorApplication.isPlaying) { EditorApplication.hierarchyChanged -= OnBeforeSerialize; EditorApplication.hierarchyChanged += OnBeforeSerialize; } #endif }
/// <summary> /// Whenever another scene is loaded, we have another shot at resolving more cross-scene references /// </summary> /// <param name="sceneSetup">The AmsMultiSceneSetup that was loaded</param> private void HandleNewSceneLoaded(AmsMultiSceneSetup sceneSetup) { var loadedScene = sceneSetup.gameObject.scene; if (!loadedScene.isLoaded) { Debug.LogErrorFormat(this, "{0} Received HandleNewSceneLoaded from scene {1} which isn't considered loaded. The scene MUST be considered loaded by this point", GetType().Name, loadedScene.name); } // Restore any references to this newly loaded scene for (int i = 0; i < _crossSceneReferences.Count; ++i) { var xRef = _crossSceneReferences[i]; if (!xRef.fromObject) { AmsDebug.LogWarning(this, "xRef Index {0} had Null source (probably stale), Consider removing (via right-click on entry)", i); continue; } if (!_referencesToResolve.Contains(xRef) && xRef.toScene.scene == loadedScene) { _referencesToResolve.Add(xRef); } } if (_referencesToResolve.Count > 0) { AmsDebug.Log(this, "Scene {0} Loaded. {1} Cross-Scene References (in total) from Cross-Scene Manager in {2} are queued for resolve.", loadedScene.name, _referencesToResolve.Count, gameObject.scene.name); ConditionalResolveReferences(_referencesToResolve); } }
/// <summary> /// Start is executed just before the first Update (a frame after Awake/OnEnable). /// We execute the Scene loading here because Unity has issues loading scenes during the initial Awake/OnEnable calls. /// </summary> void Start() { AmsDebug.Log(this, "{0}.Start() Scene: {1}. Frame: {2}", GetType().Name, gameObject.scene.name, Time.frameCount); // Notify any listeners (like the cross-scene referencer) if (OnStart != null) { OnStart(this); } // Second chance at loading scenes if (_isMainScene) { LoadSceneSetup(); } #if UNITY_EDITOR // Make sure we update the settings every time we unload/load a Scene // This is strategically placed after the LoadSceneSetup(). if (!EditorApplication.isPlaying) { #if UNITY_2018 EditorApplication.hierarchyChanged -= OnHierarchyChanged; EditorApplication.hierarchyChanged += OnHierarchyChanged; #else EditorApplication.hierarchyWindowChanged -= OnHierarchyChanged; EditorApplication.hierarchyWindowChanged += OnHierarchyChanged; #endif } #endif }
/// <summary> /// Awake can be used to tell anyone that a Scene has just been loaded. /// Due to a bug in PostProcessScene, this is the first thing to occur in a loaded scene. /// </summary> void Awake() { AmsDebug.Log(this, "{0}.Awake() (Scene {1}). IsLoaded: {2}. Frame: {3}", GetType().Name, gameObject.scene.name, gameObject.scene.isLoaded, Time.frameCount); #if UNITY_EDITOR if (!BuildPipeline.isBuildingPlayer) { _thisScenePath = gameObject.scene.path; } #endif // Notify any listeners we're now awake if (OnAwake != null) { OnAwake(this); } if (_isMainScene) { if (!Application.isEditor || gameObject.scene.isLoaded || Time.frameCount > 1) { LoadSceneSetup(); } } }
void Awake() { AmsDebug.Log(this, "{0}.Awake() Scene: {1}. Path: {2}. Frame: {3}. Root Count: {4}", GetType().Name, gameObject.scene.name, gameObject.scene.path, Time.frameCount, gameObject.scene.rootCount); _referencesToResolve.Clear(); _referencesToResolve.AddRange(_crossSceneReferences); StartCoroutine(CoWaitForSceneLoadThenResolveReferences(gameObject.scene)); }
/// <summary> /// Start is executed just before the first Update (a frame after Awake/OnEnable). /// We execute the Scene loading here because Unity has issues loading scenes during the initial Awake/OnEnable calls. /// </summary> void Start() { AmsDebug.Log(this, "{0}.Start() Scene: {1}. Frame: {2}", GetType().Name, gameObject.scene.name, Time.frameCount); // Notify any listeners (like the cross-scene referencer) if (OnStart != null) { OnStart(this); } // Second chance at loading scenes LoadSceneSetup(); }
public bool IsSameSource( RuntimeCrossSceneReference other ) { try { return (this.fromObject == other.fromObject) && (this._sourceField == other._sourceField); } catch ( System.Exception ex ) { AmsDebug.Log( null, "IsSameSource: Could not compare: {0} and {1}: {2}", ToString(), other, ex ); } return false; }
private void MergeScene(SceneEntry entry) { var scene = entry.scene.scene; // Make sure there is only ever one AmsMultiSceneSetup in a given scene var sourceSetup = GameObjectEx.GetSceneSingleton <AmsMultiSceneSetup>(scene, false); if (sourceSetup) { GameObject.Destroy(sourceSetup.gameObject); } AmsDebug.Log(this, "Merging {0} into {1}", scene.path, gameObject.scene.path); SceneManager.MergeScenes(scene, gameObject.scene); }
void Awake() { AmsDebug.Log(this, "{0}.Awake() Scene: {1}. IsLoaded: {2}. Path: {3}. Frame: {4}. Root Count: {5}", GetType().Name, gameObject.scene.name, gameObject.scene.isLoaded, gameObject.scene.path, Time.frameCount, gameObject.scene.rootCount); // Upgrade us (we upgraded the Cross-Scene Reference data) // This may freak out users on their first run since it will look like their cross-scene references are gone // In reality, they are just in the scene, ready to be re-created on save if (_version < CurrentSerializedVersion) { int numCrossSceneRefs = _crossSceneReferences.Count; ConditionalResolveReferences(_crossSceneReferences); if (numCrossSceneRefs != _crossSceneReferences.Count) { AmsDebug.LogWarning(this, "{0} was upgraded. {1} cross-scene references will be re-created on next save.", name, numCrossSceneRefs - _crossSceneReferences.Count); } else { AmsDebug.LogWarning(this, "{0} needs upgrading. Please resave {1}", name, gameObject.scene.name); } #if UNITY_EDITOR if (!Application.isPlaying) { GameObject gameObject = this.gameObject; EditorApplication.delayCall += () => { if (gameObject) { EditorSceneManager.MarkSceneDirty(gameObject.scene); } }; } #endif } // Make sure we keep track of all of the merged scenes AmsSceneReference thisScene = new AmsSceneReference(gameObject.scene); foreach (var prevScene in _mergedScenes) { _activeMergedScenes.Add(prevScene, thisScene); } // We need to queue our cross-scene references super early in case we get merged. _referencesToResolve.Clear(); _referencesToResolve.AddRange(_crossSceneReferences); // We should be able to merge our cross-scene references to any other loaded scene here. ResolvePendingCrossSceneReferences(); }
void Start() { AmsDebug.Log(this, "{0}.Start() Scene: {1}. IsLoaded: {2}. Path: {3}. Frame: {4}. Root Count: {5}", GetType().Name, gameObject.scene.name, gameObject.scene.isLoaded, gameObject.scene.path, Time.frameCount, gameObject.scene.rootCount); // A build might have just been performed, in that case clean-up the leftovers. PerformPostBuildCleanup(); // For some reason in Awake(), the scene we belong to isn't considered "loaded"?! We've tried to work around that, but if it failed, try again here. ResolvePendingCrossSceneReferences(); // Register to these callbacks only once AmsMultiSceneSetup.OnStart -= HandleNewSceneLoaded; AmsMultiSceneSetup.OnStart += HandleNewSceneLoaded; AmsMultiSceneSetup.OnDestroyed -= HandleSceneDestroyed; AmsMultiSceneSetup.OnDestroyed += HandleSceneDestroyed; }
void Start() { AmsDebug.Log(this, "{0}.Start() Scene: {1}. Path: {2}. Frame: {3}. Root Count: {4}", GetType().Name, gameObject.scene.name, gameObject.scene.path, Time.frameCount, gameObject.scene.rootCount); // A build might have just been performed, in that case clean-up the leftovers. PerformPostBuildCleanup(); if (Application.isPlaying) { // Give us a second chance (helps initial load of scene) // For some reason in Awake(), the scene we belong to isn't considered "loaded"?! // Unfortunately we can get a Start() before CoWaitForSceneLoadThenResolveReferences finishes... so we need to nuke it if it's still running. StopAllCoroutines(); ResolvePendingCrossSceneReferences(); } }
/// <summary> /// Loads a particular Scene Entry in the Editor /// </summary> /// <param name="entry">The entry to load</param> private void LoadEntryInEditor(SceneEntry entry) { // Bad time to do this. if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying) { return; } // We can't do this if (string.IsNullOrEmpty(entry.scene.editorPath) || entry.scene.editorPath == gameObject.scene.path) { return; } bool bShouldLoad = entry.loadInEditor && AmsPreferences.AllowAutoload; var scene = entry.scene.scene; try { if (!scene.IsValid()) { if (bShouldLoad) { AmsDebug.Log(this, "Scene {0} is loading Scene {1} in Editor", gameObject.scene.name, entry.scene.name); EditorSceneManager.OpenScene(entry.scene.editorPath, OpenSceneMode.Additive); } else { AmsDebug.Log(this, "Scene {0} is opening Scene {1} (without loading) in Editor", gameObject.scene.name, entry.scene.name); EditorSceneManager.OpenScene(entry.scene.editorPath, OpenSceneMode.AdditiveWithoutLoading); } } else if (bShouldLoad != scene.isLoaded) { if (bShouldLoad && !scene.isLoaded) { AmsDebug.Log(this, "Scene {0} is loading existing Scene {1} in Editor", gameObject.scene.name, entry.scene.name); EditorSceneManager.OpenScene(entry.scene.editorPath, OpenSceneMode.Additive); } else { AmsDebug.Log(this, "Scene {0} is closing Scene {1} in Editor", gameObject.scene.name, entry.scene.name); EditorSceneManager.CloseScene(scene, false); } } } catch (System.Exception ex) { Debug.LogException(ex, this); } }
/// <summary> /// This is called during the build pipeline to ensure a proper merge from one scene into another, taking into account cross-scene references /// </summary> /// <param name="sourceSceneSetup">The scene we're merging from</param> /// <param name="destSceneSetup">The scene we're merging to</param> public static void EditorBuildPipelineMergeScene(AmsMultiSceneSetup sourceSceneSetup, AmsMultiSceneSetup destSceneSetup) { // This is happening during the build system, so we're going to end up with a scene name of 0.backup // So we need to get the actual path from the AmsMultiSceneSetup object and clobber it. var amsFromSceneRef = new AmsSceneReference(sourceSceneSetup.gameObject.scene); amsFromSceneRef.editorPath = sourceSceneSetup.scenePath; var amsIntoSceneRef = new AmsSceneReference(destSceneSetup.gameObject.scene); amsIntoSceneRef.editorPath = destSceneSetup.scenePath; // Now get the cross-scene references from both scenes to merge them var srcCrossSceneRefs = GetSceneSingleton(sourceSceneSetup.gameObject.scene, false); if (!srcCrossSceneRefs) { return; } var destCrossSceneRefs = GetSceneSingleton(destSceneSetup.gameObject.scene, true); for (int i = 0; i < srcCrossSceneRefs._crossSceneReferences.Count; ++i) { var xRef = srcCrossSceneRefs._crossSceneReferences[i]; if (!srcCrossSceneRefs._referencesToResolve.Contains(xRef)) { AmsDebug.Log(srcCrossSceneRefs, "Already resolved xRef {0}. No need to merge it.", xRef); continue; } AmsDebug.Log(destSceneSetup, "Merging {0} into Scene {1}", xRef, amsIntoSceneRef.editorPath); xRef.DEPRECATED_fromScene = amsIntoSceneRef; destCrossSceneRefs.AddReference(xRef); } // Mark this as a merged scene in the destination, so when we look-up cross-scene references we're aware. destCrossSceneRefs._mergedScenes.Add(amsFromSceneRef); // Destroy this object after the merge is complete (we don't want it merged into the scene) GameObject.DestroyImmediate(srcCrossSceneRefs.gameObject, false); }
/// <summary> /// Whenever another scene is loaded, we have another shot at resolving more cross-scene references /// </summary> /// <param name="sceneSetup">The AmsMultiSceneSetup that was loaded</param> private void HandleNewSceneLoaded(AmsMultiSceneSetup sceneSetup) { var loadedScene = sceneSetup.gameObject.scene; if (!Application.isPlaying) { // Restore any references to this newly loaded scene foreach (var xRef in _crossSceneReferences) { if (!_referencesToResolve.Contains(xRef) && xRef.toScene.scene == loadedScene) { _referencesToResolve.Add(xRef); } } } if (_referencesToResolve.Count > 0) { AmsDebug.Log(this, "Scene {0} Loaded. {1} Cross-Scene References are queued for resolve.", loadedScene.name, _referencesToResolve.Count); StartCoroutine(CoWaitForSceneLoadThenResolveReferences(loadedScene)); } }
/// <summary> /// Whenever another scene is loaded, we have another shot at resolving more cross-scene references /// </summary> /// <param name="sceneSetup">The AmsMultiSceneSetup that was loaded</param> private void HandleNewSceneLoaded(AmsMultiSceneSetup sceneSetup) { var loadedScene = sceneSetup.gameObject.scene; if (!loadedScene.isLoaded) { Debug.LogErrorFormat(this, "{0} Received HandleNewSceneLoaded from scene {1} which isn't considered loaded. The scene MUST be considered loaded by this point", GetType().Name, loadedScene.name); } // Restore any references to this newly loaded scene foreach (var xRef in _crossSceneReferences) { if (!_referencesToResolve.Contains(xRef) && xRef.toScene.scene == loadedScene) { _referencesToResolve.Add(xRef); } } if (_referencesToResolve.Count > 0) { AmsDebug.Log(this, "Scene {0} Loaded. {1} Cross-Scene References (in total) from Cross-Scene Manager in {2} are queued for resolve.", loadedScene.name, _referencesToResolve.Count, gameObject.scene.name); ConditionalResolveReferences(_referencesToResolve); } }
private void ConditionalResolveReferences(List <RuntimeCrossSceneReference> references) { var fromScene = gameObject.scene; for (int i = references.Count - 1; i >= 0; --i) { var xRef = references[i]; if (!xRef.fromObject) { AmsDebug.LogWarning(this, "Missing Source Object for xRef at (possible index) {0}: {1}", i, xRef); continue; } try { // See if it's a reference to a merged scene... if so we need to redirect var toScene = xRef.toScene; if (!toScene.IsValid()) { AmsSceneReference mergedSceneRedirect; if (_activeMergedScenes.TryGetValue(toScene, out mergedSceneRedirect)) { AmsDebug.Log(this, "Redirecting cross scene reference {0} from original target scene {1} to scene {2}", xRef, toScene.name, mergedSceneRedirect.name); toScene = mergedSceneRedirect; xRef.toScene = mergedSceneRedirect; } } // Debug information AmsDebug.Log(this, "{0}.ConditionalResolveReferences() Scene: {1}. xRef: {2}. fromSceneLoaded: {3}. toSceneLoaded: {4}.", GetType().Name, fromScene.name, xRef, fromScene.isLoaded, toScene.isLoaded); if (toScene.isLoaded) { // Remove it from our list (assuming it goes through) references.RemoveAt(i); AmsDebug.Log(this, "Restoring Cross-Scene Reference {0}", xRef); AmsCrossSceneReferenceResolver.Resolve(xRef); // Notify any listeners if (CrossSceneReferenceRestored != null) { CrossSceneReferenceRestored(xRef); } } } catch (ResolveException ex) { string message = ex.Message; // Make it easier for us to find where the cross-scene reference is Object context = xRef.fromObject; if (!context) { context = this; } Debug.LogErrorFormat(context, "{0} in {1}: {2}", GetType().Name, gameObject.scene.name, message); } catch (System.Exception ex) { Debug.LogException(ex, this); } } }
/// <summary> /// Load a particular Scene Entry /// </summary> /// <param name="entry">The Entry to load</param> private void LoadEntryAtRuntime(SceneEntry entry) { // Don't load if (entry.loadMethod == LoadMethod.DontLoad) { return; } // Already loaded, try editor first var existingScene = SceneManager.GetSceneByPath(entry.scene.editorPath); // Try runtime path if (!existingScene.IsValid()) { existingScene = SceneManager.GetSceneByPath(entry.scene.runtimePath); } #if UNITY_EDITOR // Could be we just created the scene because it's baked if (!existingScene.IsValid()) { existingScene = SceneManager.GetSceneByName(entry.scene.runtimePath); } if (Application.isEditor && entry.loadMethod == LoadMethod.Baked) { // If we've already processed this, return early if (_bakedScenesLoading.Contains(entry) || _bakedScenesMerged.Contains(entry)) { return; } // We're loading this entry, don't allow this to be re-entrant _bakedScenesLoading.Add(entry); if (!existingScene.IsValid()) { // This allows us to load the level even in playmode EditorApplication.LoadLevelAdditiveInPlayMode(entry.scene.editorPath); } // Loading a scene can take multiple frames so we have to wait. // Baking scenes can only take place when they're all loaded due to cross-scene referencing if (_waitingToBake != null) { StopCoroutine(_waitingToBake); } _waitingToBake = StartCoroutine(CoWaitAndBake()); return; } #endif // If it's already loaded, return early if (existingScene.IsValid()) { return; } if (entry.loadMethod == LoadMethod.AdditiveAsync) { AmsDebug.Log(this, "Loading {0} Asynchronously from {1}", entry.scene.name, gameObject.scene.name); entry.asyncOp = SceneManager.LoadSceneAsync(entry.scene.runtimePath, LoadSceneMode.Additive); return; } if (entry.loadMethod == LoadMethod.Additive) { AmsDebug.Log(this, "Loading {0} from {1}", entry.scene.name, gameObject.scene.name); SceneManager.LoadScene(entry.scene.runtimePath, LoadSceneMode.Additive); return; } }
public static void OnSceneSaving(Scene scene, string path) { if (!scene.IsValid() || !scene.isLoaded) { return; } var instance = scene.GetSceneSingleton <AmsMultiSceneSetup>(true); if (!instance) { return; } instance.OnBeforeSerialize(); bool isSceneSetupManual = instance._sceneSetupMode == SceneSetupManagement.Manual; if (isSceneSetupManual) { return; } // Clear if we're disabled... by implicitly never setting any values var newSceneSetup = new List <SceneEntry>(); bool bForceDirty = false; // We only update the scene setup if we're the active scene var activeScene = SceneManager.GetActiveScene(); bool isSceneSetupAuto = instance._sceneSetupMode == SceneSetupManagement.Automatic && (activeScene == scene); if (isSceneSetupAuto) { // Update our scene setup SceneSetup[] editorSceneSetup = EditorSceneManager.GetSceneManagerSetup(); for (int i = 0; i < editorSceneSetup.Length; ++i) { // If we're the active scene, don't save it. var editorEntry = editorSceneSetup[i]; if (editorEntry.path == activeScene.path) { continue; } var newEntry = new SceneEntry(editorEntry); newSceneSetup.Add(newEntry); // Save the baked settings var oldEntry = instance._sceneSetup.Find(x => newEntry.scene.Equals(x.scene)); if (oldEntry != null) { newEntry.loadMethod = oldEntry.loadMethod; // We need to update the path if the runtime paths aren't the same (implies a rename) bForceDirty = bForceDirty || (newEntry.scene.runtimePath != oldEntry.scene.runtimePath); } } } // If we had a new scene setup... if (bForceDirty || !newSceneSetup.SequenceEqual(instance._sceneSetup)) { instance._sceneSetup = newSceneSetup; EditorUtility.SetDirty(instance); EditorSceneManager.MarkSceneDirty(scene); AmsDebug.Log(instance, "SceneSetup for {0} has been updated. If this is unexpected, click here to double-check the entries!", activeScene.name); } }