【Unity遊戲開發】SpriteAtlas與AssetBundle最佳食用方案

馬三小夥兒發表於2020-12-28

一、簡介

  在Unity步入2019.4以後,新版的SpriteAtlas日趨完善,已經完全可以在商業專案中使用了。但是縱觀網路平臺上,許多關於SpriteAtlas的文章還停留在2018的初版時期,其中許多解釋在現在看來都是過時的,甚至近期UWA問答上的一篇Q&A也是錯誤的結論,傳送門。(筆者文章寫於2020.9月)如果還按照CSDN或者UWA上的這種錯誤的教程來使用SpriteAtlas的話,一來有可能造成圖集和資源的冗餘,二來會導致享受不到新版圖集帶來的開發便利從而影響了效率。因此進行SpriteAtlas和AssetBundle的正確配合使用調研實在必行。

二、圖集的往事今生

1.NGUI和TP時代

  早在NGUI時代就已經有了圖集的概念了,與UGUI先使用後製作圖集的工作流程不同,NGUI是先製作圖集再使用。

先製作圖集再使用的時候,在反覆迭代開發的過程中,圖集打包容易引起衝突。同時,實現規劃的圖集隨著需求的變更可能需要重新規劃,重新規劃以後,又要重新打圖集,之前做好的介面又要重新修復引用,這中間的工作量,經歷過的人都知道。

  對於NGUI這種對迭代開發十分不友好的工作流,UGUI帶來的改進可以說是廣大開發者的福音。

  在訪問NGUI圖集的圖元時,我們需要先載入圖集,然後再從這個圖集中獲取單個圖元,用虛擬碼表示大概是這個流程:

var spriteAtlas = Resources.Load("spriteAtlasName");

var sprite = spriteAtlas:GetSprite("spriteName");

2.UGUI時代的舊SpritePacker和新SpriteAtlas

  和NGUI不同,UGUI訪問圖集的圖元不需要我們主動去載入圖集,再從圖集中獲取圖元。當我們載入圖集的圖元時,圖集會被引擎自動載入,圖集的釋放也是自動完成的,不需要針對圖集編寫任何的業務邏輯程式碼,而有關圖集的處理工作都是放在了資源後處理、資源分組和打包上。

  簡單來說,就是執行時寫的那些業務邏輯不需要關心我這個圖元屬於哪一張圖集,屬於哪一個AssetBundle,直接以散圖的形式去使用、去獲取就可以了,比如程式碼可以寫成下面這樣:

var sprite = Resources.Load("Bag/spriteName");

  UGUI圖集的先使用後製作主要有兩種作業模式,也就是舊的SpritePacker和新的SpriteAtlas模式:

1.舊的SpritePacker模式,給Sprite設定PackingTagTag相同的會被分配到同一個圖集中,如下圖所示

2.新版的SpriteAtlas相比之前的SpritePacker做了更多的優化,比如可以實時檢視圖集的大小,圖集裡面元素的排列布局,並且增加了LateBinding等特性

  我們可以通過Asset/Create/Sprite Atlas去建立一個圖集,圖集建立以後可以通過拖拽的方式去選擇要打包的物件,如下圖所示:

  Objects for Pakcing指定了圖集中需要打包的圖元,可以指定單獨的檔案,也可以指定整個資料夾。

  在新的作業模式中,每一個圖集都是用一個單獨的SpriteAtlas管理起來,圖集的格式也被定義在這個資源中,預覽單張圖集不用像舊版的那樣需要把所有的圖集都集中在一個SpritePacker的編輯器視窗中預覽,而是可以實時在Inspector視窗視覺化預覽。舊版的圖集管理方式在圖集數量多的時候,查詢不方便還非常卡,新版的作業方式是一種分而治之的理念,更為方便和快捷。

  需要注意的,UGUI的圖集,無論新舊,在構建AssetBundle的時候,同一個圖集內的所有圖元都要放在同一個AssetBundle中,否則,如果同一個圖集的圖元被分散到多個AssetBundle中,那麼每一個AssetBundle都會包含一份這個圖集的Copy,最終的結果就是包體冗餘、記憶體膨脹和載入耗時等問題。

  如果是看的CSDN等網路上的教程的話,多半會讓你不要勾選SpriteAtlasInclude In Build選項,說是會造成圖集的雙份冗餘。但是這種說法實際上早就過時了,這個Bug早已經在Unity2018.4.6中修復了,所以我們在使用中放心大膽地勾選Include In Build就好了,這樣也可以避免使用LateBinding

  同樣,如果是看了網上的教程的話,也會發現有一些在使用SpriteAtlas時遇到了白圖或者不顯示的情況,這種情況實際上是對UGUI新圖集的工作流不熟悉導致的。經過測試,只要打包的時候勾選圖集的Include In Build,然後,不需要主動對SpriteAtlas資產檔案進行打包,也不需要寫額外的程式碼,就可以正常使用了,只需要對檔案下下的散圖進行ab打包即可。例項程式碼如下:

string path = "UI/Atlas/MySprite.png"

var sprite = Assets.LoadAsset(path,typeof(Sprite)); //封裝好的載入介面

MyImage.image = sprite;

三、實踐工程

  為了佐證上面的一些結論,這裡特意配置了一份教練工程。

  首先有一個引用了圖集中圖元的UIPrefab,如下圖所示,它上面有三個Image,右邊的兩張Image分別引用了同一個圖集中的圖元,我們用它來驗證圖元是否合批,左邊的Image是引用了另外一個圖集中的圖元,我們用它來對比驗證DrawCall:

 

  然後這裡面有三分SpriteAtlas檔案,它們都勾選了Include In Build 但是不參與打包。這三個圖集分別管理這Sprites目錄下的每個子目錄中的散圖檔案,這些散圖檔案時需要參與打包的(AssetBundle)。

  然後我們進行打ab操作,打出ab以後我們用AssetStudio去驗證ab包的內容,看看它有沒有冗餘情況出現。首先看一下打出來的UIPrefab的ab檔案,prefabs_uiroot.bundle,可以看到儘管UIPrefab引用了圖集裡面的圖元,並且圖集勾選了Include In Build,但是並沒有UIPrefab的ab裡面並沒有冗餘,Unity的確是修復好了這個bug:

 

  然後再來看一下圖集的ab包,因為沒有對.spriteatlas檔案特意的包,所以打包的實際上按目錄劃分的散圖檔案,shared_ui_sprites_game.bundle,可以看到裡面只有Texture和Sprite,也沒有多餘的冗餘檔案出來:

  但是,如果我們故意指定對.spritealtas檔案也打包,特意指定一下,然後我們再看spriteatlas資原始檔打出來的ab,atlas_game.bundle,可以發現裡面有個合併好的512x512的圖集,這個就造成了的冗餘,因為散圖目錄下已經有一份資原始檔了:

  然後我們再看下,只設定了圖集,但是不特意針對圖集進行匯出打包的情況下,UI使用圖集內的圖元,是否還可以正常的合批,DrawCall是否正常。

  通過上面的一些資訊,我們可以看到Unity進行了合批操作,DrawCall為3也是正常的。在FrameDebugger裡面也可以看到引用了同一圖集內的兩張圖元的Image,也是在一個Batch裡面去繪製的。

四、總結

  實際上,通過上面的一系列測試,我們可以得出以下結論,新版的SpriteAtlas可以看做是對舊版的SpritePacker的升級,我們在使用的時候仍然是不需要關注圖集這個東西的,這裡的SpriteAtlas可以看做僅僅是用來作為一種對散圖的歸納與整理。當我們載入圖集的圖元時,圖集會被引擎自動載入,圖集的釋放也是自動完成的,不需要針對圖集編寫任何的業務邏輯程式碼,而有關圖集的處理工作都是放在了資源後處理、資源分組和打包上。簡單來說,就是執行時寫的那些業務邏輯不需要關心我這個圖元屬於哪一張圖集,屬於哪一個AssetBundle,直接以散圖的形式去使用、去獲取就可以了。

簡單來說遵循以下幾點就不會有錯了:

  • 工作過程中(拼接UI等)放心大膽地以散圖的方式去引用UI/Atlas/XXX下的各種圖片即可
  • SpriteAltas檔案需要勾選Include In Build,但是不要特意打包,會造成冗餘和包體膨脹
  • 程式碼中動態載入Sprite的地方,直接使用散圖的資源路徑去載入就可以了,比如:var sprite = Assets.LoadAsset<Sprite>(path);
  • 平時工作的機器上SpritePacker的Mode可以設定為Disable,這樣可以提高效率(開啟的話每次Run之前,圖集會打包,資源多了以後會卡)。但是在打包機上一定要把Mode設定為Enable for build或者Always enable,這樣圖元才能被正確地合批
  • 同一個圖集內的所有圖元打包時都要放在同一個AssetBundle中

 

 

 

如果覺得本篇部落格對您有幫助,可以掃碼小小地鼓勵下馬三,馬三會寫出更多的好文章,支援微信和支付寶喲!

       

 

作者:馬三小夥兒
出處:https://www.cnblogs.com/msxh/p/14194756.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和程式碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

相關文章