using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; /// /// Throttled preload: uses queues and concurrency limits to load scenes and Resources in small time slices /// to avoid long frames on low-end devices. /// - scenesToPreload: names of scenes included in Build Settings to preload additively (allowSceneActivation=false) /// - resourcesToPreload: paths under Resources/ to load via Resources.LoadAsync /// public class preload_allScene : MonoBehaviour { [Header("Preload settings")] public string[] resourcesToPreload = new string[0]; [Header("UI")] public Text progressText; [Tooltip("Optional Image whose fillAmount will be driven by the overall progress (0..1)")] public Image progressFillImage; [Tooltip("Optional Text to show current loading item: '���ڼ��أ�xxx'")] public Text loadingInfoText; [Header("Behavior")] public bool startOnAwake = true; [Header("Fade-out on complete")] [Tooltip("GameObject to disable after fade completes")] public GameObject fadeOutObject; [Tooltip("CanvasGroup to fade (if null and fadeOutObject assigned, will search for one on it)")] public CanvasGroup fadeCanvasGroup; [Tooltip("Duration of fade-out in seconds")] public float fadeOutDuration = 0.5f; [Tooltip("Delay before starting fade-out after completion")] public float fadeOutDelay = 0f; [Header("Throttle")] public int maxConcurrentResourceLoads = 2; [Tooltip("Max milliseconds spent per frame on driver work (approx)")] public int maxMilliSecondsPerFrame = 4; // internal queues and trackers private Queue resourceQueue = new Queue(); private List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>(); private List loadedResources = new List(); private Coroutine preloadCoroutine; void Awake() { Debug.Log("[preload_allScene] Awake called. Forcing Time.timeScale = 1."); Time.timeScale = 1f; // Ensure time is running so fades and DOTween work if (fadeOutObject != null) { fadeOutObject.SetActive(true); } else { Debug.LogWarning("[preload_allScene] fadeOutObject is not assigned!"); } // if UI not assigned, try to auto-find Text components if (progressText == null) { progressText = GetComponentInChildren(true); } if (loadingInfoText == null) { // try to find a different Text child (avoid using same as progressText) var texts = GetComponentsInChildren(true); foreach (var t in texts) { if (t != progressText) { loadingInfoText = t; break; } } } // If a fade object is set but no CanvasGroup assigned, try to find one on the object if (fadeOutObject != null && fadeCanvasGroup == null) { fadeCanvasGroup = fadeOutObject.GetComponent() ?? fadeOutObject.GetComponentInChildren(true); } } void Start() { Debug.Log("[preload_allScene] Start called. resourcesToPreload count: " + (resourcesToPreload?.Length ?? 0)); // Always call StartPreload to ensure the screen fades out even if no resources are listed StartPreload(); } public void StartPreload() { if (preloadCoroutine != null) return; // fill queues resourceQueue.Clear(); if (resourcesToPreload != null) { foreach (var r in resourcesToPreload) if (!string.IsNullOrWhiteSpace(r)) resourceQueue.Enqueue(r); } Debug.Log($"Preload starting. Resources queued: {resourceQueue.Count}"); // ensure UI shows 0% UpdateProgressText(0f); UpdateLoadingInfo(""); // If nothing to preload, just show 100% and fade out immediately if (resourceQueue.Count == 0) { UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); if (fadeOutObject != null) { StartCoroutine(FadeAndDisableCoroutine()); } return; } preloadCoroutine = StartCoroutine(PreloadRoutine()); } public void StopPreload() { if (preloadCoroutine != null) { StopCoroutine(preloadCoroutine); preloadCoroutine = null; } } private IEnumerator PreloadRoutine() { // Start initial batch while (resourceQueue.Count > 0 || activeResourceReqs.Count > 0) { float frameStart = Time.realtimeSinceStartup; // start resource reqs up to concurrency while (activeResourceReqs.Count < maxConcurrentResourceLoads && resourceQueue.Count > 0) { string path = resourceQueue.Dequeue(); UpdateLoadingInfo($"Loading: {path}"); ResourceRequest rr = null; try { rr = Resources.LoadAsync(path); } catch (System.Exception ex) { Debug.LogWarning($"Preload: failed to start loading resource '{path}': {ex}"); } if (rr != null) { activeResourceReqs.Add((path, rr)); Debug.Log($"Preload: started resource load '{path}'"); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break; } // Poll progress and collect finished resources for (int i = activeResourceReqs.Count - 1; i >= 0; i--) { var tuple = activeResourceReqs[i]; var rr = tuple.rr; if (rr.isDone) { if (rr.asset != null) loadedResources.Add(rr.asset); Debug.Log($"Preload: resource loaded '{tuple.path}'"); activeResourceReqs.RemoveAt(i); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break; } // Update progress calculation float overall = CalculateOverallProgress(); UpdateProgressText(overall); // If there are no active resource requests and no queued resources, we're done bool anyActiveResources = activeResourceReqs.Count > 0 || resourceQueue.Count > 0; if (!anyActiveResources) { UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); preloadCoroutine = null; Debug.Log("Preload complete (throttled). Resources cached: " + loadedResources.Count); // start fade+disable if configured if (fadeOutObject != null) { StartCoroutine(FadeAndDisableCoroutine()); } yield break; } // yield one frame to respect budget yield return null; } // if we exit loop, also mark complete UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); preloadCoroutine = null; if (fadeOutObject != null) { StartCoroutine(FadeAndDisableCoroutine()); } } private float CalculateOverallProgress() { float sum = 0f; int items = 0; items += resourceQueue.Count + activeResourceReqs.Count; if (items == 0) return 1f; foreach (var (path, rr) in activeResourceReqs) { sum += rr.progress; // 0..1 } return Mathf.Clamp01(sum / (float)items); } private void UpdateProgressText(float t) { int percent = Mathf.RoundToInt(t * 100f); // update fill image if present if (progressFillImage != null) { // clamp to 0..1 progressFillImage.fillAmount = Mathf.Clamp01(t); } if (progressText != null) { progressText.text = percent + "%"; return; } // If no UI assigned, try to find any in children and cache it var tcomp = GetComponentInChildren(true); if (tcomp != null) { progressText = tcomp; progressText.text = percent + "%"; } } // helper to update loading info text private void UpdateLoadingInfo(string message) { if (loadingInfoText != null) { loadingInfoText.text = message; return; } // try to find and cache a Text child that is not the progress text var texts = GetComponentsInChildren(true); foreach (var t in texts) { if (t != progressText) { loadingInfoText = t; loadingInfoText.text = message; return; } } } private IEnumerator FadeAndDisableCoroutine() { // ensure we have a CanvasGroup reference if (fadeOutObject == null) yield break; if (fadeCanvasGroup == null) { fadeCanvasGroup = fadeOutObject.GetComponent() ?? fadeOutObject.GetComponentInChildren(true); } // if there's no CanvasGroup, just disable after delay if (fadeCanvasGroup == null) { if (fadeOutDelay > 0f) yield return new WaitForSecondsRealtime(fadeOutDelay); fadeOutObject.SetActive(false); yield break; } // optionally wait before fading if (fadeOutDelay > 0f) yield return new WaitForSecondsRealtime(fadeOutDelay); float startAlpha = fadeCanvasGroup.alpha; float elapsed = 0f; while (elapsed < fadeOutDuration) { elapsed += Time.unscaledDeltaTime; float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, fadeOutDuration)); fadeCanvasGroup.alpha = Mathf.Lerp(startAlpha, 0f, frac); yield return null; } fadeCanvasGroup.alpha = 0f; // disable raycasts and interaction fadeCanvasGroup.blocksRaycasts = false; fadeCanvasGroup.interactable = false; // finally disable the GameObject fadeOutObject.SetActive(false); } }