Files
bansonic_beta_main/Assets/Resources/op/load_preload/preload_allScene.cs
T

402 lines
14 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>
/// 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
/// </summary>
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<string> sceneQueue = new Queue<string>();
private Queue<string> resourceQueue = new Queue<string>();
// 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<Object> loadedResources = new List<Object>();
private Coroutine preloadCoroutine;
void Awake()
{
fadeOutObject.SetActive(true);
// if UI not assigned, try to auto-find Text components
if (progressText == null)
{
progressText = GetComponentInChildren<Text>(true);
}
if (loadingInfoText == null)
{
// try to find a different Text child (avoid using same as progressText)
var texts = GetComponentsInChildren<Text>(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<CanvasGroup>() ?? fadeOutObject.GetComponentInChildren<CanvasGroup>(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<Object>(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<Text>(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<Text>(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<CanvasGroup>() ?? fadeOutObject.GetComponentInChildren<CanvasGroup>(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);
}
/// <summary>
/// Activate a previously preloaded additive scene by name (sets allowSceneActivation=true)
/// </summary>
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.");
}
}