1. 程式人生 > >Unity減少構建安裝包的體積(210MB減小到7MB)

Unity減少構建安裝包的體積(210MB減小到7MB)

# 概述 ## 專案簡介 由於是公司內做的專案,不方便開源,就只分享優化過程吧。 ### 專案資訊 **逐日**是一個移動端單機小遊戲,使用Unity開發,目前已將專案使用的Unity升級到`2019.4.14f1c1 (3e5991a5f6ba)`版本。 ### 專案內容 在進行優化前,專案資源目錄如下,可以看到,專案目錄命名雜亂,包含很多需求迭代產生的舊資源、無用場景、未壓縮的音視訊等內容。 > 由於這次主要是對於安裝包大小的一些嘗試,所以就不會特別關注遊戲邏輯,整體能載入完成,不Crash就OK,沒有對遊戲邏輯上花費過多精力,後面的過程中可能有小部分圖被壓糊了,有視訊無法載入等問題,不影響縮包的最終結果。 ### 未優化的包體大小 如圖所示,使用新版UnityEditor直接打包之後,生成的遊戲Apk大小為218MB,這對於一個只有兩個關卡的小遊戲而言,明顯過於臃腫了,假如上架到GooglePlay之後,光下載內容所消耗的時間和流量就勸退了一部分使用者(更何況,現在GooglePlay限制了安裝包大小不能大於100M!)。所以,對於安裝包大小的優化是勢在必行的。
# 第一步:Assembly構建優化 ## 使用IL2CPP代替Mono > - Mono使用即時(JIT)編譯,並在執行時按需編譯程式碼。 > - IL2CPP使用提前(AOT)編譯並在執行之前編譯整個應用程式。 使用IL2CPP進行構建有助於提高執行速度,並減少包體大小,IL2CPP的工作流程如下圖: 將ScriptingBackend設定為IL2CPP後,執行構建得到的安裝包大小為210MB,比使用Mono少了8MB(這個專案裡遊戲邏輯不多,指令碼比較少,對於較大的專案來說,使用IL的優勢更大) ## 裁剪更多未使用的程式碼(不是特別推薦) Unity有提供一個`Managed Stripping Level`選項,可以在構建時裁減掉未使用的程式碼,這個選項又是那個可選級別,IL2CPP不能關閉這個選項並且預設級別為`Low`。不同的裁剪級別對應的規則不同,裁減掉游泳的程式碼可能性越高,並且有可能無法檢測到通過反射引用其他程式碼的情況,導致程式崩潰。 設定`Managed Stripping Level=High`後,安裝包大小為209M。
## 關於指令碼構建優化 對於Android Platform,我對於一個空的Unity3D專案進行過如下打包測試: | 構建設定 | ApkSize | | --------------------------------------------- | ------- | | 預設(Mono_Release_DotNetStandard2.0) | 16.9 MB | | ScriptBackend使用IL2CPP | 6.38 MB | | StrippingLevel=High | 6.02 MB | | CompressMethod=LZ4HC | 5.96 MB | | GraphicApi只使用OpenGLES2(Android 2.2+都支援) | 5.73 MB | 再往下就很難減小了...... # 第二步:資源優化 ## 分析資源大小的利器 在Editor中構建完成後,可以開啟EditorLog,裡面列出了在未壓縮的情況下,不同型別資源的佔比以及每個資源體積從大到小的排列,我們可以直接根據Log查詢哪些資源比較大,針對性地處理:
## 移除未使用的資源 > 為什麼把這一步放在資源優化的第一位?因為先去掉無用的資源後面打包會快很多嘛。。。測試一個屬性就要重新打個包有時候甚至需要重新Import整個專案半小時的時間傷不起啊~ 在GitHub上隨便找了個資源清理工具[UnityAssetCleaner](https://github.com/tsubaki/UnityAssetCleaner),對專案中的無用資源進行了清理: 這個步驟直接讓Assets目錄的大小從**`1.27GB`**減小到**`192MB`**,由於資源沒有放在Resource下,所以未引用的資源並沒有打包到Apk中,這個步驟之後僅僅讓包體大小減少到了208MB。 > 但是重新Import專案會快很多啊有木有~!這也看出來當時做的時候走了多少彎路。。。 ## 去掉無用的場景 > 第一次用Unity做遊戲時,關卡切換、各種UI面板都是使用獨立的場景做的,明明可以用Prefab的> _ < 看了下游戲裡的場景,沒有需要刪掉的。。。如果有無用的場景,刪除掉也可以減少包體大小。 ## 紋理尺寸壓縮、視訊音訊質量壓縮 在專案中,儘可能降低圖片和音視訊的質量,使用低質量高壓縮率的壓縮格式,不僅記憶體佔用低、效能也更好;當出現質量不滿足要求時,再逐步的提升尺寸和壓縮格式來滿足需要。[Link→官方文件:不同平臺下支援的壓縮格式](https://docs.unity3d.com/Manual/class-TextureImporterOverride.html) ![image-20201115234438102](Apk%E5%8E%8B%E7%BC%A9%E5%AE%9E%E4%BE%8B.assets/image-20201115234438102.png) 要在不修改原始檔的情況下修改尺寸,可以直接在Unity裡調整: 如上圖所示,遊戲裡的紋理都是按照最大尺寸來匯入的,並且由於使用的是Sprite動畫,導致一個動畫就要使用很多紋理,所以首要的問題是把不同的圖片調整到適當的尺寸,比如場景地圖使用2048或1024,較大的Boss角色和樹木使用512,小怪、小火球等紋理使用256或更小。 音效以及視訊,也按照類似的方法,適當調低解析度和位元速率(視訊其實可以直接放CDN)。 ![image-20201115232657349](https://picgo-1251759020.cos.ap-guangzhou.myqcloud.com/image-20201115232657349.png) ![image-20201115232741122](https://picgo-1251759020.cos.ap-guangzhou.myqcloud.com/image-20201115232741122.png) 調整完之後,重新打了個包,emmm...只有52MB了(當然,有一小部分紋理被我壓糊了,適當調整之後最終結果其實相差不大)。 ## Sprite Atlas 多個NPTO的紋理拼合到一起組成一個POT型的圖集有利於Unity壓縮圖片,更能節省空間。建立方式如下(Unity2019+版本預設不使用給紋理打Tag的方式建立圖集,而是需要手動建立) 將遊戲中大小、用途類似的紋理,新增到各自的圖集中,重新打包,包體大小33M。 ![image-20201122203032593](https://picgo-1251759020.cos.ap-guangzhou.myqcloud.com/image-20201122203032593.png) ## AssetBundle打包 這可能是縮小Apk大小的最好方案了...... ### 總體流程概述 如果前面的一系列縮包方法都不能達到想要的效果,就可以考慮把資源單獨打包拿出來,使用者啟動App時進行下載。 首先在Unity裡給資源打標籤,把資源分到對應的Bundle中。 ![image-20201123000518152](https://picgo-1251759020.cos.ap-guangzhou.myqcloud.com/image-20201123000518152.png) 然後執行構建AssetBundle指令碼(指令碼官網文件有提供),把AssetBundle包輸出到指定路徑。 ### 詳細過程 #### 把場景使用Prefab代替 在優化的過程中我發現,Unity通過BuildSetting中的場景列表來查詢所有依賴的資源,所以為了打出來獨立的AssetBundle包並斷開AssetBundle內的資源與Apk之間的依賴關係,就需要對場景做優化。 將場景中的內容簡單粗暴地整合到一個GameObject內,製作成Prefab,用掛載Prefab的方式代替掛載場景,並刪掉原來的場景,清理BuildSetting中的場景列表。這樣就斷開了AssetBundle內的資源與Apk之間的依賴。 #### 實現入口指令碼 我的辦法是建立一個Init場景作為整個App的入口,裡面只掛一個GameFramework指令碼來執行一系列必要的AssetBundle載入過程、管理各個用於代替場景的Prefab。 這樣修改之後,我們構建出來的Apk就只會包含一個空的場景,以及C#指令碼構建產生的dll,大小最小能有前面所說的5MB!!! #### 打AssetBundle包 前面我們把資源和Apk之間的依賴關係切斷了,但是我們還是要想辦法把它們動態載入回去的,這種方法就是AssetBundle包。 給資源打標籤的過程是非常繁瑣的,所以就使用指令碼自動化處理了,會自動給ResForBundle下的直接子目錄按照目錄名遞迴新增標籤。 運行了BuildAssetBundle命令之後,我們就能在輸出目錄得到一系列相互依賴的AssetBundle檔案了(也包括他們的manifest描述檔案)。 #### 動態載入AssetBundle 我們可以使用Unity提供的AssetBundle Api來動態把AssetBundle檔案載入到記憶體裡,並根據路徑讀取它們包含的資源,由於遊戲內的資源比較少,大約20~30MB,所以我就直接放在StreamingAssets目錄下了(關於這個目錄,是和Resource一樣特殊的存在,感興趣的話可以直接查一下) 在GameFramework啟動時就載入所有的AssetBundle,並掛載Scene_Start.prefab到場景中,這樣我們的遊戲流程又能正常走下去了。 > 當然這個過程中還有好多好多細節要處理: > > - 比如需要用巨集來判斷是在編輯器中還是在移動端,從而採用不同的自定義ResourceLoad來載入資源(如果是編輯器的話直接使用AssetDatabase Api通過路徑載入資源,移動端則採用上面提到的AssetBundle內的資源); > > - 如何遞迴地給指定目錄下的內容新增標籤並自動維護它們; > ### 優化結果 對場景進行精簡優化,只保留一個入口場景載入AssetBundle和載入入口,構建後,Apk大小隻有7M。 打出的AssetBundle可以在App啟動時通過http拉取到本地,如果不嫌資源包大的話也可以直接放在StreamingAssets目錄打到apk內。 # 縮包歷程 直接上圖好了: ![image-20201123000745375](https://picgo-1251759020.cos.ap-guangzhou.myqcloud.com/image-20201123000745375.png) **KeyWorkds:Unity安裝包體積優化,Unity減小安裝包體積,Unity構建更小的Apk,Unity減小構建的安裝包大小,Unity縮小安