任何高品質遊戲都有一個關鍵要素——有效的資產輸入和輸出記憶體,多年來Unity一直在努力提高使用者計畫的效能。今天我們將來分享一些關於如何利用Unity地址追蹤資產系統來增強使用者的內容載入策略。
記憶體是一種稀缺資源,因此必須小心的進行管理,尤其是在將計畫移植到新平台時。使用地址追蹤系統可以透過引入弱參照來防止載入不必要的資產,從而提高執行時記憶體。弱參照意味著使用者可以控制被參照的資產何時載入到記憶體中以及何時從記憶體中取出;地址追蹤系統會找到所有必要的依賴項,並載入它們。
本篇文章將涵蓋在使用Unity地址追蹤資產系統設定計畫時可能遇到的方案和問題,並解釋如何辨識它們並及時修復它們。
庫存範例
我們將使用一個簡單的範例來進行展示。
在場景中我們建立了一個庫存管理員指令碼,它參照了Unity的三個庫存資產:劍、Boss劍、盾牌偏好設定。
這些資產在遊戲中並不總是需要的。
我們可以使用預覽包記憶體分析器在遊戲執行時檢視記憶體。在Unity 2020 LTS中,則必須首先在計畫設定中啟用預覽包,然後才能從包管理器安裝此包。
而如果你使用的是Unity 2021.1,請在「包管理器」視窗中的附加選單(+)中選擇按名稱添加包的選項。
第一階段 : 硬參照, 無需尋址
讓我們從最基本的實作開始,然後朝著設定地址追蹤內容的最佳方法前進。我們將簡單地套用硬參照(由檢查器的直接分配,被GUID追蹤)到我們的預設中。
載入場景時,場景中的所有物件及其依賴項也會載入到記憶體中。這意味著在我們的清單系統中列出的每一個偏好設定項,以及這些偏好設定項的所有依賴項(紋理、網格、音訊等)都將存在在記憶體中。)
當我們開始構建並使用記憶體分析器拍攝快照時,我們可以看到資產的紋理已經儲存在記憶體中,盡管它們還沒有被例項化。
計畫的紋理儲存在記憶體中,即使它們還沒有被例項化。
存在問題:記憶體中有我們目前不需要的資產。在有大量庫存條目的計畫中,在執行時這將導致相當大的記憶體壓力。
第二 階段 : 實作 地址追蹤
為了避免載入不需要的資產,我們將改變了庫存系統,以使用地址追蹤系統。使用資源參照而不是直接參照可以防止這些物件隨場景一起載入。我們將庫存偏好設定移動到一個地址追蹤組,並使用地址追蹤套用編程介面(API)改變庫存系統來例項化和釋放物件。
構建播放器並拍攝快照。請註意,記憶體中沒有任何資產,因為它們還沒有被例項化。
記憶體中沒有庫存物品紋理;僅有TextMeshPro紋理。
例項化所有計畫,以檢視它們在記憶體中的資產是否正確顯示。
存在問題:如果我們例項化我們所有的物品並銷毀boss劍,我們仍然會在記憶體中看到boss劍的紋理「BossSword_E」,即使它沒有被使用。原因是,雖然可以部份載入資產包,但不可能自動部份解除安裝它們。這種行為對於包含許多資產的包來說尤其成問題,比如包含我們所有庫存偏好設定的單個資產包。在不再需要整個資產捆綁包之前,或者在我們呼叫昂貴的CPU操作資源之前,捆綁包中的任何資產都不會解除安裝。
BossSword_E的紋理即使在boss劍被刪除後依然保留在記憶中。
第三階段: 更小的捆綁包
要解決這個問題,我們必須改變我們組織資產的方式。雖然我們目前有一個單獨的地址追蹤組,它將其所有資產打包成一個資產包,但是我們可以為每個偏好設定建立一個資產包。這些更小的資產捆綁包減輕了大捆綁包在記憶體中保留我們不再需要的資產的問題。
做出這種改變很容易。選擇一個地址追蹤組,然後選擇內容打包和載入——高級選項——捆綁模式,並轉到檢查器將捆綁模式從打包中拆分出來。
透過使用分開包裝要構建這個地址追蹤組,您可以為地址追蹤組中的每個資產建立一個資產資料夾。
資產和捆綁包如下所示:
現在,回到我們最初的測試:找到我們的三個計畫,然後去掉boss劍的紋理。我們會發現boss劍的紋理現在被解除安裝了,因為我們現在把它拆分開來了。
存在問題:如果我們生成所有三個計畫並獲取記憶體捕獲,重復的資產將出現在記憶體中。更具體地說,這將導致紋理「劍_N「」和「劍_D「」產生多個副本。如果我們只改變軟體包的數量,這又能否實作呢?
第四階段: 修復重復的資產
為了回答這個問題,讓我們考慮一下我們建立的三個包中的所有內容。雖然我們只將三個偏好設定資產放入捆綁包中,但是有額外的資產作為偏好設定的依賴項被拉進那些捆綁包中。例如,劍預制資產也有網格、材料和紋理資產等。如果這些依賴項沒有明確地包含在地址表的其他地方,那麽它們會被自動添加到每個需要它們的軟體包中。
我們的劍和Boss劍的資產包包含一些相同的依賴關系。
地址追蹤功能包括一個分析視窗,以幫助診斷軟體包布局。開啟視窗——資產管理——地址追蹤——分析執行規則軟體包布局預覽。在這裏,我們看到劍的包中明確地包括了劍. prefib,但是有許多隱含的依賴關系也被拉入到這個軟體包中。
在同一視窗中,執行檢查重復的捆綁依賴項。該規則基於我們當前的地址追蹤布局突出顯示了多個資產包中包含的資產。
分析表明,劍的軟體包之間有重復的紋理和網格,所有三個包都復制了相同的著色器。
我們可以透過兩種方式防止這些資產重復使用:
1、將劍、boss劍和盾牌偏好設定放在同一個包中,以便它們共享依賴關系
2、在地址追蹤的某個地方明確標明包含重復的資產
我們希望避免將多個庫存偏好設定放在同一個包中,使不需要的資產從記憶體中消失。因此,我們將把重復的資產添加到它們自己的捆綁包(捆綁包4和捆綁包5)中。
重復的紋理被明確地放置在它們自己的包中。
除了分析我們的包之外,分析規則還可以透過以下方式自動修復違規資產修復選定的規則。按此按鈕建立一個名為「重復資產隔離」的新地址追蹤組,其中包含四個重復資產。將該組的捆綁模式設定為分開包裝以防止不再需要的任何其他資產保留在記憶體中。
第五 階 端: 在大型計畫中減少資產捆綁後設資料的大小
使用這種資產捆綁策略可能會導致大規模的問題。對於給定時間載入的每個資產後設資料,都有記憶體開銷。如果我們將當前策略擴充套件到數百或數千個庫存計畫,這些後設資料可能會消耗難以計數的記憶體量。
在Unity的分析器中檢視當前資產後設資料的記憶體開銷。轉到記憶體模組並拍攝記憶體快照。在類別中尋找其他——序列化檔。
目前有1,819個包載入了序列化檔記憶體,總大小為263 MB。
對於每個載入的資產檔,記憶體中都有一個序列化的檔條目。這些記憶體是資產包後設資料,而不是軟體包中的實際資產。這些後設資料包括:
l 兩個檔讀取緩沖區
l 列出包中包含的每個唯一型別的型別樹
l 指向資產的目錄
在這三項後設資料中,檔讀取緩沖區占用的空間最大。這些緩沖區在PS4、Switch和Windows RT上為64 KB,在所有其他平台上為7 KB。在上面的範例中,1,819個包* 64 KB * 2個緩沖區= 227 MB(僅用於緩沖區)。
鑒於緩沖區的數量與資產包的數量成線性比例,減少記憶體的簡單解決方案是在執行時載入更少的包。然而,我們以前避免了載入大的包,以防止不需要的資產留存在記憶體中。那麽,我們如何在保持粒度的同時減少包的數量呢?
第一步是根據資產在應用程式中的用途將它們組合在一起。如果你能根據你的應用程式做出明智的假設,那麽你就可以將那些你知道總是會一起載入和解除安裝的資產分組,比如那些根據它們所處的遊戲級別分組的資產。
另一方面,你可能處於這樣一種情況,你不能對何時需要/不需要你的資產做出安全的假設。例如,如果你正在建立一個開放世界的遊戲,那麽你不能簡單地把森林生物群落中的所有東西都組合成一個資產包,因為你的玩家可能會從森林中抓取一個計畫,並在生物群落之間攜帶它。因為玩家仍然需要森林中的那一個資產,所以整個森林捆綁包會保留在記憶體中。
幸運的是,現在有一種方法可以減少包的數量,同時保持所需的粒度級別。讓我們更明智地對待如何對包進行重復數據消除。
我們執行Unity內建的重復數據消除分析規則可檢測多個捆綁包中的所有資產,並將它們高效地移動到單個地址追蹤組中。透過將該組設定為分開包裝,我們最終會得到一個資產包。然而,有一些重復的資產,我們可以安全地打包在一起,而不會導致引入記憶體的問題。參照下圖:
我們知道紋理「劍_N」和「劍_D」在同一個包(軟體包1和軟體包2)中屬於依賴關系。因為這些紋理有相同的用途和來源,所以我們可以安全地將它們打包在一起,而不會造成記憶體問題。兩種劍的紋理總是同時載入或解除安裝。所以我們永遠不會擔心其中一個紋理可能會保留在記憶體中,因為永遠不會有我們專門使用一個紋理而不使用另一個紋理的情況。
在這個簡單的計畫中,我們只是將包的總數從七個減少到五個。但是想象一下這樣一個場景,您的應用程式的地址追蹤資產中有數百、數千甚至更多的重復資產。如:Unity與Unknown Worlds Entertainment公司合作,為他們的遊戲Subnautica提供專業服務,在使用內建的重復數據消除分析規則後,該計畫最初共有8,718個包。在套用自訂規則,根據已消除重復數據的資產包的父項對其進行分組後,我們將其減少到5,199個包。
這意味著包的數量減少了40%,同時包中仍然有相同的內容,並保持相同的粒度級別。軟體包數量減少了40%,執行時序列化檔的大小也減少了40%(從311MB到184MB)。
結論
使用地址追蹤功能可以顯著降低記憶體消耗。透過組織資產包以適套用例,你可以進一步減少記憶體的使用量。畢竟,為了適合所有應用程式,內建的分析規則是保守的。透過編寫你自己的分析規則還可以實作自動化軟體包布局而且你也可以繼續最佳化它。為了找到更多記憶體的問題,你需要繼續經常進行概要分析,並檢查分析視窗,以檢視哪些重復資產被顯式和隱式地包含在軟體包之中。