您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Unity的資源管理是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
在Unity最佳實踐明確指出, 要使用AssetBundle而不是Resources目錄來管理資源。
然而,事情并不像Unity官方描述的那么美好。因為使用AssetBundle我們甚至無法實現一個易用的,完備的資源管理方案。
據Unity官方說,一般有兩種方案。
方案一,如果你的游戲是關卡性質的,可以在一個關卡里加載所有AssetBundle,然后在進入下一關卡時,卸載本關卡中加載的所有AssetBundle. 但這種機制似乎只對憤怒的小鳥這種小游戲才適用吧:D。
方案二,如果你的游戲不是關卡類的,那么Unity推薦做一個資源對AssetBundle引用計數。
如果一個對象(Asset或其他AssetBundle)引用此AssetBundle則其引用計數加1. 如果此AssetBundle首次加載(即加載前引用計數為0), 還需要遞歸對其依賴引用計數加1。
如果一個AssetBundle的引用計數為0則釋放這個AssetBundle,同時還需要遞歸對其依賴引用計數減1.
除非,我們做像憤怒小鳥一樣的通關游戲,不然似乎只有方案二給我們用。而且方案二乍一看是完備的,因為這正是GC算法的一種實現。
但是如果稍微仔細思考一下就會發現,這個方案只是AssetBundle的管理方案,是個半成品,要如何管理管理資源之間的依賴,Unity卻只字未掉,看起來是讓用戶自己想辦法,這似乎與其易學易用的宗旨不太相符。
下面來分析一下Unity中資源之間的關系。
在Unity中資源大約分為以下幾種:紋理(Texture)、網格(Mesh)、動畫片段(AnimationClip)、音頻片段(AudioClip)、材質(Material)、著色器(Shader)、字體資源(Font)以及文本資源(TextAsset)。
AssetBundle中還有一個極其特殊的存在,那就是Prefab, AssetBundle.LoadAsset時返回的是GameObject, 但是又必須經過Instantitate之后變成另外一個GameObject才能使用。此后所說的GameObject均是Instantitate之后的GameObject。
GameObject可以添加各種Component來引用上述除資源,還可以通過代碼動態增減某個GameObject上的Component或者修改Component對資源的引用。這種靈活性給資源管理帶來了巨大麻煩,而沒有這種靈活性,邏輯的實現就會更麻煩。
下面,舉例來說明一下,要正確管理GameObject和資源之間的引用關系有多么艱難。
Prefab P能過Instantitate生成A,B,C,D四個GameObject.
執行如下代碼之后,A引用{P,T1}, B引用{P,T1}, C引用{P,T3}。并且T2應該被Unload。
1: A.GetComponent<SpriteRender>().sprite = (Sprite)T1;
2: B.GetComponent<SpriteRender>().sprite = (Sprite)T1;
3: C.GetComponent<SpriteRender>().sprite = (Sprite)T2;
4: C.GetComponent<SpriteRender>().sprite = (Sprite)T3;
要想自動正確的管理GameObject和資源的引用關系,就必須要感知到對GameObject的賦值操作。
例如:所有的sprite賦值都必須使用類似SpriteAssign(SpriteRender sr, Sprite s)的接口。
SpriteAssign的執行流程通常是這樣的。
檢查sprite的值是不是T1相同,如果是相同則不做處理
檢查sprite的值是不是從P中clone過來的,如果不是,將此sprite的引用計數減1
將T1的引用計數加1
如果P是一個樹狀態結構,即有P–(child)–>p1–(child)–>p2。
1: A.p1.p2.GetComponent<SpriteRender>().sprite = (Sprite)T1;
2: B.p1.p2.GetComponent<SpriteRender>().sprite = (Sprite)T1;
3: C.p1.p2.GetComponent<SpriteRender>().sprite = (Sprite)T2;
4: C.p1.p2.GetComponent<SpriteRender>().sprite = (Sprite)T3;
SpriteAssign接口中的步驟2就顯得格外復雜,它必須修正引用關系如下:A引用{P,T1}, B引用{P,T1}, C引用{P,T3}。
同時Destory操作也要被感知,如果Destory(A)則需要釋放A引用的資源,而如果Destory(A.p1.p2)則需要修正A對資源的引用情況。因為此時的引用關系是,A引用{P}。換句話說Destroy的開銷也會變大。
而賦值和Destory都算不上低頻操作,尤其是賦值操作。這樣的開銷已經足夠讓程序慢上好幾倍了。如果不能承受這些開銷,全自動化資源管理是不可能實現的。
我想這也是Unity不默認提供一套標準的全自動化資源管理方案的根本原因吧。
受方案一的啟發,我覺得可以通過如下接口做一個半自動化的資源管理器。
void level.open();
void level.close();
void level.dispose();
void stack.push() {
level l = new level()
l.open()
push l in to stack
}
void stack.pop() {
pop l from stack
l.dispose()
}
每一個level對象都會記錄在level.open()和level.close()之間所有加載過的資源,加過載多少次就記錄多少次,這些資源會在執行level.dispose()時如實的進行釋放。
其中stack在管理UI資源方面幾乎已經達到了全自動化,當你打開一個UI時調用stack.push,在退出此UI時調用stack.pop會自動釋放在此UI期間你所加載的全部資源。
而在其他不具有棧式加載資源特征的地方,level類也提供了一種方便的半自動化管理方案。
最重要的是,此種方案的開銷和復雜度,都要遠低于全自動化管理方案。
關于Unity的資源管理是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。