UI换了很多,spine主视觉停用

This commit is contained in:
2026-06-15 20:17:25 +08:00
parent 5eec879582
commit 216ab08e8d
3634 changed files with 814422 additions and 382766 deletions
+490 -157
View File
@@ -1,28 +1,24 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Spine.Unity;
using UnityEngine.Video;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
/// <summary>
/// Throttled preload: uses queues and concurrency limits to load scenes and Resources in small time slices
/// Throttled preload: uses queues and concurrency limits to load 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
/// 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")]
public GameObject spineObject1;
public GameObject spineObject2;
public CanvasGroup spineObject1CanvasGroup;
public float spineObject1DestroyDelay = 6.7f;
public float spineObject1FadeDelay = 5.7f;
public float spineObject2PlayDelay = 0f;
[Header("UI references")]
public Text progressText;
[Tooltip("Optional Image whose fillAmount will be driven by the overall progress (0..1)")]
public Image progressFillImage;
@@ -31,6 +27,11 @@ public class preload_allScene : MonoBehaviour
[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;
@@ -39,27 +40,30 @@ public class preload_allScene : MonoBehaviour
[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 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;
void Awake()
private void Awake()
{
Debug.Log("[preload_allScene] Awake called. Forcing Time.timeScale = 1.");
Time.timeScale = 1f; // Ensure time is running so fades and DOTween work
Time.timeScale = 1f;
// 确保物体 1 初始处于关闭状态,以便 1 秒后延迟开启
if (spineObject1 != null)
{
spineObject1.SetActive(false);
}
EnsureOpeningVideoSetup();
SetVideoDisplayVisible(openingVideoRawImage, false);
// if UI not assigned, try to auto-find Text components
if (progressText == null)
{
progressText = GetComponentInChildren<Text>(true);
@@ -67,7 +71,6 @@ public class preload_allScene : MonoBehaviour
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)
{
@@ -80,28 +83,12 @@ public class preload_allScene : MonoBehaviour
}
}
void Start()
private 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());
}
StartOpeningVideoSequence();
if (spineObject2 != null)
{
// Start 时显式禁用物体 2,确保初始状态正确
spineObject2.SetActive(false);
// 延迟播放物体 2 的动画
StartCoroutine(DelayedPlaySpine2Logic());
}
// 1 秒后禁用指定的 Image
if (startDisableImage != null)
{
StartCoroutine(DelayedDisableImage());
@@ -110,6 +97,113 @@ public class preload_allScene : MonoBehaviour
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);
@@ -120,117 +214,292 @@ public class preload_allScene : MonoBehaviour
}
}
private IEnumerator DelayedPlaySpine1()
private VideoPlayer CreateVideoPlayer(string objectName, VideoClip clip, bool loop, ref RenderTexture renderTexture)
{
// 等待 1 秒
yield return new WaitForSeconds(1f);
if (spineObject1 != null)
if (clip == null || openingVideoRawImage == null)
{
spineObject1.SetActive(true);
var sg1 = spineObject1.GetComponentInChildren<SkeletonGraphic>(true);
if (sg1 != 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)
{
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());
openingVideoRawImage.texture = nextPlayer.targetTexture;
}
}
}
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)
if (!nextPlayer.isPlaying)
{
state2.SetAnimation(0, "loop", true);
nextPlayer.Play();
}
activeLoopPlayer = nextPlayer;
standbyLoopPlayer = finishedPlayer;
if (standbyLoopPlayer.isPlaying)
{
standbyLoopPlayer.Stop();
}
standbyLoopPlayer.Prepare();
return;
}
source.Play();
}
public void StartPreload()
{
if (preloadCoroutine != null) return;
if (preloadCoroutine != null)
{
return;
}
// fill queues
resourceQueue.Clear();
if (resourcesToPreload != null)
{
foreach (var r in resourcesToPreload)
if (!string.IsNullOrWhiteSpace(r)) resourceQueue.Enqueue(r);
{
if (!string.IsNullOrWhiteSpace(r))
{
resourceQueue.Enqueue(r);
}
}
}
Debug.Log($"Preload starting. Resources queued: {resourceQueue.Count}");
// ensure UI shows 0%
UpdateProgressText(0f);
UpdateLoadingInfo("");
UpdateLoadingInfo(string.Empty);
// If nothing to preload, just show 100% and fade out immediately
if (resourceQueue.Count == 0)
{
UpdateProgressText(1f);
@@ -252,12 +521,10 @@ public class preload_allScene : MonoBehaviour
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();
@@ -278,10 +545,12 @@ public class preload_allScene : MonoBehaviour
Debug.Log($"Preload: started resource load '{path}'");
}
if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break;
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];
@@ -289,36 +558,36 @@ public class preload_allScene : MonoBehaviour
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;
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;
@@ -330,25 +599,25 @@ public class preload_allScene : MonoBehaviour
int items = 0;
items += resourceQueue.Count + activeResourceReqs.Count;
if (items == 0) return 1f;
foreach (var (path, rr) in activeResourceReqs)
if (items == 0)
{
sum += rr.progress; // 0..1
return 1f;
}
return Mathf.Clamp01(sum / (float)items);
foreach (var (_, rr) in activeResourceReqs)
{
sum += rr.progress;
}
return Mathf.Clamp01(sum / 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);
}
@@ -358,16 +627,14 @@ public class preload_allScene : MonoBehaviour
return;
}
// If no UI assigned, try to find any in children and cache it
var tcomp = GetComponentInChildren<Text>(true);
if (tcomp != null)
var textComponent = GetComponentInChildren<Text>(true);
if (textComponent != null)
{
progressText = tcomp;
progressText = textComponent;
progressText.text = percent + "%";
}
}
// helper to update loading info text
private void UpdateLoadingInfo(string message)
{
if (loadingInfoText != null)
@@ -376,7 +643,6 @@ public class preload_allScene : MonoBehaviour
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)
{
@@ -388,4 +654,71 @@ public class preload_allScene : MonoBehaviour
}
}
}
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
}
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 26613b5040884aa4486eff889fdf0eea
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 2
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a88f50e3064ffd54ba9d1ddf36f5d729
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
fileFormatVersion: 2
guid: 1c27fc5c2d1c39347874aad933096b27
VideoClipImporter:
externalObjects: {}
serializedVersion: 3
frameRange: 0
startFrame: -1
endFrame: -1
colorSpace: 0
deinterlace: 0
encodeAlpha: 0
flipVertical: 0
flipHorizontal: 0
importAudio: 1
targetSettings:
:
enableTranscoding: 1
codec: 1
resizeFormat: 0
aspectRatio: 0
customWidth: 1920
customHeight: 1080
bitrateMode: 2
spatialQuality: 2
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
fileFormatVersion: 2
guid: e2fac5aa18afc054bb4d5d526ee66c5c
VideoClipImporter:
externalObjects: {}
serializedVersion: 3
frameRange: 0
startFrame: -1
endFrame: -1
colorSpace: 0
deinterlace: 0
encodeAlpha: 0
flipVertical: 0
flipHorizontal: 0
importAudio: 1
targetSettings:
:
enableTranscoding: 1
codec: 1
resizeFormat: 0
aspectRatio: 0
customWidth: 1920
customHeight: 1080
bitrateMode: 2
spatialQuality: 2
userData:
assetBundleName:
assetBundleVariant: