725 lines
19 KiB
C#
725 lines
19 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.Video;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Throttled preload: uses queues and concurrency limits to load Resources in small time slices
|
|
/// to avoid long frames on low-end devices.
|
|
/// The opening presentation now uses two videos: vj01 plays once, then vj02 loops.
|
|
/// </summary>
|
|
public class preload_allScene : MonoBehaviour
|
|
{
|
|
[Header("Preload settings")]
|
|
public string[] resourcesToPreload = new string[0];
|
|
|
|
[Header("UI references")]
|
|
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("Opening Video")]
|
|
public RawImage openingVideoRawImage;
|
|
public VideoClip vj01Clip;
|
|
public VideoClip vj02Clip;
|
|
|
|
[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;
|
|
|
|
private readonly Queue<string> resourceQueue = new Queue<string>();
|
|
private readonly List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>();
|
|
private readonly List<Object> loadedResources = new List<Object>();
|
|
|
|
private Coroutine preloadCoroutine;
|
|
private VideoPlayer videoPlayer1;
|
|
private VideoPlayer videoPlayer2A;
|
|
private VideoPlayer videoPlayer2B;
|
|
private RenderTexture videoTexture1;
|
|
private RenderTexture videoTexture2A;
|
|
private RenderTexture videoTexture2B;
|
|
private VideoPlayer activeLoopPlayer;
|
|
private VideoPlayer standbyLoopPlayer;
|
|
private bool waitingForLoopStartup;
|
|
private Coroutine openingVideoStartupCoroutine;
|
|
|
|
private void Awake()
|
|
{
|
|
Debug.Log("[preload_allScene] Awake called. Forcing Time.timeScale = 1.");
|
|
Time.timeScale = 1f;
|
|
|
|
EnsureOpeningVideoSetup();
|
|
SetVideoDisplayVisible(openingVideoRawImage, false);
|
|
|
|
if (progressText == null)
|
|
{
|
|
progressText = GetComponentInChildren<Text>(true);
|
|
}
|
|
|
|
if (loadingInfoText == null)
|
|
{
|
|
var texts = GetComponentsInChildren<Text>(true);
|
|
foreach (var t in texts)
|
|
{
|
|
if (t != progressText)
|
|
{
|
|
loadingInfoText = t;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
Debug.Log("[preload_allScene] Start called. resourcesToPreload count: " + (resourcesToPreload?.Length ?? 0));
|
|
|
|
StartOpeningVideoSequence();
|
|
|
|
if (startDisableImage != null)
|
|
{
|
|
StartCoroutine(DelayedDisableImage());
|
|
}
|
|
|
|
StartPreload();
|
|
}
|
|
|
|
private void EnsureOpeningVideoSetup()
|
|
{
|
|
TryAutoAssignVideoClips();
|
|
|
|
if (videoPlayer1 == null && openingVideoRawImage != null)
|
|
{
|
|
videoPlayer1 = CreateVideoPlayer("OpeningVideoPlayer_VJ01", vj01Clip, false, ref videoTexture1);
|
|
if (videoPlayer1 != null)
|
|
{
|
|
videoPlayer1.loopPointReached += OnVj01Finished;
|
|
}
|
|
}
|
|
|
|
if (videoPlayer2A == null && openingVideoRawImage != null)
|
|
{
|
|
videoPlayer2A = CreateVideoPlayer("OpeningVideoPlayer_VJ02_A", vj02Clip, false, ref videoTexture2A);
|
|
if (videoPlayer2A != null)
|
|
{
|
|
videoPlayer2A.prepareCompleted += OnLoopPlayerPrepared;
|
|
videoPlayer2A.loopPointReached += OnLoopVideoReachedEnd;
|
|
}
|
|
}
|
|
|
|
if (videoPlayer2B == null && openingVideoRawImage != null)
|
|
{
|
|
videoPlayer2B = CreateVideoPlayer("OpeningVideoPlayer_VJ02_B", vj02Clip, false, ref videoTexture2B);
|
|
if (videoPlayer2B != null)
|
|
{
|
|
videoPlayer2B.prepareCompleted += OnLoopPlayerPrepared;
|
|
videoPlayer2B.loopPointReached += OnLoopVideoReachedEnd;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StartOpeningVideoSequence()
|
|
{
|
|
EnsureOpeningVideoSetup();
|
|
if (openingVideoStartupCoroutine != null)
|
|
{
|
|
StopCoroutine(openingVideoStartupCoroutine);
|
|
}
|
|
|
|
openingVideoStartupCoroutine = StartCoroutine(PrepareOpeningVideosAndStart());
|
|
}
|
|
|
|
private IEnumerator PrepareOpeningVideosAndStart()
|
|
{
|
|
SetVideoDisplayVisible(openingVideoRawImage, false);
|
|
|
|
if (videoPlayer1 != null && !videoPlayer1.isPrepared)
|
|
{
|
|
videoPlayer1.Prepare();
|
|
}
|
|
|
|
if (videoPlayer2A != null && !videoPlayer2A.isPrepared)
|
|
{
|
|
videoPlayer2A.Prepare();
|
|
}
|
|
|
|
if (videoPlayer2B != null && !videoPlayer2B.isPrepared)
|
|
{
|
|
videoPlayer2B.Prepare();
|
|
}
|
|
|
|
while (!AreOpeningVideosPrepared())
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
openingVideoStartupCoroutine = null;
|
|
|
|
if (videoPlayer1 != null)
|
|
{
|
|
if (openingVideoRawImage != null)
|
|
{
|
|
openingVideoRawImage.texture = videoTexture1;
|
|
}
|
|
|
|
SetVideoDisplayVisible(openingVideoRawImage, true);
|
|
ResetPlayerToStart(videoPlayer1);
|
|
videoPlayer1.Play();
|
|
yield break;
|
|
}
|
|
|
|
StartLoopVideo2();
|
|
}
|
|
|
|
private bool AreOpeningVideosPrepared()
|
|
{
|
|
if (videoPlayer1 != null && !videoPlayer1.isPrepared)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (videoPlayer2A != null && !videoPlayer2A.isPrepared)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (videoPlayer2B != null && !videoPlayer2B.isPrepared)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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 VideoPlayer CreateVideoPlayer(string objectName, VideoClip clip, bool loop, ref RenderTexture renderTexture)
|
|
{
|
|
if (clip == null || openingVideoRawImage == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Transform existing = transform.Find(objectName);
|
|
GameObject playerObject = existing != null ? existing.gameObject : new GameObject(objectName);
|
|
playerObject.transform.SetParent(transform, false);
|
|
|
|
var player = playerObject.GetComponent<VideoPlayer>();
|
|
if (player == null)
|
|
{
|
|
player = playerObject.AddComponent<VideoPlayer>();
|
|
}
|
|
|
|
bool mustStartFromFirstFrame = clip == vj01Clip;
|
|
|
|
player.playOnAwake = false;
|
|
player.isLooping = loop;
|
|
player.waitForFirstFrame = false;
|
|
player.skipOnDrop = !mustStartFromFirstFrame;
|
|
player.timeUpdateMode = VideoTimeUpdateMode.UnscaledGameTime;
|
|
player.playbackSpeed = 1f;
|
|
player.audioOutputMode = VideoAudioOutputMode.None;
|
|
player.source = VideoSource.VideoClip;
|
|
player.clip = clip;
|
|
player.renderMode = VideoRenderMode.RenderTexture;
|
|
|
|
int width = clip.width > 0 ? (int)clip.width : 1920;
|
|
int height = clip.height > 0 ? (int)clip.height : 1080;
|
|
EnsureRenderTexture(ref renderTexture, width, height, objectName + "_RT");
|
|
player.targetTexture = renderTexture;
|
|
|
|
return player;
|
|
}
|
|
|
|
private static void SetVideoDisplayVisible(RawImage image, bool visible)
|
|
{
|
|
if (image == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
image.gameObject.SetActive(visible);
|
|
}
|
|
|
|
private static void ResetPlayerToStart(VideoPlayer player)
|
|
{
|
|
if (player == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try { player.playbackSpeed = 1f; } catch { }
|
|
try { player.Stop(); } catch { }
|
|
try { player.frame = 0; } catch { }
|
|
try { player.time = 0d; } catch { }
|
|
}
|
|
|
|
private void EnsureRenderTexture(ref RenderTexture renderTexture, int width, int height, string textureName)
|
|
{
|
|
if (renderTexture != null && renderTexture.width == width && renderTexture.height == height)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReleaseRenderTexture(ref renderTexture);
|
|
|
|
renderTexture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
|
|
renderTexture.name = textureName;
|
|
renderTexture.Create();
|
|
}
|
|
|
|
private void ReleaseRenderTexture(ref RenderTexture renderTexture)
|
|
{
|
|
if (renderTexture == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (renderTexture.IsCreated())
|
|
{
|
|
renderTexture.Release();
|
|
}
|
|
|
|
Destroy(renderTexture);
|
|
renderTexture = null;
|
|
}
|
|
|
|
private void OnVj01Finished(VideoPlayer source)
|
|
{
|
|
if (videoPlayer1 != null)
|
|
{
|
|
videoPlayer1.Stop();
|
|
}
|
|
|
|
StartLoopVideo2();
|
|
}
|
|
|
|
private void StartLoopVideo2()
|
|
{
|
|
if (videoPlayer2A == null && videoPlayer2B == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetVideoDisplayVisible(openingVideoRawImage, true);
|
|
|
|
VideoPlayer firstPrepared = GetPreparedLoopPlayer();
|
|
if (firstPrepared != null)
|
|
{
|
|
StartPreparedLoopPlayer(firstPrepared);
|
|
return;
|
|
}
|
|
|
|
waitingForLoopStartup = true;
|
|
|
|
if (videoPlayer2A != null && !videoPlayer2A.isPrepared)
|
|
{
|
|
videoPlayer2A.Prepare();
|
|
}
|
|
|
|
if (videoPlayer2B != null && !videoPlayer2B.isPrepared)
|
|
{
|
|
videoPlayer2B.Prepare();
|
|
}
|
|
}
|
|
|
|
private VideoPlayer GetPreparedLoopPlayer()
|
|
{
|
|
if (videoPlayer2A != null && videoPlayer2A.isPrepared)
|
|
{
|
|
return videoPlayer2A;
|
|
}
|
|
|
|
if (videoPlayer2B != null && videoPlayer2B.isPrepared)
|
|
{
|
|
return videoPlayer2B;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void StartPreparedLoopPlayer(VideoPlayer player)
|
|
{
|
|
if (player == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
activeLoopPlayer = player;
|
|
standbyLoopPlayer = player == videoPlayer2A ? videoPlayer2B : videoPlayer2A;
|
|
waitingForLoopStartup = false;
|
|
|
|
if (openingVideoRawImage != null)
|
|
{
|
|
openingVideoRawImage.texture = player.targetTexture;
|
|
}
|
|
|
|
SetVideoDisplayVisible(openingVideoRawImage, true);
|
|
|
|
if (!activeLoopPlayer.isPlaying)
|
|
{
|
|
activeLoopPlayer.Play();
|
|
}
|
|
|
|
if (standbyLoopPlayer != null && !standbyLoopPlayer.isPrepared)
|
|
{
|
|
standbyLoopPlayer.Prepare();
|
|
}
|
|
}
|
|
|
|
private void OnLoopPlayerPrepared(VideoPlayer source)
|
|
{
|
|
if (waitingForLoopStartup)
|
|
{
|
|
StartPreparedLoopPlayer(source);
|
|
}
|
|
}
|
|
|
|
private void OnApplicationPause(bool pauseStatus)
|
|
{
|
|
if (!pauseStatus)
|
|
{
|
|
ResumeOpeningVideosIfNeeded();
|
|
}
|
|
}
|
|
|
|
private void OnApplicationFocus(bool hasFocus)
|
|
{
|
|
if (hasFocus)
|
|
{
|
|
ResumeOpeningVideosIfNeeded();
|
|
}
|
|
}
|
|
|
|
private void ResumeOpeningVideosIfNeeded()
|
|
{
|
|
if (openingVideoRawImage == null || !openingVideoRawImage.gameObject.activeInHierarchy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (videoPlayer1 != null && openingVideoRawImage.texture == videoTexture1 && !videoPlayer1.isPlaying)
|
|
{
|
|
videoPlayer1.Play();
|
|
return;
|
|
}
|
|
|
|
if (activeLoopPlayer != null && openingVideoRawImage.texture == activeLoopPlayer.targetTexture && !activeLoopPlayer.isPlaying)
|
|
{
|
|
activeLoopPlayer.Play();
|
|
return;
|
|
}
|
|
|
|
VideoPlayer prepared = GetPreparedLoopPlayer();
|
|
if (prepared != null && !prepared.isPlaying)
|
|
{
|
|
StartPreparedLoopPlayer(prepared);
|
|
}
|
|
}
|
|
|
|
private void OnLoopVideoReachedEnd(VideoPlayer source)
|
|
{
|
|
if (source != activeLoopPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (standbyLoopPlayer != null && standbyLoopPlayer.isPrepared)
|
|
{
|
|
VideoPlayer finishedPlayer = activeLoopPlayer;
|
|
VideoPlayer nextPlayer = standbyLoopPlayer;
|
|
|
|
if (openingVideoRawImage != null)
|
|
{
|
|
openingVideoRawImage.texture = nextPlayer.targetTexture;
|
|
}
|
|
|
|
if (!nextPlayer.isPlaying)
|
|
{
|
|
nextPlayer.Play();
|
|
}
|
|
|
|
activeLoopPlayer = nextPlayer;
|
|
standbyLoopPlayer = finishedPlayer;
|
|
|
|
if (standbyLoopPlayer.isPlaying)
|
|
{
|
|
standbyLoopPlayer.Stop();
|
|
}
|
|
|
|
standbyLoopPlayer.Prepare();
|
|
return;
|
|
}
|
|
|
|
source.Play();
|
|
}
|
|
|
|
public void StartPreload()
|
|
{
|
|
if (preloadCoroutine != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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}");
|
|
|
|
UpdateProgressText(0f);
|
|
UpdateLoadingInfo(string.Empty);
|
|
|
|
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()
|
|
{
|
|
while (resourceQueue.Count > 0 || activeResourceReqs.Count > 0)
|
|
{
|
|
float frameStart = Time.realtimeSinceStartup;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
float overall = CalculateOverallProgress();
|
|
UpdateProgressText(overall);
|
|
|
|
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 return null;
|
|
}
|
|
|
|
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 (_, rr) in activeResourceReqs)
|
|
{
|
|
sum += rr.progress;
|
|
}
|
|
|
|
return Mathf.Clamp01(sum / items);
|
|
}
|
|
|
|
private void UpdateProgressText(float t)
|
|
{
|
|
int percent = Mathf.RoundToInt(t * 100f);
|
|
|
|
if (progressFillImage != null)
|
|
{
|
|
progressFillImage.fillAmount = Mathf.Clamp01(t);
|
|
}
|
|
|
|
if (progressText != null)
|
|
{
|
|
progressText.text = percent + "%";
|
|
return;
|
|
}
|
|
|
|
var textComponent = GetComponentInChildren<Text>(true);
|
|
if (textComponent != null)
|
|
{
|
|
progressText = textComponent;
|
|
progressText.text = percent + "%";
|
|
}
|
|
}
|
|
|
|
private void UpdateLoadingInfo(string message)
|
|
{
|
|
if (loadingInfoText != null)
|
|
{
|
|
loadingInfoText.text = message;
|
|
return;
|
|
}
|
|
|
|
var texts = GetComponentsInChildren<Text>(true);
|
|
foreach (var t in texts)
|
|
{
|
|
if (t != progressText)
|
|
{
|
|
loadingInfoText = t;
|
|
loadingInfoText.text = message;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (openingVideoStartupCoroutine != null)
|
|
{
|
|
StopCoroutine(openingVideoStartupCoroutine);
|
|
openingVideoStartupCoroutine = null;
|
|
}
|
|
|
|
if (videoPlayer1 != null)
|
|
{
|
|
videoPlayer1.loopPointReached -= OnVj01Finished;
|
|
}
|
|
|
|
if (videoPlayer2A != null)
|
|
{
|
|
videoPlayer2A.prepareCompleted -= OnLoopPlayerPrepared;
|
|
videoPlayer2A.loopPointReached -= OnLoopVideoReachedEnd;
|
|
}
|
|
|
|
if (videoPlayer2B != null)
|
|
{
|
|
videoPlayer2B.prepareCompleted -= OnLoopPlayerPrepared;
|
|
videoPlayer2B.loopPointReached -= OnLoopVideoReachedEnd;
|
|
}
|
|
|
|
ReleaseRenderTexture(ref videoTexture1);
|
|
ReleaseRenderTexture(ref videoTexture2A);
|
|
ReleaseRenderTexture(ref videoTexture2B);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
TryAutoAssignVideoClips();
|
|
}
|
|
|
|
private void TryAutoAssignVideoClips()
|
|
{
|
|
bool changed = false;
|
|
|
|
if (vj01Clip == null)
|
|
{
|
|
vj01Clip = AssetDatabase.LoadAssetAtPath<VideoClip>("Assets/op/opVideo/vj01.mp4");
|
|
changed |= vj01Clip != null;
|
|
}
|
|
|
|
if (vj02Clip == null)
|
|
{
|
|
vj02Clip = AssetDatabase.LoadAssetAtPath<VideoClip>("Assets/op/opVideo/vj02.mp4");
|
|
changed |= vj02Clip != null;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
if (gameObject.scene.IsValid())
|
|
{
|
|
EditorSceneManager.MarkSceneDirty(gameObject.scene);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
private void TryAutoAssignVideoClips()
|
|
{
|
|
}
|
|
#endif
|
|
}
|