using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using VOID.Generic.Navigation; using VOID.Generic.Objects.Unit; using VOID.Generic.Player; using VOID.Generic.SaveLoad; using VOID.UserInterface.LoadingScreen; using VOID.Generic.Util; using VOID.Generic.World; namespace VOID.Generic { public class GameLoopManager : MonoBehaviour { public static GameLoopManager current { get; private set; } [SerializeField] private SceneReference _mainMenuScene; [SerializeField] private SceneReference _LoadingScreenScene; [SerializeField] private SceneReference _lobbyScene; [SerializeField] private SceneReference _worldGeneratorScene; private void Awake() { if (current) { Debug.LogWarning("Instance of " + nameof(GameLoopManager) + " already exists!"); Destroy(this); return; } // Only one instance for whole game, this should manage scene changing nad using all other managers // DontDestroyOnLoad works only on root game object so we need to unparent it current = this; current.transform.parent = null; DontDestroyOnLoad(this); } private void Start() { // Only for dev/debug purpose - start instantly current scene if (SceneManager.GetActiveScene().path != _mainMenuScene) { StartCoroutine(InitializeDebugScene()); return; } StartCoroutine(InitializeMainMenu()); } private IEnumerator InitializeDebugScene() { // wait for other things yield return new WaitForFixedUpdate(); // So far only thing needed for dev/debug scene to work is initialize player's units if (PlayerController.current.mainUnit) { PlayerController.current.SetMainUnit(PlayerController.current.mainUnit); PlayerController.current.SetMainUnitActive(); CameraManager.current.stickTo = PlayerController.current.mainUnit.transform; } if (PlayerController.current.subUnit) { PlayerController.current.SetSubUnit(PlayerController.current.subUnit); } // Create (if missing) profile and load it PlayerProfileManager.current.SelectProfile(PlayerProfileManager.current.GetDefaultProfile()); } private IEnumerator InitializeMainMenu() { // wait for other things yield return new WaitForFixedUpdate(); // Load last profile if exists var defaultProfile = PlayerProfileManager.current.GetDefaultProfile(); if (defaultProfile != null) PlayerProfileManager.current.SelectProfile(defaultProfile); LoadMainMenuAfter().ForEach(action => action.Invoke()); } #region BeforeLoading public void LoadMainMenu() { StartCoroutine(LoadScene(_mainMenuScene, LoadMainMenuAfter())); } public void LoadLobby() { StartCoroutine(LoadScene(_lobbyScene, LoadLobbyAfter())); } public void LoadGenerator(UnitObject playerUnit) { // Dont destroy selected avatar by player on scene change, to do that we need to remove its PARENT! playerUnit.gameObject.transform.parent = null; DontDestroyOnLoad(playerUnit.gameObject); StartCoroutine(LoadScene(_worldGeneratorScene, LoadGeneratorAfter(playerUnit))); } #endregion #region LoadingScreen private IEnumerator LoadScene(SceneReference sceneReference, List onLoadAfter) { // Load LoadingScene // Reset loading progress // Previous scene will be automatically unloaded var loadAsync = SceneManager.LoadSceneAsync(_LoadingScreenScene, LoadSceneMode.Single); yield return new WaitUntil(() => loadAsync.isDone); LoadingScreenUI.current.SetProgress(0f); yield return new WaitForEndOfFrame(); // Load wanted scene // Important - make that scene active before any Awake() execute! var sceneAsync = SceneManager.LoadSceneAsync(sceneReference, LoadSceneMode.Additive); sceneAsync.completed += _ => SceneManager.SetActiveScene(SceneManager.GetSceneByPath(sceneReference)); yield return new WaitUntil(() => sceneAsync.isDone); LoadingScreenUI.current.SetProgress(0.2f); yield return new WaitForEndOfFrame(); // Additional steps to do before fully changing to next scene if (onLoadAfter.Count > 0) { var progressPerAction = 0.8f / onLoadAfter.Count; for (var i = 0; i < onLoadAfter.Count; i++) { onLoadAfter[i].Invoke(); LoadingScreenUI.current.SetProgress(0.2f + (i+1) * progressPerAction); yield return new WaitForEndOfFrame(); } } // Set progress to 100% + TODO: wait 1 second so we won't be too "fast" LoadingScreenUI.current.SetProgress(1f); yield return new WaitForSecondsRealtime(1f); // Change to next scene + remove loading screen SceneManager.UnloadSceneAsync(_LoadingScreenScene); } #endregion #region AfterLoading private List LoadGeneratorAfter(UnitObject playerUnit) { return new List { GenerateWorld, GenerateNavMesh, InitializePlayer }; void GenerateWorld() { // Generate world var seed = RandomUtil.RandomInteger(0, int.MaxValue); WorldGeneratorManager.current.Generate(150, seed, true); Debug.Log("World Generated with seed: " + seed); } void GenerateNavMesh() { // Build nav mesh for whole map NavigationManager.current.BuildNavMesh(); } void InitializePlayer() { // initialize managers with selected player avatar playerUnit.transform.position = new Vector3(0, 0, 0); PlayerController.current.SetMainUnit(playerUnit); PlayerController.current.SetMainUnitActive(); CameraManager.current.stickTo = playerUnit.transform; // Make avatar destroyable again on scene change SceneManager.MoveGameObjectToScene(playerUnit.gameObject, SceneManager.GetActiveScene()); } } private List LoadMainMenuAfter() { return new List() { }; } private List LoadLobbyAfter() { return new List() { }; } #endregion } }