在start函数中执行IEnumerator协程函数.
在协程函数中异步加载场景 AsyncOperation op=SceneManager.LoadSceneAsync(),并设置op.allowSceneActivation = false使场景加载完毕后不立即显示,此时op.progress最多加载到90%,等op.allowSceneActivation = true后op.progress再加载后10%.
问题是:op=SceneManager.LoadSceneAsync()这句会导致Loaing界面的卡顿(打印消息和特效粒子都卡住很久才显示).
求助:我希望能避免这个卡顿(毕竟异步和协程就是为了减少卡顿的,但它还是卡),所以我怀疑可能是我没用好协程和异步,导致异步没有生效.有没有知道原理的大神,求大神解说以下,或者推荐一下比较有用的文章.(还是说异步只能把卡顿转移到某一个画面,要想完全不显示卡顿,只能用静态界面.)
资料:
1.我查看了一些文章,有些说异步LoadSceneAsync()并非真正的后台载入,它在每一帧载入一些游戏资源,并给出一个progress值,所以在载入的时候还是会造成游戏卡顿.基于此原理,我觉得我的异步可能并没有生效,因为它在一开始就卡顿很久.(而后面进度条显示由于写了延时器所以都比较流畅.)
2.资源会在start函数开始时加载,如果没有分包,它的加载流量是虎头蛇尾的形式,它会在第一帧就能加载多少就加载多少,多到导致卡顿.(这就是我这个问题的真正原因).
3.关于协程IEnumerator,并不是异步,即使使用了协程,其实还是单线程(因为Unity基本上都是单线程,若出现多线程会导致Unity很多原生功能出现线程不安全的问题),只不过把中间帧抽出来做另一个任务,所以实质是还是同步.
4.我还发现在异步加载时,Unity编辑器暂停也不管用,后台会继续执行异步内容(Loaing界面是暂停的),异步加载完后还会自动转场景.(异步一旦开始是不是就无法停止了)
5.降低异步加载优先级,使Application.backgroundLoadingPriority = ThreadPriority.Low,尽量不影响帧率.异步加载包括:资源.LoadAsync/AssetBundle.LoadAssetAsync/AssetBundle.LoadAllAssetAsync, 场景sceneManager.LoadSceneAsync.
6.Unity官方专家给的解释:
在 Unity 中实例化操作(Instantiate),以及资源的初始化(如 Texture.AwakeFromLoad/Shader.Parse)等都不是异步的,会导致主线程的卡顿。Application.LoadLevelAsync 同样会在 Load 操作完成后自动进行资源的初始化和物件的实例化。在这个过程里就无法保持当前的帧率了。
因此,能否提供一下具体的 profiler 截图,看加载中卡在了什么地方,如果第二帧就加载完,很可能是在第一帧里就已经完成了 load 操作,而第二帧里是在做实例化等卡住主线程的操作。
我现在没办法解决这个问题,所以一开始就把进度先从0随机显示某一个位置,并结合几秒延迟创建异步加载场景,播放一会儿粒子效果,假装是加载过程中随机的卡顿,而不是创建异步的卡顿.(但我明白这治标不治本)
顺便说一下我是怎么测出"op=SceneManager.LoadSceneAsync()"这句会导致卡顿的,因为我把它放到哪里哪里就卡.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Loading : MonoBehaviour
{
public Slider m_Slider;
public Text m_Text;
private ParticleSystem particle;
private AsyncOperation op =null;
private int startShowProgess=5;
void Awake()
{
particle=GameObject.Find("Particle").GetComponent<ParticleSystem>();
particle.Play();
}
void Start()
{
SetLoadingPercentage(startShowProgess);
StartCoroutine(loadScene()); //该协程会导致加载卡顿
}
string GetLoadName()
{
string loadName="";
GameObject LevelRecordCreater=GameObject.Find("LevelRecordCreater");
if (LevelRecordCreater)
{
int nowlevel=LevelRecordCreater.GetComponent<levelUpdata>().level;
if (nowlevel == 1)
{
loadName = "level1";
}
if (nowlevel == 2)
{
loadName = "level2";
}
if (nowlevel == 3)
{
loadName = "level3";
}
}
return loadName;
}
IEnumerator loadScene()
{
yield return new WaitForEndOfFrame(); //加上这么一句就可以先显示加载画面然后再进行加载
int displayProgress = startShowProgess;
int toProgress = (int)(Random.value*88); //假随机卡在某个数
//假进度前88%
while (displayProgress < toProgress)
{
SetLoadingPercentage(++displayProgress);
yield return new WaitForEndOfFrame();
}
yield return new WaitForSeconds(3); //把卡顿推后,假装是进度中的,防止一开始玩家就跑了<过半理论>
op = SceneManager.LoadSceneAsync(GetLoadName()); //这个创建还是会导致卡顿
op.allowSceneActivation = false;
//真进度后88%~90%
while (op.progress < 0.9f) //op.allowSceneActivation = true至前,op.progress最大值为0.9
{
toProgress = (int)op.progress * 100;
while (displayProgress < toProgress)
{
SetLoadingPercentage(++displayProgress);
yield return new WaitForEndOfFrame();
}
}
//假进度后90%~100%
toProgress = 100;
while (displayProgress < toProgress)
{
SetLoadingPercentage(++displayProgress);
yield return new WaitForEndOfFrame();
}
op.allowSceneActivation = true;
}
void SetLoadingPercentage(int sliderProgress)
{
m_Slider.value = sliderProgress * 0.01f;
m_Text.text = "加载资源 "+sliderProgress.ToString() + "%";
}
}