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閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容