using System; using System.Collections; using System.Collections.Generic; using System.Text; using NaughtyAttributes; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using TMPro; enum LoadState { Idle, FadeOut, UnityUnload, Cleanup, UnityLoad, SceneSetup, Sync, FadeIn, COUNT } public class Bootstrap : MonoBehaviour { [Header( "Debug" )] [SerializeField] private bool _debugMode; [Header( "Config" )] [SerializeField] [Expandable] private BootstrapConfig _config; private static Bootstrap _instance; private LoadState _loadState; private Coroutine _currentSceneLoad; private SceneType _activeSceneType; private SceneLoader _activeScene; private string _activeSceneName; [Header( "References" )] [SerializeField] private Image _fader; [SerializeField] private TMP_Text _debugDisplay; //private LevelDescriptor _activeLevelDescriptor; private List _loadedLevels = new List(); private Queue _toLoadLevels = new Queue(); private AsyncOperation _levelLoadAsync = null; private Queue _toUnloadLevels = new Queue(); private AsyncOperation _levelUnloadAsync = null; public bool IsLoading => _loadState != LoadState.Idle; private void QueryLoadState() { if ( _activeScene != null && _activeScene.ToLoad != SceneType.None ) { LoadScene( _activeScene.ToLoad ); } } private void Awake() { _instance = this; if ( _debugMode ) { StartCoroutine( DebugCR() ); } } void Start() { LoadScene( SceneType.BootSequence ); } public static void LoadScene( SceneType scene ) { if ( _instance.IsLoading ) { Debug.LogError( "[Bootstrap]: couldn't load scene: another load operation already active." ); return; } _instance._currentSceneLoad = _instance.StartCoroutine( _instance.LoadSceneCR( scene ) ); } public static void LoadLevel( LevelDescriptor descriptor ) { _instance._toLoadLevels.Enqueue( descriptor ); } private IEnumerator DebugCR() { LoadState loadState = LoadState.COUNT; StringBuilder sb = new StringBuilder(); while ( true ) { if ( _loadState != loadState ) { loadState = _loadState; sb.Clear(); sb.Append( "LoadState::" ); sb.AppendLine( _loadState.ToString() ); if ( _activeSceneName != null ) { sb.AppendFormat( "S: {0} ({1})", _activeSceneType, _activeSceneName ); } else { sb.AppendFormat( "S: [none]" ); } sb.Append( "\n" ); if ( (_loadedLevels?.Count ?? 0) > 0 ) { sb.Append( "L: " ); for ( int i = 0; i < _loadedLevels.Count; ++i ) { sb.Append( _loadedLevels[ i ].LevelScene ); if ( i != _loadedLevels.Count - 1 ) { sb.Append( ", " ); } } } else { sb.AppendFormat( "L: [none]" ); } sb.Append( "\n" ); sb.AppendFormat( "Load: {0} ({1} queued)\n", _levelLoadAsync?.isDone ?? true ? "idle" : "busy" , _toLoadLevels.Count ); sb.AppendFormat( "Unload: {0} ({1} queued)\n", _levelUnloadAsync?.isDone ?? true ? "idle" : "busy" , _toUnloadLevels.Count ); _debugDisplay.SetText( sb.ToString() ); } yield return null; } } private bool ProcessLevelUnloads() { if (_levelUnloadAsync?.isDone ?? true) { while (true) { if ( _toUnloadLevels.Count == 0 ) { break; } var target = _toUnloadLevels.Dequeue(); if ( _loadedLevels.Contains( target ) == false ) { continue; } _levelUnloadAsync = SceneManager.UnloadSceneAsync( target.LevelScene ); _loadedLevels.Remove( target ); return false; } } return (_levelUnloadAsync?.isDone ?? true) && _toUnloadLevels.Count == 0; } private bool ProcessLevelLoads() { if (_levelLoadAsync?.isDone ?? true) { while (true) { if ( _toLoadLevels.Count == 0 ) { break; } var target = _toLoadLevels.Dequeue(); if ( _loadedLevels.Contains( target ) ) { continue; } _levelLoadAsync = SceneManager.LoadSceneAsync( target.LevelScene, LoadSceneMode.Additive ); _loadedLevels.Add( target ); return false; } } return (_levelLoadAsync?.isDone ?? true) && _toLoadLevels.Count == 0; } private IEnumerator LoadSceneCR( SceneType scene ) { AsyncOperation asyncOp; // fade out _loadState = LoadState.FadeOut; _fader.CrossFadeAlpha( 1f, _config.TransitionDuration, true ); yield return new WaitForSecondsRealtime( _config.TransitionDuration ); // unload unity's scenes _loadState = LoadState.UnityUnload; if ( _activeSceneName != null ) { // unload main scene asyncOp = SceneManager.UnloadSceneAsync( _activeSceneName ); while ( asyncOp.isDone == false ) yield return null; _activeScene = null; _activeSceneName = null; } // cleanup _loadState = LoadState.Cleanup; if ( scene == SceneType.Gameplay ) { _toUnloadLevels.Clear(); _toLoadLevels.Clear(); _loadedLevels.Clear(); _toLoadLevels.Enqueue( _config.StartLevel ); } else { _toLoadLevels.Clear(); for ( int i = 0; i < _loadedLevels.Count; ++i ) { if ( _toUnloadLevels.Contains( _loadedLevels[ i ] ) == false ) { _toUnloadLevels.Enqueue( _loadedLevels[ i ] ); } } } // 6 collections, as that is the deepest unity hierarchies go. for ( int i = 0; i < 6; ++i ) { GC.Collect(); yield return null; } // unity loading _loadState = LoadState.UnityLoad; _activeSceneType = scene; _activeSceneName = _config.GetScene( scene ); asyncOp = SceneManager.LoadSceneAsync( _activeSceneName, LoadSceneMode.Additive ); while ( asyncOp.isDone == false ) yield return null; // setup _loadState = LoadState.SceneSetup; _activeScene = FindObjectOfType(); while ( _activeScene.LoadComplete == false ) yield return null; // sync _loadState = LoadState.Sync; while ( ProcessLevelUnloads() == false ) { yield return null; } while ( ProcessLevelLoads() == false ) { yield return null; } // fade in _loadState = LoadState.FadeIn; _fader.CrossFadeAlpha( 0f, _config.TransitionDuration, true ); yield return new WaitForSecondsRealtime( _config.TransitionDuration ); yield return null; _loadState = LoadState.Idle; _currentSceneLoad = null; } private void Update() { if ( !IsLoading ) { QueryLoadState(); if ( _activeSceneType == SceneType.Gameplay ) { if ( ProcessLevelUnloads() ) { ProcessLevelLoads(); } } } } }