392 lines
12 KiB
C#
392 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.UI;
|
|
using Spine.Unity;
|
|
|
|
/// <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[] 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<string> resourceQueue = new Queue<string>();
|
|
|
|
private List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>();
|
|
|
|
private List<Object> loadedResources = new List<Object>();
|
|
|
|
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<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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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<SkeletonGraphic>(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<SkeletonGraphic>(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<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;
|
|
}
|
|
|
|
// 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<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;
|
|
}
|
|
}
|
|
}
|
|
}
|