Unity中的异步场景批量加载

场景(Scene)是Unity中组织我们的环境,物品,玩家,障碍等一切游戏相关的内容的地方。我们基本上可以把Scene当做关卡(Level)来理解。

在游戏中基本上我们不会只有一个场景,这个时候场景之间的切换就会显得尤为重要。当然,首先我们需要一个良好的设计,什么内容需要放在同一个场景下面,什么时候需要切分到不同的场景内部。把所有GameObject都放在同一场景里,显然不是一个好办法,虽然它可以极大程度地避免掉切换场景带来的消耗,但是随着场景的内容越来越复杂,可能加载当个场景就会耗费大量的时间。也就是说,每次玩家打开游戏,为了加载这个唯一的场景,可能他需要面对的是漫长的读条等待,这和主线程被阻塞没什么区别。

当然,如果将场景切分得过分细致,可能原来属于同一个关卡的内容被放到了不同的场景下面,这样带来的结果就是要频繁的切换场景。原本应该属于同一个场景的内容理论上被玩家同时访问的概率就应该会比较大,所以需要放在同一个场景下避免每次访问都要切换场景。

Unity运行中场景的加载由SceneManager来处理。在旧版本的Unity中是使用Application.LoadLevel来进行场景的加载。这个在新版本的Unity中已经升级成为了SceneManager.LoadScene。这个方法是同步地加载场景,所以如果场景比较大的话,可能会造成游戏的主线程被阻塞的感觉,LoadScene在运行的时候,我们是无法进行其他的操作的,如果场景比较小或者不需要进行其他的计算或者操作的话,可以使用LoadScene来进行加载,建议是在场景上面覆盖一个进度条来提示玩家游戏正在加载中,以免造成玩家认为游戏卡住了。

异步加载场景则是使用SceneManager.LoadSceneAsync来进行的,异步和同步地区别就在于异步加载使得游戏能够在加载场景的同时进行一些其他的运算操作。关于同步和异步的区别可以参考一下这个帖子。而Unity又提供了两种主要的加载场景模式,LoadSceneMode.Single和LoadSceneMode.Additive。

如同其字面上的意思,Single模式就是加载单个场景,意思是只会加载一个场景,其他的场景在此场景被加载之后就会被销毁。Additive模式是附加模式,新加载的场景和旧场景附加在一起,所以在场景被加载之后,旧场景不会消失,所以可以再Hierarchy Window下可以看到同时有多个场景存在。

Hierachyview

顺带一提,异步加载场景的语法是SceneManager.LoadSceneAsync(strNameOfYourScene, LoadSceneMode.Additive)。

这样我们就可以实现批量加载场景了。如果一个主场景特别得大,我们可以将其切分成几个子场景,然后批量地加载它们,先把最基础的场景加载出来,其他的细节逐步添加进来。可能没有办法一口气全部加载出来,但是起码玩家不会感到自己被阻塞在游戏加载上面。

必须注意到的是,如果想要批量加载场景,必须将这些场景的加载模式全部设置为Additive,否则场景就会一直卡在加载状态。

在异步加载场景的过程中,我们可以将allowSceneActivation设置为false,这样可以更加稳定地来控制场景的激活。SceneManager.LoadSceneAsync返回了一个AsyncOperation, 通过这个变量,我们能够了解场景加载的进度。当AsyncOperation.progress为0.9f的时候,场景的加载完成,我们此时可以设置allowSceneActivation = true来开始激活场景。当场景完全激活,AsyncOperation.isDone变为true。

具体的实现上面,我们需要使用到协程(coroutine)。对于Unity的coroutine不太了解的话,如果你的英文够好,可以看看这一篇文章。当然百度也能找到不少关于coroutine的解释。简单来说coroutine就是类似线程的一种存在,但是它比线程更加得轻量,它能够使得程序暂停一帧的时间去执行协程,然后再返回原来的位置。注意的是,协程的返回得使用yield return ***,并且协程的返回类型是IEnumerator。

下面我们来看看实现异步加载的方法的代码的例子:

   IEnumerator asynchronousLoadScene()
    {
        yield return null;

        AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName, mode);

        ao.allowSceneActivation = false;

        while (!ao.isDone)
        {
            float progress = Mathf.Clamp01(ao.progress / 0.9f);
            Debug.Log("Loading progress:" + (progress * 100) + "%");

            if (Mathf.Approximately(ao.progress, 0.9f))
            {
                Debug.Log("Almost loaded!");
                ao.allowSceneActivation = true;
            }

            yield return null;
        }
        // Callback when scene is loaded
        yield return StartCoroutine(OnSceneLoaded);
    }

其中yield return null使得这一帧的执行结束,返回调用这个协程asynchronousLoadScene()的地方,这样能够使得游戏时间继续进行下去,而不是阻塞在一帧的时间内等待场景加载(这显然是不可能的)。

以上就是非常基础的一个场景加载的例子。如果是异步地批量加载场景,则需要用一个List数组来保存所有需要加载的场景的名字,以及加载每个场景的AsyncOperation:

   IEnumerator BatchLoadingScenes(List<string> namesOfScene)
    {
        List<AsyncOperation> BatchAsynOperation = new List<AsyncOperation>();

        for(int i =0; i < namesOfScene.Count; i++)
        {
            AsyncOperation SceneLoading = SceneManager.LoadSceneAsync(namesOfScene[i], LoadSceneMode.Additive);
            SceneLoading.allowSceneActivation = false;
            BatchAsynOperation.Add(SceneLoading);

            while (BatchAsynOperation[i].progress < 0.9f)
            {
                yield return null;  
            }

        }
        for (int i = 0; i < BatchAsynOperation.Count; i++)
        {
            BatchAsynOperation[i].allowSceneActivation = true;
            while (!BatchAsynOperation[i].isDone)
            {
                yield return null;
            }

            yield return StartCoroutine(OnBatchSceneLoaded[i]);
        }
    }

如果需要销毁场景,直接调用SceneManager.UnloadSceneAsync即可。顺带一提,所有正在加载的,已经加载的场景,都会保存在SceneManager里面,有每个场景的Index和名字,加载信息。

上面就是我这几天了解到的关于Unity中关于场景加载的一些小知识,当然还有很多坑等着我们慢慢去探索。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容