Unity中的異步場(chǎng)景批量加載

場(chǎng)景(Scene)是Unity中組織我們的環(huán)境,物品,玩家,障礙等一切游戲相關(guān)的內(nèi)容的地方。我們基本上可以把Scene當(dāng)做關(guān)卡(Level)來理解。

在游戲中基本上我們不會(huì)只有一個(gè)場(chǎng)景,這個(gè)時(shí)候場(chǎng)景之間的切換就會(huì)顯得尤為重要。當(dāng)然,首先我們需要一個(gè)良好的設(shè)計(jì),什么內(nèi)容需要放在同一個(gè)場(chǎng)景下面,什么時(shí)候需要切分到不同的場(chǎng)景內(nèi)部。把所有GameObject都放在同一場(chǎng)景里,顯然不是一個(gè)好辦法,雖然它可以極大程度地避免掉切換場(chǎng)景帶來的消耗,但是隨著場(chǎng)景的內(nèi)容越來越復(fù)雜,可能加載當(dāng)個(gè)場(chǎng)景就會(huì)耗費(fèi)大量的時(shí)間。也就是說,每次玩家打開游戲,為了加載這個(gè)唯一的場(chǎng)景,可能他需要面對(duì)的是漫長(zhǎng)的讀條等待,這和主線程被阻塞沒什么區(qū)別。

當(dāng)然,如果將場(chǎng)景切分得過分細(xì)致,可能原來屬于同一個(gè)關(guān)卡的內(nèi)容被放到了不同的場(chǎng)景下面,這樣帶來的結(jié)果就是要頻繁的切換場(chǎng)景。原本應(yīng)該屬于同一個(gè)場(chǎng)景的內(nèi)容理論上被玩家同時(shí)訪問的概率就應(yīng)該會(huì)比較大,所以需要放在同一個(gè)場(chǎng)景下避免每次訪問都要切換場(chǎng)景。

Unity運(yùn)行中場(chǎng)景的加載由SceneManager來處理。在舊版本的Unity中是使用Application.LoadLevel來進(jìn)行場(chǎng)景的加載。這個(gè)在新版本的Unity中已經(jīng)升級(jí)成為了SceneManager.LoadScene。這個(gè)方法是同步地加載場(chǎng)景,所以如果場(chǎng)景比較大的話,可能會(huì)造成游戲的主線程被阻塞的感覺,LoadScene在運(yùn)行的時(shí)候,我們是無法進(jìn)行其他的操作的,如果場(chǎng)景比較小或者不需要進(jìn)行其他的計(jì)算或者操作的話,可以使用LoadScene來進(jìn)行加載,建議是在場(chǎng)景上面覆蓋一個(gè)進(jìn)度條來提示玩家游戲正在加載中,以免造成玩家認(rèn)為游戲卡住了。

異步加載場(chǎng)景則是使用SceneManager.LoadSceneAsync來進(jìn)行的,異步和同步地區(qū)別就在于異步加載使得游戲能夠在加載場(chǎng)景的同時(shí)進(jìn)行一些其他的運(yùn)算操作。關(guān)于同步和異步的區(qū)別可以參考一下這個(gè)帖子。而Unity又提供了兩種主要的加載場(chǎng)景模式,LoadSceneMode.Single和LoadSceneMode.Additive。

如同其字面上的意思,Single模式就是加載單個(gè)場(chǎng)景,意思是只會(huì)加載一個(gè)場(chǎng)景,其他的場(chǎng)景在此場(chǎng)景被加載之后就會(huì)被銷毀。Additive模式是附加模式,新加載的場(chǎng)景和舊場(chǎng)景附加在一起,所以在場(chǎng)景被加載之后,舊場(chǎng)景不會(huì)消失,所以可以再Hierarchy Window下可以看到同時(shí)有多個(gè)場(chǎng)景存在。

Hierachyview

順帶一提,異步加載場(chǎng)景的語法是SceneManager.LoadSceneAsync(strNameOfYourScene, LoadSceneMode.Additive)。

這樣我們就可以實(shí)現(xiàn)批量加載場(chǎng)景了。如果一個(gè)主場(chǎng)景特別得大,我們可以將其切分成幾個(gè)子場(chǎng)景,然后批量地加載它們,先把最基礎(chǔ)的場(chǎng)景加載出來,其他的細(xì)節(jié)逐步添加進(jìn)來。可能沒有辦法一口氣全部加載出來,但是起碼玩家不會(huì)感到自己被阻塞在游戲加載上面。

必須注意到的是,如果想要批量加載場(chǎng)景,必須將這些場(chǎng)景的加載模式全部設(shè)置為Additive,否則場(chǎng)景就會(huì)一直卡在加載狀態(tài)。

在異步加載場(chǎng)景的過程中,我們可以將allowSceneActivation設(shè)置為false,這樣可以更加穩(wěn)定地來控制場(chǎng)景的激活。SceneManager.LoadSceneAsync返回了一個(gè)AsyncOperation, 通過這個(gè)變量,我們能夠了解場(chǎng)景加載的進(jìn)度。當(dāng)AsyncOperation.progress為0.9f的時(shí)候,場(chǎng)景的加載完成,我們此時(shí)可以設(shè)置allowSceneActivation = true來開始激活場(chǎng)景。當(dāng)場(chǎng)景完全激活,AsyncOperation.isDone變?yōu)閠rue。

具體的實(shí)現(xiàn)上面,我們需要使用到協(xié)程(coroutine)。對(duì)于Unity的coroutine不太了解的話,如果你的英文夠好,可以看看這一篇文章。當(dāng)然百度也能找到不少關(guān)于coroutine的解釋。簡(jiǎn)單來說coroutine就是類似線程的一種存在,但是它比線程更加得輕量,它能夠使得程序暫停一幀的時(shí)間去執(zhí)行協(xié)程,然后再返回原來的位置。注意的是,協(xié)程的返回得使用yield return ***,并且協(xié)程的返回類型是IEnumerator。

下面我們來看看實(shí)現(xiàn)異步加載的方法的代碼的例子:

   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使得這一幀的執(zhí)行結(jié)束,返回調(diào)用這個(gè)協(xié)程asynchronousLoadScene()的地方,這樣能夠使得游戲時(shí)間繼續(xù)進(jìn)行下去,而不是阻塞在一幀的時(shí)間內(nèi)等待場(chǎng)景加載(這顯然是不可能的)。

以上就是非常基礎(chǔ)的一個(gè)場(chǎng)景加載的例子。如果是異步地批量加載場(chǎng)景,則需要用一個(gè)List數(shù)組來保存所有需要加載的場(chǎng)景的名字,以及加載每個(gè)場(chǎng)景的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]);
        }
    }

如果需要銷毀場(chǎng)景,直接調(diào)用SceneManager.UnloadSceneAsync即可。順帶一提,所有正在加載的,已經(jīng)加載的場(chǎng)景,都會(huì)保存在SceneManager里面,有每個(gè)場(chǎng)景的Index和名字,加載信息。

上面就是我這幾天了解到的關(guān)于Unity中關(guān)于場(chǎng)景加載的一些小知識(shí),當(dāng)然還有很多坑等著我們慢慢去探索。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • This article is a record of my journey to learn Game Deve...
    蔡子聰閱讀 3,892評(píng)論 0 9
  • 111. [動(dòng)畫系統(tǒng)]如何將其他類型的動(dòng)畫轉(zhuǎn)換成關(guān)鍵幀動(dòng)畫? 動(dòng)畫->點(diǎn)緩存->關(guān)鍵幀 112. [動(dòng)畫]Unit...
    胤醚貔貅閱讀 13,214評(píng)論 3 89
  • Unity 5.3中新增加了多場(chǎng)景編輯功能,允許用戶將一個(gè)大場(chǎng)景以某種邏輯分割成多個(gè)小場(chǎng)景并方便的編輯和管理。這在...
    好怕怕閱讀 11,194評(píng)論 2 10
  • 親愛的同學(xué),你好,你學(xué)的專業(yè)是你喜歡的么?你學(xué)的專業(yè)是你將來要實(shí)現(xiàn)的夢(mèng)想么?倘若不是,你是在每天煎熬的埋頭學(xué)著自己...
    漪漪麻麻417閱讀 311評(píng)論 1 3
  • 2017年7月28日 聚焦網(wǎng)初五原創(chuàng) 張婷 鄭州 分享第五十六天 這幾天學(xué)習(xí)。腦子全是葫蘆吞棗。 根...
    心愿幸福閱讀 193評(píng)論 0 0