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[] scenesToPreload = new string[0]; 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")] [Tooltip("Max concurrent resource loads")] public int maxConcurrentResourceLoads = 2; [Tooltip("Max concurrent scene loads")] public int maxConcurrentSceneLoads = 1; [Tooltip("Max milliseconds spent per frame on driver work (approx)")] public int maxMilliSecondsPerFrame = 4; // internal queues and trackers private Queue sceneQueue = new Queue(); private Queue resourceQueue = new Queue(); // scenes currently loading (progress < ~0.9) private List<(string sceneName, AsyncOperation op)> loadingSceneOps = new List<(string, AsyncOperation)>(); // scenes loaded to 0.9 and waiting activation private List<(string sceneName, AsyncOperation op)> readySceneOps = new List<(string, AsyncOperation)>(); private List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>(); private List loadedResources = new List(); private Coroutine preloadCoroutine; void Awake() { fadeOutObject.SetActive(true); // 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() { // fadeOutObject.SetActive(true); // auto-start if configured OR there are items to preload if (startOnAwake || (scenesToPreload != null && scenesToPreload.Length > 0) || (resourcesToPreload != null && resourcesToPreload.Length > 0)) { StartPreload(); } } public void StartPreload() { if (preloadCoroutine != null) return; // fill queues sceneQueue.Clear(); resourceQueue.Clear(); if (scenesToPreload != null) { foreach (var s in scenesToPreload) if (!string.IsNullOrWhiteSpace(s)) sceneQueue.Enqueue(s); } if (resourcesToPreload != null) { foreach (var r in resourcesToPreload) if (!string.IsNullOrWhiteSpace(r)) resourceQueue.Enqueue(r); } Debug.Log($"Preload starting. Scenes queued: {sceneQueue.Count}, Resources queued: {resourceQueue.Count}"); // ensure UI shows 0% UpdateProgressText(0f); UpdateLoadingInfo(""); preloadCoroutine = StartCoroutine(PreloadRoutine()); } public void StopPreload() { if (preloadCoroutine != null) { StopCoroutine(preloadCoroutine); preloadCoroutine = null; } } private IEnumerator PreloadRoutine() { // Start initial batch while (sceneQueue.Count > 0 || resourceQueue.Count > 0 || loadingSceneOps.Count > 0 || activeResourceReqs.Count > 0) { float frameStart = Time.realtimeSinceStartup; // start scene ops up to concurrency while (loadingSceneOps.Count < maxConcurrentSceneLoads && sceneQueue.Count > 0) { string sceneName = sceneQueue.Dequeue(); UpdateLoadingInfo($"正在加载:{sceneName}"); AsyncOperation op = null; try { op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); } catch (System.Exception ex) { Debug.LogWarning($"Preload: failed to start loading scene '{sceneName}': {ex}"); } if (op != null) { op.allowSceneActivation = false; loadingSceneOps.Add((sceneName, op)); Debug.Log($"Preload: started scene load '{sceneName}'"); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break; } // start resource reqs up to concurrency while (activeResourceReqs.Count < maxConcurrentResourceLoads && resourceQueue.Count > 0) { string path = resourceQueue.Dequeue(); UpdateLoadingInfo($"正在加载:{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; } // Poll loading scenes and move to ready list when reach ~0.9 for (int i = loadingSceneOps.Count - 1; i >= 0; i--) { var (sceneName, op) = loadingSceneOps[i]; if (op.progress >= 0.89f) { // move to ready list so we free a slot for next scene readySceneOps.Add((sceneName, op)); loadingSceneOps.RemoveAt(i); Debug.Log($"Preload: scene '{sceneName}' reached 0.9 and moved to ready list"); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break; } // Update progress calculation float overall = CalculateOverallProgress(); UpdateProgressText(overall); // If there are no active resource requests, no loading scenes, no queued scenes/resources, and ready scenes are present (they are considered loaded), we're done bool anyLoadingScenes = loadingSceneOps.Count > 0 || sceneQueue.Count > 0; bool anyActiveResources = activeResourceReqs.Count > 0 || resourceQueue.Count > 0; if (!anyLoadingScenes && !anyActiveResources) { // All loading work started and finished; readySceneOps contains scenes at 0.9 waiting activation UpdateProgressText(1f); UpdateLoadingInfo("加载完成"); preloadCoroutine = null; Debug.Log("Preload complete (throttled). Scenes ready: " + readySceneOps.Count + ", 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("加载完成"); preloadCoroutine = null; if (fadeOutObject != null) { StartCoroutine(FadeAndDisableCoroutine()); } } private float CalculateOverallProgress() { float sum = 0f; int items = 0; // queued scenes count as 0 items += sceneQueue.Count; // loading scenes contribute normalized progress items += loadingSceneOps.Count; // ready scenes count as completed (1) items += readySceneOps.Count; items += resourceQueue.Count + activeResourceReqs.Count; if (items == 0) return 1f; foreach (var (sceneName, op) in loadingSceneOps) { float p = Mathf.Clamp01(op.progress / 0.9f); sum += p; } // ready scenes are counted as full sum += readySceneOps.Count * 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 WaitForSeconds(fadeOutDelay); fadeOutObject.SetActive(false); yield break; } // optionally wait before fading if (fadeOutDelay > 0f) yield return new WaitForSeconds(fadeOutDelay); float startAlpha = fadeCanvasGroup.alpha; float elapsed = 0f; while (elapsed < fadeOutDuration) { elapsed += Time.deltaTime; 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); } /// /// Activate a previously preloaded additive scene by name (sets allowSceneActivation=true) /// public void ActivatePreloadedScene(string sceneName) { for (int i = 0; i < readySceneOps.Count; i++) { if (readySceneOps[i].sceneName == sceneName) { readySceneOps[i].op.allowSceneActivation = true; // remove from ready list so further activations don't re-trigger readySceneOps.RemoveAt(i); return; } } // fallback: if scene loaded and already in SceneManager, nothing to do Debug.LogWarning($"ActivatePreloadedScene: scene '{sceneName}' not found among ready preloaded ops."); } }