using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Spine.Unity; /// /// 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 GameObject spineObject1; public GameObject spineObject2; public CanvasGroup spineObject1CanvasGroup; public float spineObject1DestroyDelay = 6.7f; public float spineObject1FadeDelay = 5.7f; public float spineObject2PlayDelay = 0f; public Text progressText; [Tooltip("Optional Image whose fillAmount will be driven by the overall progress (0..1)")] public Image progressFillImage; [Tooltip("Optional Image to disable after 1 second")] public Image startDisableImage; [Tooltip("Documentation text normalized.")] public Text loadingInfoText; [Header("Behavior")] public bool startOnAwake = true; [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 // 确保物体 1 初始处于关闭状态,以便 1 秒后延迟开启 if (spineObject1 != null) { spineObject1.SetActive(false); } // 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; } } } } void Start() { Debug.Log("[preload_allScene] Start called. resourcesToPreload count: " + (resourcesToPreload?.Length ?? 0)); // 同时启用并初始化两个 Spine 物体 if (spineObject1 != null) { // Start 时显式禁用物体 1,确保初始状态正确 spineObject1.SetActive(false); // 等待 1 秒后再播放 Spine 1 StartCoroutine(DelayedPlaySpine1()); } if (spineObject2 != null) { // Start 时显式禁用物体 2,确保初始状态正确 spineObject2.SetActive(false); // 延迟播放物体 2 的动画 StartCoroutine(DelayedPlaySpine2Logic()); } // 1 秒后禁用指定的 Image if (startDisableImage != null) { StartCoroutine(DelayedDisableImage()); } StartPreload(); } private IEnumerator DelayedDisableImage() { yield return new WaitForSeconds(1f); if (startDisableImage != null) { startDisableImage.gameObject.SetActive(false); Debug.Log("[preload_allScene] startDisableImage has been disabled after 1 second."); } } private IEnumerator DelayedPlaySpine1() { // 等待 1 秒 yield return new WaitForSeconds(1f); if (spineObject1 != null) { spineObject1.SetActive(true); var sg1 = spineObject1.GetComponentInChildren(true); if (sg1 != null) { sg1.allowMultipleCanvasRenderers = true; sg1.Initialize(true); var state1 = (sg1.Animation as IAnimationStateComponent)?.AnimationState; if (state1 != null) { state1.SetAnimation(0, "intro", false); } // 改为延迟切换到 loop 动画 StartCoroutine(DelayedPlaySpine1Loop(sg1)); // 新增渐隐逻辑 StartCoroutine(FadeOutSpine1()); } } } private IEnumerator DelayedPlaySpine1Loop(SkeletonGraphic sg) { if (spineObject1DestroyDelay > 0) yield return new WaitForSeconds(spineObject1DestroyDelay); var state1 = (sg.Animation as IAnimationStateComponent)?.AnimationState; if (state1 != null) { state1.SetAnimation(0, "loop", true); } } private IEnumerator FadeOutSpine1() { if (spineObject1CanvasGroup == null) yield break; if (spineObject1FadeDelay > 0) yield return new WaitForSeconds(spineObject1FadeDelay); float duration = 1f; float elapsed = 0f; float startAlpha = spineObject1CanvasGroup.alpha; while (elapsed < duration) { elapsed += Time.deltaTime; spineObject1CanvasGroup.alpha = Mathf.Lerp(startAlpha, 0f, elapsed / duration); yield return null; } spineObject1CanvasGroup.alpha = 0f; // 渐隐结束后销毁物体 1 if (spineObject1 != null) { Debug.Log("SpineObject1 fade out completed. Destroying object."); Destroy(spineObject1); } } private IEnumerator DelayedPlaySpine2Logic() { if (spineObject2 == null) yield break; // 等待指定的播放延迟 if (spineObject2PlayDelay > 0) yield return new WaitForSeconds(spineObject2PlayDelay); // 启用物体并初始化动画 spineObject2.SetActive(true); var sg2 = spineObject2.GetComponentInChildren(true); if (sg2 != null) { sg2.allowMultipleCanvasRenderers = true; sg2.Initialize(true); var state2 = (sg2.Animation as IAnimationStateComponent)?.AnimationState; if (state2 != null) { state2.SetAnimation(0, "loop", true); } } } 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"); 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); 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; } 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; } } } }