#
最近因為在搞游戲地形的關系,又想起id Tech5的地形技術,以前就看過了一些視頻,知道了大概,但一直不是很清楚其基本的實現原理,于是搜索一番,還是有些收獲的
首先,來看看wikipedia上面對于MegaTexture的定義:
http://en.wikipedia.org/wiki/MegaTexture
MegaTexture refers to a texture allocation technique facilitating the use of a single extremely large texture rather than repeating multiple smaller textures. It is featured in Splash Damage's game, Enemy Territory: Quake Wars and was developed by id Software technical director John Carmack.[citation needed]
MegaTexture employs a single large texture space for static terrain. The texture is stored on removable mediaor the hard drive and streamed as needed, allowing large amounts of detail and variation over a large area with comparatively little RAM usage.[citation needed]
Then during rendering, required parts of the texture space are streamed inside dynamically (re-)allocated textures in video memory, scaled to the correct mipmap level(s) depending on the polygon size. This allows the engine to reduce the number of texels in VRAM/number of pixels on the screen ratio (the goal being getting closer to 1), saving memory.
The upcoming games Doom 4 and Rage, powered by the id Tech 5 engine, use textures that measure up to 128000×128000 pixels[1].
id Tech 6 will use a more advanced technique that virtualizes further both the geometry and the texels (texture points): Voxel Spare Octree (VSO). This works by not using geometries anymore (triangles and textures) but by instead storing colored 3d points in an octree. The goal being to be able to stream parts of the octree, going further down along the tree for nearby objects to give them more details, and to use higher level, larger voxels for further objects, which give an automatic level of detail system for both the geometry and the texture space at the same time. Despite most Voxel rendering tests end using very large amount of memory (up to several Gb), John Carmack claimed he's able to compress such VSO to 1 byte per voxel.
翻譯的大意:
MegaTexture使用一張貼圖來表現整個地表的像素外觀,通過動態定位和載入所需的該貼圖的局部的合適mipmap,來節約顯存開支(這里說到選擇mipmap level是基于polygon size的,不是很清楚實際的做法,估計可能是基于地形自身的polygon,但考慮上地形polygon可能會有的LOD,情況就比較復雜)
Doom4和Rage都將使用最大為128000x128000的MegaTexture(這說明Doom4可能會有大量的室外場景?)
id Tech6會使用一種叫做VSO的更為先進的技術,同時虛擬場景幾何和圖素(關于VSO,完全不懂鳥),這意味著將不再使用傳統的場景幾何(三角形+貼圖),而是通過octree儲存彩色化3D點(這句話我自己也無從了解,所以翻譯僅供參考了),目標就是動態載入octree的局部,對近處的物體,沿著此樹往下以獲得更多細節,而對于遠處的物體,則使用更高級別、更粗大的voxels(現查,voxels即volumetric pixels,可以理解為三維像素),這樣就給場景幾何和貼圖同時提供了LOD系統。盡管多數的Voxel渲染測試都反映需要很大量的顯存(幾個G),卡馬克表示他已經能夠把每個三維像素壓縮到1字節
注:下面提到的姚勇的pdf里面,說到Doom3里已經有MegaTexture的實現了(沒有另外去查詢考證),尺寸最大32768x32768,不使用傳統LOD和地形的Geometry Morphing,可見卡馬克同學還是比較“激進”的,那么多年前就已經搞這個,和UE3(地形LOD+Morphing)果然是截然不同的思路阿
通過google搜索,發現了另一篇好文,作者是姚勇(很多人應該知道的八):
http://blog.csdn.net/puzzy3d/archive/2007/08/16/1746589.aspx
點這個頁面里的pdf連接
這個pdf寫得很好啊,我這樣的小白一看就基本明白MegaTexture背后的實現原理了,就是基于一種叫做clipmap的技術,clipmap顧名思義,就是把map的一部分clip下來用,每次更新需要的局部
關于clipmap,據說是SGI在1998年的時候最先發表的(我沒去考證),這里有SGI關于此技術的文章一枚:
http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&srch=&fname=/SGI_Developer/Perf_PG/sgi_html/ch15.html
這文章前半段還可以看看,對于非圖形程序員的普通群眾來說,看起來恐怕都會和我一樣累
如果不愿意看這篇文章,你還可以去下載SGI當年那篇文章的pdf:
The Clipmap:A Virtual Mipmap
另外,clipmap這類原理的技術,在3d虛擬和大規模地形系統上(比如google earth),好像也頗為有用,關于google earth,最近找到一篇很贊的文章,打算花點功夫翻譯出來(希望還沒人干過這件事情)
特地又重看了一下Tech 5的引擎演示, 這次的感覺就和以前不一樣了,MegaTexture如果只是一個換了名字的clipmap,那其實也沒啥好說的,Doom3里面也有,但沒見id以前怎么力推過。之所以tech 5要主推MegaTexture,還是因為有了一套為其配合的高效制作工具。
其實clipmap技術和傳統基于tiling的技術注定要在制作上走兩條完全不同的路子。tiling技術的思路就是,我只做局部,然后讓局部不斷重復產生出整體,EPIC到UE3這一代為止,就是努力貫徹這種思路,所以不光是貼圖可以tiling,連模型也強調模塊化和拼接(當然模塊化的涵蓋比tiling要大一些)。這種思路的優點就是制作效率高、資源利用率高、資源存儲節約,缺點么,就是視覺上容易重復,制作上需要考慮避免突兀和不均勻(尤其是貼圖,tiling貼圖往往會導致趨向于均勻和缺乏反差從而無從表現個性化細節),在動態載入和LOD上,只能用一些較土的手段,總體就是不適合做富有變化的大規模地形。而clipmap,說白了,就是每一個像素都可以特殊處理,都不會因此而影響運行效率,且天生對動態載入和LOD有很棒的支持,對內存和顯存的消耗還好,對外存的需求就很大了,但畢竟外存的容量發展比較快。但clipmap的問題是,如果每一個像素都要制作,那么制作上的工作量就極大,所以使用此類技術的游戲,應該都會想辦法開發一些減輕工作量的工具,據Porky同學說WIC是“先用地形系統根據高度坡度等生成自然貼圖和lightmap, 公路和deco會往上投射公路和爆炸坑等貼圖,然后導出到PS里手畫其他一些東西,比如平地上的沙坑啥的”,用PS畫是一種方式,但問題是畢竟導進導出麻煩且不直觀,而tech 5的工具所提供的,就是一套內嵌的"PS",讓美工直接在場景里面繪制,那些stamp,其實很像PS里面的各種不同造型的筆刷,然后tech 5編輯器還提供這些“筆刷”繪制后的互相的疊加效果(這其實就是PS里面圖層之間的混合模式),所以,tech 5的MegaTexture,好玩就好玩在這里,感覺在編輯器層面,那個“大貼圖”就是一個巨大的PSD文件阿
所以id和EPIC的確是個性很不一樣的公司,我特期待EPIC會把UE4設計成什么樣子
http://lichong.blogbus.com/tag/clipmap/
Unity3D是一個跨平臺的開發工具,支持的平臺五花八門,常常開發一款游戲要發布到不同的平臺,在不同的平臺上會使用不同的代碼,難道要我們各平臺分別使用一套代碼,單獨編譯一次嗎?當然不用了。
Unity3D有一個功能叫平臺依賴編譯(Platform Dependent Compilation),它可以讓我們簡單地使用if...else...對不同平臺的代碼進行區分,當我們切換一個發布平臺重新編譯時,Unity3D使用自動編譯相應代碼,從而省去了繁瑣的操作。
下邊舉一個例子:
function Awake() {
#if UNITY_ANDROID
Debug.Log("這里是安卓設備^_^");
#endif
#if UNITY_IPHONE
Debug.Log("這里是蘋果設備>_<");
#endif
#if UNITY_STANDALONE_WIN
Debug.Log("我是從Windows的電腦上運行的T_T");
#endif
}
那么其它的平臺怎么判斷呢?請見官方手冊。
要提醒一下的是,手冊里還有對Unity3D版本的判斷方法,和上邊一樣的方法哦!
另外Application.isEditor 和Application.isWebPlayer 也可以判斷程序是否是在Unity3D IDE里運行,或者是否在WebPlayer里運行的。
原文鏈接:http://bbs.9ria.com/thread-173907-1-1.html
昨日gamelook曾就某投資人把移動團隊失敗原因之一歸于選擇Unity引擎進行了一番評論,工具本身無罪,但如何理解工具、正確使用Unity引擎確實需要討論,在選擇Unity之前你或許需要了解下這個引擎實際開發過程中的技術特點、以及適應的游戲產品類型,gamelook熱心讀者Fxcarl昨天就這個問題專門撰文一篇,來幫助大家了解Unity游戲開發、分享心得,推薦閱讀。
文/FXCarl
代碼驅動帶來的技術題
游戲碎片化。U3D 引擎有個很有力的特色,就是實時編譯運行。這意味著無論在任何時候,只要按下運行圖標,當前的場景就會進入可執行狀態。這導致了游戲在開發的過程中經常陷入一種不應當的自信狀態。同時也導致了游戲內容長期處在碎片狀態下,并低估游戲功能整合時可能遇到的困難。
資源管理是 U3D 引擎的一個難點。U3D 的資源管理系統因為跨平臺的緣故和操作系統的文件系統是脫鉤的,需要熟練的掌握 Resources 目錄和 Assetbundle 的技術才能靈活的控制游戲中的資源使用情況。但這一工作時常會被簡單的理解為將資源放置在游戲工程目錄下,剩下的交給引擎自己搞定 ……
需要自己做數據系統。我們如今國內研發的作品,絕大多數是數據密集型(策略、經營、卡牌、KRPG),這和 Temple Run 這樣的游戲類型有些不同。數據密集型的游戲需要采用數據驅動的形式來進行游戲的設計和開發,但是 U3D 提供的框架是一個代碼驅動型的結構(對于原型開發來說極為有力)很多時候會讓研發團隊陷入泥潭 —— 看起來功能開發出來了(只要在U3D的對象檢查器里調調參數就能工作),卻遲遲無法進入大規模制作階段(策劃拿著數據表格卻無法應用到游戲里)。U3D 引擎本身也沒有提供任何在數據方面的支持,數據表要么需要自行處理,要么需要自己尋找嵌入式的數據庫解決方案。
網絡連接部分其實也是類似。U3D 本身集成的網絡模塊并不是為大規模 C/S 結構的游戲所設計,常需要自行開發一套客戶端和服務器結構。當然也可以求助中間件來解決 …… 但是容易讓人迷惑的地方在于,U3D 既可以使用 .net 的網絡機制像端游一樣工作,也退一步可以用加密的 www 機制,當一個簡單的頁游來處理。如何抉擇是個難題,貿然貪多求全往往換來遙遙無期。
測試 U3D 開發的游戲亦一個很麻煩的過程。原因也是那個幾乎不會崩潰,隨時可運行的場景/邏輯混成編輯器 —— 它會讓開發團隊誤算自己當前的游戲完成度,以及需要什么樣的測試。
尖端技術帶來的麻煩事
高精尖的動態光照和復雜材質系統。U3D 比起其他的移動平臺或者網頁游戲開發工具而言,往往最打動人的就是其無與倫比的畫面渲染效果。但是在光鮮的官方演示背后,仿佛總有看不到的壁壘阻礙著其他開發者的步伐。實際上駕馭 U3D 所需要的能力是超乎一般想像的。U3D 的渲染架構的確夠強大,完成 Unreal 甚至 CryEngine 級別的畫面渲染質量都是可能的,但是它并沒有包裝這些系統而是將靈活性交給了開發者。我們的程序員是否已經控制住了渲染管線的復雜度?我們的技術美術是否可以指導我們的美術完成充分發揮 U3D 能力?美術制作人員是否有具有勝任所謂“次世代”精度要求的游戲內容制作?這些東西屬于小團隊嗎?
全局光照烘培。這是一個非常非常非常實用的 U3D 功能。理應所有的 U3D 團隊都靈活使用。但是想要用好就有了另外一番難度 —— 美術和場景制作人員的配合,而誰來負責就比較難說了。另外美術必須用非常精準的尺寸來制作場景中的物件,否則 U3D 將無法正確的處理全局 UV。
工具鏈帶來的紛紛擾擾
GUI 系統的各種理論。所有人都在吐槽 U3D 自帶的 GUI 系統太慢 —— 問題是真的有證據嗎?一方面很多人說我做測試的時候做了一大堆的控件,的確很慢。另外一方面大家也會發覺 GUI 系統會帶來一些不必要的渲染請求(Draw Call)。于是大家都在拼了命的做兩件事情,一個是減少渲染請求,一個是想盡一切辦法的避開 GUI。但其實情況沒那么嚴重,無論是挑選替代中間件如 NGUI 還是直接使用 U3D 的 UI 系統都不會巨大的影響 —— 除了不當使用之外極少見到 GUI 成為性能焦點的時候。不過無論是 NGUI 還是 U3D 內置 UI 都沒有很好的 UI 工具 —— 要么過于程序員導向,要么過于偏向布局而不方便增加代碼功能。內部開發一些擴展工具或者工作流程都很有必要。
版本控制的難題。Asset Server 還是 SVN 其實多多稍稍都有不適應 U3D 的情況。但是更關鍵的地方在于整理好文件的內部結構以及經常備份。恰當的使用 U3D 的命令行模式可以實現 U3D 工程的自動編譯發布。
擴展 U3D 本身功能的能力。因為 U3D 較為完整的功能而忽視對 U3D 本身的功能拓展是一種常見狀態,隨時保持專人不斷的優化 U3D 本身的功能是非常重要的,譬如各種各樣的批量化操作等等。但是這有個前提,擴展工具需要充分理解工具,U3D 相對來說功能過于強大,以至于很多團隊中的成員會害怕學習,而將 U3D 作為少數團隊成員或專屬于程序員的工具 —— 這就很成問題了。
需要前瞻性的判斷能力
每一個,每一個國內開發 U3D 游戲的團隊都在抱怨 U3D 的中文字體支持問題等等。可是實際上真正用前瞻性的角度在使用 U3D 引擎的團隊并不多 —— 以今天此時此刻為例,U3D 4.0 已經可以在任何平臺上使用動態的字體,支持 Unicode 編碼 —— 中文不在話下。從 U3D 3.5 遷移到 4.0 幾乎不用對項目做任何的修改,而如果說之前并不知道 4.0 會支持動態字體的話,那么為什么不多去官方論壇關注一下每個版本的開發進度情況呢?每一個在 2013 才會發布的游戲都不應該擔心字體問題才對嘛 ……
保持對每個版本 U3D 更新內容和未來 U3D 功能的關注可以大量減少重新發明輪子的問題,也能在遇到一些困境時保持更好的心態。直接郵件開發者也會是個很好的選擇,請一定要多騷擾他們!一般提前3個月到6個月就能獲知將來版本可能更新的內容的。
1 “Code-Driven”
State Management
Assets Management
Data Management
Networking
Testing
2 CuttingEdge Techs
Dynamic Lighting & Complex Materials (Textures)
Lightmapping
Nav mesh
Mecanim
DX11
3 Toolchain
GUI
VersionContorl
4 Vision
原文地址:http://game.zol.com.cn/354/3543149.html
當我們開發一個大型項目的時候-會遇到這樣的問題(地形場景的切換)這個只是字面意思-并不是重場景1的100 100 100坐標一下切換到場景2的100 100 100坐標這樣的方法--(如果您以為是這樣的技術和代碼)那就不用看了。這個技術的實質意義是為了解決--多地形場景帶來的大量內存占用問題-舉個我的例子-我的測試項目是1013張繪制地形--在不用上面的技術情況下-占用了我4.2G的內存。想想下-如果一個大型的游戲直接這么運行的話)一下就會帶來5-8G的內存占用--這個游戲還能玩嗎?
下面讓我們來研究實現的方法和代碼
涉及到幾個U3D函數:(引用高人的文檔介紹)
Application.LoadLevel(lv) 場景讀取(記著用多場景前要現在File-Build Setting里登記下場景,才能在腳本里讀取到) DontDestroyOnLoad(object) 保持物體在場景切換的時候不被卸載(能保持他的所有屬性哦)
*AssetBundle 類 預讀資源(主要用于web3d,運行時實時從服務器下載需要的場景資源)
涉及到幾個基礎知識:
static 靜態類、靜態變量:在整個游戲中都不會被重新加載,所以可以當全局全場景變量使用,主要用于記錄場景數組。
Collider的Is Trigger屬性:設置成True,他是可以穿越不會產生能量傳遞的,但是,他是可以接受碰撞偵測的。配合主角的OnTriggerEnter事件,就可以知道你是否正在穿越一個Trigger了。在這里,我們用在判斷何時加載新場景上。
2.制作場景邊界
使用Cube + IsTrigger=True屬性是最好的辦法。
1)如果是雙場景切換,注意兩個場景邊界坐標別重在一起,不然你走到邊界會發現兩個邊界不停的切換=.=,要讓2個場景邊界互相交錯一起(做的時候你自然會明白)。有必要的話,邊界可以往里面縮一點(甚至可以吧場景重疊1/3,但這樣兩個場景你要做很多重復的東西),避免用戶看到邊界。然后建議是把Cube
3.編寫腳本
好了,現在可以開始寫腳本了,我一如既往的很懶,所以不會吧全部代碼粘帖出來 。(我會別看到這里不看了,這是以前高人寫的)
先理清楚邏輯關系以及一些常識:
1.是主攝像機走到邊界才會做場景加載或卸載動作。所以代碼是放在主攝像機上或者主角上。
2.場景可以加載,但是沒有卸載場景這個東西(也沒必要卸載,因為同一時間只會有一個場景為當前場景),所以如果你用九宮格方式做無縫連接,你需要把場景讀取,然后讓場景里所有物體DontDestroyOnLoad(當然包括主角,也就是this),然后其他場景也這么操作,當需要卸載場景時,只要把所有那個場景Object給Destroy掉就可以了。而做雙場景連接則不需要這樣,也簡單的多。
3.如果是九宮格,你需要一個靜態二維數組去記錄每個位置場景的名字。這里也可以不需要這么做,有個技巧,你可以格式化場景名字規則來推算下一個需要加載的場景名字,比如M1N1表示(1,1)場景,那你就可以用字符串拆分的方法知道需要讀取M0N0,M1N0,M2N0等等的場景。
4.接下來就是處理碰撞,獲取下一個場景(雙場景方式)或者當前場景(九宮格方式)的名字,這里你就可以看到一個被格式化過的場景名有多么重要。
function OnTriggerEnter(other:Collider){
Application.LoadLevel(other.name); //這是雙場景方式直接把邊界Cube名字設為了下一個場景名
}
上面的介紹是大概的描述-具體實現方法如下
我們在使用這個技術之前要將你的地形--在U3D里的File-Build Setting里登記下場景,才能在腳本里讀取到-Add Current(這個是登記地形)
之后我們來制作一個簡單的-2個地形的切換方法
如圖:
這個圖是做好了的2張地形--中間的Cube是用來接受角色的碰撞的--這樣我們就知道在何時去載入我們的下張地形場景了(不包括其他-數據。。列入--坐騎這類的-這個需要另一段代碼單獨給坐騎-馬或者車子這類的--還有很多)
Cube---把Inspector--Is Trigger--劃勾--(這個作用是接受碰撞但不產生能量傳遞-也就是說他接受碰撞,但可以讓同樣具有 Is Trigger-劃勾的屬性物體通過--列入-我們的角色或坐騎)(補充--我們的角色也需要去勾選-Is Trigger-這些才可以通過,但官方自帶的FPS--沒有Is Trigger-這個勾選像-可以用如下方法解決-創建一個新的Cube為他重新命名-把他作為FPS-父物體--然后勾選-Cube的 Is Trigger-子物體就會有這項屬性了)
這些工作建立好了以后-開始我們的腳本工作。
腳本如下:
function OnTriggerEnter(other:Collider)
{
if (other.gameObject.name=="Cube1")
Application.LoadLevel("Terrain 1");
};
復制代碼
為了方便大家理解--我沒有重新命名--Cube1--就是我們接受碰撞體,Terrain 1是我們的要載入的下張地形。(大家可以用很多方法去優化他--有優化的方法請發在這個帖子內-方便大家查看-非常感謝)
這段代碼要放在角色上或角色攝像機上或FPS的父物體Cube上。
以上的操作就完成了-一個重地形0到地形1的切換--(可以解決100M或跟高的內存占用問題-這個要看你的地形場景而定了)
但這并不完整--我們也可能要重地形1回到地形0---這個要你們自己解決了-以上的內容已經把這個解決的方法說出來了--大家自己學習發揮下--這樣才有進步。
下面我們來說明4張地形場景的載入--邏輯(這里只說明邏輯,具體代碼和上方一樣,需要大家自己發揮下)
如圖
這個圖中可以看到4張地形場景--我重點講解--中間的2個大的Cube邏輯--那4個長方形的大家應該都清楚了。除非你沒認真看。
中間最大的Cube是來判斷--角色走的這個范圍內的時候他要去載入那張地形場景-如果在這個大的Cube的范圍內折載入其他的2張地形場景。(這個大的Cube可以根據自己的地形規格-做出調整這里給出的并不準確)
中間最小的Cube是來判斷--角色走的這個范圍內的時候他要去載入那張地形場景-如果角色走入小的Cube中后-載入其他3地形。
原文鏈接:http://keigoliye.blog.163.com/blog/static/146213359201081923658957/
用Unity3D制作基于web的網絡游戲,不可避免的會用到一個技術-資源動態加載。比如想加載一個大場景的資源,不應該在游戲的開始讓用戶長時間等待全部資源的加載完畢。應該優先加載用戶附近的場景資源,在游戲的過程中,不影響操作的情況下,后臺加載剩余的資源,直到所有加載完畢。
本文包含一些代碼片段講述實現這個技術的一種方法。本方法不一定是最好的,希望能拋磚引玉。代碼是C#寫的,用到了Json,還有C#的事件機制。
在講述代碼之前,先想象這樣一個網絡游戲的開發流程。首先美工制作場景資源的3D建模,游戲設計人員把3D建模導進Unity3D,托托拽拽編輯場景,完成后把每個gameobject導出成XXX.unity3d格式的資源文件(參看BuildPipeline),并且把整個場景的信息生成一個配置文件,xml或者Json格式(本文使用Json)。最后還要把資源文件和場景配置文件上傳到服務器,最好使用CMS管理。客戶端運行游戲時,先讀取服務器的場景配置文件,再根據玩家的位置從服務器下載相應的資源文件并加載,然后開始游戲,注意這里并不是下載所有的場景資源。在游戲的過程中,后臺繼續加載資源直到所有加載完畢。
一個簡單的場景配置文件的例子:
MyDemoSence.txt
Json代碼
{
"AssetList" : [{
"Name" : "Chair 1",
"Source" : "Prefabs/Chair001.unity3d",
"Position" : [2,0,-5],
"Rotation" : [0.0,60.0,0.0]
},
{
"Name" : "Chair 2",
"Source" : "Prefabs/Chair001.unity3d",
"Position" : [1,0,-5],
"Rotation" : [0.0,0.0,0.0]
},
{
"Name" : "Vanity",
"Source" : "Prefabs/vanity001.unity3d",
"Position" : [0,0,-4],
"Rotation" : [0.0,0.0,0.0]
},
{
"Name" : "Writing Table",
"Source" : "Prefabs/writingTable001.unity3d",
"Position" : [0,0,-7],
"Rotation" : [0.0,0.0,0.0],
"AssetList" : [{
"Name" : "Lamp",
"Source" : "Prefabs/lamp001.unity3d",
"Position" : [-0.5,0.7,-7],
"Rotation" : [0.0,0.0,0.0]
}]
}]
}
AssetList:場景中資源的列表,每一個資源都對應一個unity3D的gameobject
Name:gameobject的名字,一個場景中不應該重名
Source:資源的物理路徑及文件名
Position:gameobject的坐標
Rotation:gameobject的旋轉角度
你會注意到Writing Table里面包含了Lamp,這兩個對象是父子的關系。配置文件應該是由程序生成的,手工也可以修改。另外在游戲上線后,客戶端接收到的配置文件應該是加密并壓縮過的。
主程序:
C#代碼
。。。
public class MainMonoBehavior : MonoBehaviour {
public delegate void MainEventHandler(GameObject dispatcher);
public event MainEventHandler StartEvent;
public event MainEventHandler UpdateEvent;
public void Start() {
ResourceManager.getInstance().LoadSence("Scenes/MyDemoSence.txt");
if(StartEvent != null){
StartEvent(this.gameObject);
}
}
public void Update() {
if (UpdateEvent != null) {
UpdateEvent(this.gameObject);
}
}
}
。。。
}
這里面用到了C#的事件機制,大家可以看看我以前翻譯過的國外一個牛人的文章。C# 事件和Unity3D
在start方法里調用ResourceManager,先加載配置文件。每一次調用update方法,MainMonoBehavior會把update事件分發給ResourceManager,因為ResourceManager注冊了MainMonoBehavior的update事件。
ResourceManager.cs
C#代碼
。。。
private MainMonoBehavior mainMonoBehavior;
private string mResourcePath;
private Scene mScene;
private Asset mSceneAsset;
private ResourceManager() {
mainMonoBehavior = GameObject.Find("Main Camera").GetComponent<MainMonoBehavior>();
mResourcePath = PathUtil.getResourcePath();
}
public void LoadSence(string fileName) {
mSceneAsset = new Asset();
mSceneAsset.Type = Asset.TYPE_JSON;
mSceneAsset.Source = fileName;
mainMonoBehavior.UpdateEvent += OnUpdate;
}
。。。
在LoadSence方法里先創建一個Asset的對象,這個對象是對應于配置文件的,設置type是Json,source是傳進來的“Scenes/MyDemoSence.txt”。然后注冊MainMonoBehavior的update事件。
C#代碼
public void OnUpdate(GameObject dispatcher) {
if (mSceneAsset != null) {
LoadAsset(mSceneAsset);
if (!mSceneAsset.isLoadFinished) {
return;
}
//clear mScene and mSceneAsset for next LoadSence call
mScene = null;
mSceneAsset = null;
}
mainMonoBehavior.UpdateEvent -= OnUpdate;
}
OnUpdate方法里調用LoadAsset加載配置文件對象及所有資源對象。每一幀都要判斷是否加載結束,如果結束清空mScene和mSceneAsset對象為下一次加載做準備,并且取消update事件的注冊。
最核心的LoadAsset方法:
C#代碼
private Asset LoadAsset(Asset asset) {
string fullFileName = mResourcePath + "/" + asset.Source;
//if www resource is new, set into www cache
if (!wwwCacheMap.ContainsKey(fullFileName)) {
if (asset.www == null) {
asset.www = new WWW(fullFileName);
return null;
}
if (!asset.www.isDone) {
return null;
}
wwwCacheMap.Add(fullFileName, asset.www);
}
。。。
傳進來的是要加載的資源對象,先得到它的物理地址,mResourcePath是個全局變量保存資源服務器的網址,得到fullFileName類似http://www.mydemogame.com/asset/Prefabs/xxx.unity3d。然后通過wwwCacheMap判斷資源是否已經加載完畢,如果加載完畢把加載好的www對象放到Map里緩存起來。看看前面Json配置文件,Chair 1和Chair 2用到了同一個資源Chair001.unity3d,加載Chair 2的時候就不需要下載了。如果當前幀沒有加載完畢,返回null等到下一幀再做判斷。這就是WWW類的特點,剛開始用WWW下載資源的時候是不能馬上使用的,要等待諾干幀下載完成以后才可以使用。可以用yield返回www,這樣代碼簡單,但是C#要求調用yield的方法返回IEnumerator類型,這樣限制太多不靈活。
繼續LoadAsset方法:
C#代碼
。。。
if (asset.Type == Asset.TYPE_JSON) { //Json
if (mScene == null) {
string jsonTxt = mSceneAsset.www.text;
mScene = JsonMapper.ToObject<Scene>(jsonTxt);
}
//load scene
foreach (Asset sceneAsset in mScene.AssetList) {
if (sceneAsset.isLoadFinished) {
continue;
} else {
LoadAsset(sceneAsset);
if (!sceneAsset.isLoadFinished) {
return null;
}
}
}
}
。。。
代碼能夠運行到這里,說明資源都已經下載完畢了。現在開始加載處理資源了。第一次肯定是先加載配置文件,因為是Json格式,用JsonMapper類把它轉換成C#對象,我用的是LitJson開源類庫。然后循環遞歸處理場景中的每一個資源。如果沒有完成,返回null,等待下一幀處理。
繼續LoadAsset方法:
C#代碼
。。。
else if (asset.Type == Asset.TYPE_GAMEOBJECT) { //Gameobject
if (asset.gameObject == null) {
wwwCacheMap[fullFileName].assetBundle.LoadAll();
GameObject go = (GameObject)GameObject.Instantiate(wwwCacheMap[fullFileName].assetBundle.mainAsset);
UpdateGameObject(go, asset);
asset.gameObject = go;
}
if (asset.AssetList != null) {
foreach (Asset assetChild in asset.AssetList) {
if (assetChild.isLoadFinished) {
continue;
} else {
Asset assetRet = LoadAsset(assetChild);
if (assetRet != null) {
assetRet.gameObject.transform.parent = asset.gameObject.transform;
} else {
return null;
}
}
}
}
}
asset.isLoadFinished = true;
return asset;
}
終于開始處理真正的資源了,從緩存中找到www對象,調用Instantiate方法實例化成Unity3D的gameobject。UpdateGameObject方法設置gameobject各個屬性,如位置和旋轉角度。然后又是一個循環遞歸為了加載子對象,處理gameobject的父子關系。注意如果LoadAsset返回null,說明www沒有下載完畢,等到下一幀處理。最后設置加載完成標志返回asset對象。
UpdateGameObject方法:
C#代碼
private void UpdateGameObject(GameObject go, Asset asset) {
//name
go.name = asset.Name;
//position
Vector3 vector3 = new Vector3((float)asset.Position[0], (float)asset.Position[1], (float)asset.Position[2]);
go.transform.position = vector3;
//rotation
vector3 = new Vector3((float)asset.Rotation[0], (float)asset.Rotation[1], (float)asset.Rotation[2]);
go.transform.eulerAngles = vector3;
}
這里只設置了gameobject的3個屬性,眼力好的同學一定會發現這些對象都是“死的”,因為少了腳本屬性,它們不會和玩家交互。設置腳本屬性要復雜的多,編譯好的腳本隨著主程序下載到本地,它們也應該通過配置文件加載,再通過C#的反射創建腳本對象,賦給相應的gameobject。
最后是Scene和asset代碼:
C#代碼
public class Scene {
public List<Asset> AssetList {
get;
set;
}
}
public class Asset {
public const byte TYPE_JSON = 1;
public const byte TYPE_GAMEOBJECT = 2;
public Asset() {
//default type is gameobject for json load
Type = TYPE_GAMEOBJECT;
}
public byte Type {
get;
set;
}
public string Name {
get;
set;
}
public string Source {
get;
set;
}
public double[] Bounds {
get;
set;
}
public double[] Position {
get;
set;
}
public double[] Rotation {
get;
set;
}
public List<Asset> AssetList {
get;
set;
}
public bool isLoadFinished {
get;
set;
}
public WWW www {
get;
set;
}
public GameObject gameObject {
get;
set;
}
}
代碼就講完了,在我實際測試中,會看到gameobject一個個加載并顯示在屏幕中,并不會影響到游戲操作。代碼還需要進一步完善適合更多的資源類型,如動畫資源,文本,字體,圖片和聲音資源。
動態加載資源除了網絡游戲必需,對于大公司的游戲開發也是必須的。它可以讓游戲策劃(負責場景設計),美工和程序3個角色獨立出來,極大提高開發效率。試想如果策劃改變了什么NPC的位置,美工改變了某個動畫,或者改變了某個程序,大家都要重新倒入一遍資源是多么低效和麻煩的一件事。
http://www.web3d.com.cn/new/teach/unity3d/2012/3/27/37137809.html