using System.Collections; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; using UnityEngine.Analytics; using CustomAssetBundleSystem; /* DESCRIPTION: 1. When the Loading_Screen gameobject with this script is enabled, the loading slider animates to show progress when changing between UI's or scenes */ public class LoadingScreen : MonoBehaviour { public GameObject BackgroundImg_GameObject; //Reference to the background image in the loading UI public GameObject Hints_GameObject; //Reference to Hints text in the loading UI public Slider loadingSlider; //Reference to the slider in the loading UI //For the "Loading..." text shown public TextMeshProUGUI loadingText; public string loadingBaseText; //Text showing "Loading..." whereas the dots appear to animate public string og_text; public string ui_to_show; public int to_change_to; //1. Scene, 2. UI public Sprite[] backgroundImages; // Array of background images public string[] hintsArray; //Array of hints to be displayed while loading private int maxBackgroundChanges = 1, backgroundChangeCount = 0; // Maximum number of background changes private int maxhintsChanges = 1, hintsChangeCount = 0; // Counter to keep track of background changes private int maxLoaderChanges = 3, loaderChangeCount = 0; // Counter to keep track of background changes bool flag = true; //flag to check if loading has been initialized private GameManager gameManager; private float barProgess; private bool pendingInitialization; private string targetSceneName; // remember scene we are loading so we can react on load private bool sceneHideHookRegistered; private bool fallbackHideHookRegistered; // subscribed when we didn't initialize via to_change_to==1 // Scenes on which we should auto-hide the loading UI even if we didn't initiate the load via this component. // Keeps prior behavior intact while fixing cases like WaitingSceneManager. [SerializeField] private string[] autoHideOnSceneNames = new[] { "Game" }; // AssetBundle Download Integration [Header("AssetBundle Download Settings")] [SerializeField] private bool enableAssetBundleDownload = false; // DISABLED: Using Build Settings scenes instead [SerializeField] private bool downloadBeforeMenu = false; // DISABLED: Using Build Settings scenes instead private AssetBundleDownloadManager downloadManager; // Force disable AssetBundle system at runtime (overrides serialized Inspector values) private void Awake() { // IMPORTANT: Force disable AssetBundle downloading until you want to re-enable it enableAssetBundleDownload = false; downloadBeforeMenu = false; } private void OnEnable() { gameManager = GameManager.Instance; if (gameManager == null) { gameManager = GetComponent() ?? GetComponentInParent(); } if (gameManager == null) { #if UNITY_2022_2_OR_NEWER gameManager = FindFirstObjectByType(); #else gameManager = FindObjectOfType(); #endif } // Initialize AssetBundle download manager if (enableAssetBundleDownload && downloadManager == null) { downloadManager = gameObject.AddComponent(); // Pass UI references to download manager var downloadManagerType = downloadManager.GetType(); var sliderField = downloadManagerType.GetField("loadingSlider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var loadingTextField = downloadManagerType.GetField("loadingText", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var hintsTextField = downloadManagerType.GetField("hintsText", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (sliderField != null) sliderField.SetValue(downloadManager, loadingSlider); if (loadingTextField != null) loadingTextField.SetValue(downloadManager, loadingText); if (hintsTextField != null) hintsTextField.SetValue(downloadManager, Hints_GameObject.GetComponent()); } // Hints to be shown hintsArray = new string[] { "Complete Weekly Missions to gain XP", "Complete a 20 Wins streak to level up in Rank", "Missions change every Week" }; og_text = loadingText.text; // "Loading..." text loadingBaseText = loadingText.text; if (pendingInitialization) { InitializeLoading(); } // Always attach a lightweight fallback so if another system loads the scene (e.g., WaitingSceneManager), // we still hide the loading UI when the gameplay scene is ready. if (!fallbackHideHookRegistered) { SceneManager.sceneLoaded += FallbackSceneLoadedHide; fallbackHideHookRegistered = true; } } private void OnDisable() { StopAllCoroutines(); // Ensure we unsubscribe any scene hooks to avoid leaks. if (sceneHideHookRegistered) { SceneManager.sceneLoaded -= HandleSceneLoadedInternal; sceneHideHookRegistered = false; } if (fallbackHideHookRegistered) { SceneManager.sceneLoaded -= FallbackSceneLoadedHide; fallbackHideHookRegistered = false; } } // Function called when loading has been initialized public void InitializeLoading() { pendingInitialization = false; StopAllCoroutines(); backgroundChangeCount = 0; hintsChangeCount = 0; loaderChangeCount = 0; flag = true; if (loadingSlider != null) { loadingSlider.value = 0f; } if (!string.IsNullOrEmpty(og_text) && loadingText != null) { loadingBaseText = og_text; loadingText.text = og_text; } ChangeBackgroundImage(); ChangeHintsText(); StartCoroutine(ChangeBackgroundWithDelay()); StartCoroutine(ChangeHintsWithDelay()); // Check if we should download AssetBundles BEFORE loading menu if (enableAssetBundleDownload && downloadBeforeMenu && ui_to_show == "MenuScene" && to_change_to == 1) { Debug.Log("[LoadingScreen] Starting AssetBundle download before MenuScene..."); StartCoroutine(DownloadThenLoadScene(ui_to_show)); return; // Don't proceed with normal loading yet } if (to_change_to == 1) { if (GameManager.Instance != null) { SceneManager.sceneLoaded += GameManager.Instance.OnSceneLoaded; } targetSceneName = ui_to_show; // Also register a local handler to guarantee the loading UI hides even if external code path changes. if (!sceneHideHookRegistered) { SceneManager.sceneLoaded += HandleSceneLoadedInternal; sceneHideHookRegistered = true; } StartCoroutine(LoadSceneAsync(ui_to_show)); } else { StartCoroutine(AnimateSliderFill(5f));//duration should be 3 for editor simulation, 5 for mobile apk lower than that the corouting gets jammed } } /// /// Download AssetBundles first, then load the target scene /// private IEnumerator DownloadThenLoadScene(string sceneName) { if (downloadManager == null) { Debug.LogError("[LoadingScreen] DownloadManager not initialized!"); // Fallback to normal loading StartCoroutine(LoadSceneAsync(sceneName)); yield break; } Debug.Log("[LoadingScreen] Starting AssetBundle download process..."); bool downloadSuccess = false; // Start download with callback yield return downloadManager.StartDownloadProcess((success) => { downloadSuccess = success; }); if (downloadSuccess) { Debug.Log("[LoadingScreen] ✓ AssetBundle download complete! Loading scene..."); // Now proceed with scene loading if (GameManager.Instance != null) { SceneManager.sceneLoaded += GameManager.Instance.OnSceneLoaded; } targetSceneName = sceneName; if (!sceneHideHookRegistered) { SceneManager.sceneLoaded += HandleSceneLoadedInternal; sceneHideHookRegistered = true; } yield return LoadSceneAsync(sceneName); } else { Debug.LogError("[LoadingScreen] ✗ AssetBundle download failed! Cannot proceed."); // Show error message - user must retry or have internet if (loadingText != null) { loadingText.text = "Download Failed - Check Internet Connection"; } if (Hints_GameObject != null) { Hints_GameObject.GetComponent().text = "Please connect to the internet and restart the game"; } // Don't proceed to menu - block here } } // Function to animate the loading slider being filled from empty to full private IEnumerator AnimateSliderFill(float duration) { loadingBaseText = "LOADING"; int dotsCount = 3; float timer = 0f; float startValue = loadingSlider.value; float targetValue = 1f; // Assuming the slider's range is 0 to 1 float fakeProgress = 0; loadingSlider.value = 0.01f; while (timer < duration) { if (fakeProgress >= 0.25f && fakeProgress < 0.26f) { yield return new WaitForSeconds(0.15f); // Set a delay of 0.5 seconds } if (fakeProgress >= 0.75f && fakeProgress < 0.76f) { yield return new WaitForSeconds(0.25f); // Set a delay of 0.5 seconds } fakeProgress = Mathf.Lerp(startValue, targetValue, timer / duration); if (loadingSlider != null) { loadingSlider.value = fakeProgress; } //Create ... animation effect on the Loading... text int dotsToShow = Mathf.FloorToInt(timer % (dotsCount + 1)); string dots = new string('.', dotsToShow); loadingText.text = loadingBaseText + dots; timer += Time.deltaTime; if ((loaderChangeCount < maxLoaderChanges) && (fakeProgress >= 0.01f && fakeProgress < 0.4f || fakeProgress >= 0.5f && fakeProgress < 0.65f || fakeProgress >= 0.8f && fakeProgress < 0.9f)) { float randomLoaderDelay = Mathf.Round(Random.Range(0.01f, 0.09f) * 100) / 100; // Random value between 1 and 2 with 2 decimal places //Debug.Log("randomdelay " + randomLoaderDelay); yield return new WaitForSeconds(randomLoaderDelay); loaderChangeCount++; } yield return null; } //loadingSlider.value = targetValue; loadingText.text = "LOADING."; // Reset text to the original after completion yield return new WaitForSeconds(0.5f); if (to_change_to == 2) // UI { GameObject uiObject = GameObject.Find("UI"); GameObject UI_to_be_shown = uiObject.transform.Find(ui_to_show).gameObject; UI_to_be_shown.SetActive(true); } } // Function to load scene, and update slider value based on the loading progress private IEnumerator LoadSceneAsync(string sceneName) { // Start a guarded async scene load (Single mode) and prevent duplicates SceneLoader.SceneLoadHandle asyncLoad = SceneLoader.LoadSceneAsyncOnce(sceneName, LoadSceneMode.Single, allowSceneActivation: false); // If a different scene is already loading, try to reuse the current operation if available if (asyncLoad == null) { asyncLoad = SceneLoader.CurrentOperation; if (asyncLoad == null) yield break; // Nothing to wait on } if (gameManager != null) { gameManager.ShowLoadingUI(); } yield return new WaitForSeconds(2f); float timer = 0f; float previousValue = 0; while (!asyncLoad.isDone) { if (loadingSlider.value >= 0.25f && loadingSlider.value < 0.26f) { yield return new WaitForSeconds(0.15f); // Set a delay of 0.5 seconds } if (loadingSlider.value >= 0.75f && loadingSlider.value < 0.76f) { yield return new WaitForSeconds(0.25f); // Set a delay of 0.5 seconds } if (previousValue < Mathf.Lerp(0, asyncLoad.progress, timer / 4f)) { previousValue = Mathf.Lerp(0, asyncLoad.progress, timer / 4f); loadingSlider.value = Mathf.Lerp(0, asyncLoad.progress, timer / 4f); } else loadingSlider.value = previousValue; int dotsToShow = Mathf.FloorToInt(timer % (3 + 1)); string dots = new string('.', dotsToShow); loadingText.text = loadingBaseText + dots; timer += Time.deltaTime; if ((loaderChangeCount < maxLoaderChanges) && (loadingSlider.value >= 0.01f && loadingSlider.value < 0.4f || loadingSlider.value >= 0.5f && loadingSlider.value < 0.65f || loadingSlider.value >= 0.8f && loadingSlider.value < 0.9f)) { float randomLoaderDelay = Mathf.Round(Random.Range(0.01f, 0.09f) * 100) / 100; // Random value between 1 and 2 with 2 decimal places yield return new WaitForSeconds(randomLoaderDelay); loaderChangeCount++; } if (loadingSlider.value >= 0.9f) { loadingText.text = "LOADING..."; loadingSlider.value = 1.0f; float randomLoaderDelay = Mathf.Round(Random.Range(0.01f, 0.09f) * 100) / 100; // Random value between 1 and 2 with 2 decimal places yield return new WaitForSeconds(randomLoaderDelay); loaderChangeCount++; // Activate the scene once ready asyncLoad.allowSceneActivation = true; Debug.Log("[LoadingScreen] Scene activation triggered, waiting for isDone..."); } yield return null; } Debug.Log("[LoadingScreen] Scene load complete (asyncLoad.isDone=true), hiding loading UI..."); // Use GameManager.Instance singleton instead of cached reference (which may be stale after scene change) if (GameManager.Instance != null) { GameManager.Instance.HideLoadingUI(); } else if (gameManager != null) { gameManager.HideLoadingUI(); } else { Debug.LogWarning("[LoadingScreen] No GameManager available to hide loading UI!"); // Fallback: try to disable our own root if (transform.root != null) { transform.root.gameObject.SetActive(false); } } } // Internal safety: ensure we always hide loading when the intended scene finishes loading. private void HandleSceneLoadedInternal(Scene scene, LoadSceneMode mode) { if (!string.IsNullOrEmpty(targetSceneName) && scene.name == targetSceneName) { // Stop anim coroutines. StopAllCoroutines(); // Hide through GameManager if available. if (GameManager.Instance != null) { GameManager.Instance.HideLoadingUI(); } else if (gameObject.activeInHierarchy) { // Fallback: disable our root canvas (assumes parent = loadingUI) transform.root.gameObject.SetActive(false); } // cleanup subscription SceneManager.sceneLoaded -= HandleSceneLoadedInternal; sceneHideHookRegistered = false; targetSceneName = null; } } // Fallback hide when we didn't initiate the load, or targetSceneName wasn't set. private void FallbackSceneLoadedHide(Scene scene, LoadSceneMode mode) { // If InitializeLoading set a specific target, let HandleSceneLoadedInternal manage it. if (!string.IsNullOrEmpty(targetSceneName)) return; if (autoHideOnSceneNames != null) { for (int i = 0; i < autoHideOnSceneNames.Length; i++) { var name = autoHideOnSceneNames[i]; if (!string.IsNullOrEmpty(name) && scene.name == name) { // Stop any visuals if they were running. StopAllCoroutines(); if (GameManager.Instance != null) { GameManager.Instance.HideLoadingUI(); } else if (gameObject.activeInHierarchy) { transform.root.gameObject.SetActive(false); } // Once done, remove fallback hook SceneManager.sceneLoaded -= FallbackSceneLoadedHide; fallbackHideHookRegistered = false; break; } } } } // Function to change the background image of the loading UI private IEnumerator ChangeBackgroundWithDelay() { while (true) { if (backgroundChangeCount < maxBackgroundChanges) { float randomDelay = Mathf.Round(Random.Range(1f, 2f) * 100) / 100; // Random value between 1 and 2 with 2 decimal places yield return new WaitForSeconds(randomDelay); // Adjust the time to wait as needed (2 seconds in this case) ChangeBackgroundImage(); backgroundChangeCount++; } else { yield break; // Exit coroutine when maximum number of background changes is reached } yield return null; } } // Function to change the hints being displayed in the loading UI while the player waits the loading to be completed private IEnumerator ChangeHintsWithDelay() { while (true) { if (hintsChangeCount < maxhintsChanges) { float randomDelay = Mathf.Round(Random.Range(0.75f, 2.25f) * 100) / 100; // Random value between 1 and 2 with 2 decimal places yield return new WaitForSeconds(randomDelay); // Adjust the time to wait as needed (2 seconds in this case) ChangeHintsText(); hintsChangeCount++; } else { yield break; // Exit coroutine when maximum number of background changes is reached } yield return null; } } // Function to change the hints text being displayed private void ChangeHintsText() { // Remove stray literal text assignment; pick a random hint safely. if (hintsArray.Length > 0) { int randomIndex = Random.Range(0, hintsArray.Length); Hints_GameObject.GetComponent().text = hintsArray[randomIndex]; } } // Function to change the background image in the loading UI private void ChangeBackgroundImage() { if (backgroundImages.Length > 0) { int randomIndex = Random.Range(0, backgroundImages.Length); BackgroundImg_GameObject.GetComponent().sprite = backgroundImages[randomIndex]; } } // Function that is referenced/called in other script which specifies what Scene or UI to change to after the loading plays out public void UI_to_show_AfterLoading(int x, string ui_name) { to_change_to = x; ui_to_show = ui_name; backgroundChangeCount = 0; hintsChangeCount = 0; loaderChangeCount = 0; flag = true; pendingInitialization = true; if (isActiveAndEnabled) { InitializeLoading(); } } }