2018年9月19日 星期三

直播回顧 - 快樂的Minecraft 我的世界 ECS快速開發

作者:Kelvin Lo

片頭展示



直播影片回播

受到了上次Mike大叔的ECS教學啓發,我用一場GameJam活動的時間來製作了一個快速呈現ECS強大的簡單專案-Minecraft我的世界,當然沒辦法還原像是紅石系統這類高難度的功能,基本上就是挖礦和放石頭這兩個主要功能,重點在於如何利用ECS產生廣大的地形,以及體現ECS所提倡的要點-Performance by Default,在不考慮程式及任何優化的情況下,做出來的場景還能保有很好的效能。

Unity從2018開始導入ECS框架,紀錄這篇文章之時ECS仍然是體驗版,代表每一次的更新都會加入非常多的新功能,因此本篇文章僅算是我對當前ECS(Unity 2018.2.6, Entities 0.0.12)的體驗心得報告。

為什麼ECS的效能會超越GameObject+MonoBehaviour那麼多呢?我歸類為兩個要點,傳統方式的記憶體管理是分散式的,即物件和它身上的組件(Component)並非在同一個記憶體區段,每次存取都非常耗時。而ECS會確保所有的組件資料(Component Data)都緊密的連接再一起,這樣就能確保存取記憶體資料時以最快的速度存取。



這裡所提到的傳統組件Component和ECS的C,即Component Data雖然名字很像但卻是很不同的概念,舊的Component會乘載著不同的資料與邏輯功能,不同的Component代表著不同的功能。但在ECS裡,Component只會儲存單純的Data,不會有任何的邏輯運算,所有的邏輯運算必須拆離到S,即System裡面去實現。

也因為傳統的Component為了方便使用,整合了所有可能會用到的變數,不管你用得到或是用不到。好處就是一個Component可以解決很多事情,比如Transform,帶有Position, Rotation, Scale,透過這些參數很容易對物件處理位置或移動縮放處理。但這些你用不到的變數對於記憶體來說一點也不友善,尤其時物件非常大量時。

因此ECS把很重要一個概念就是管理只需要的Data即可,例如我的物件只需要處理渲染和移動,我當然就不需要紀錄Scale資料。





再來說說幾種方式產生Entity物件:



可以看到這張圖中Hierarchy裡只有一個方塊物件,但畫面卻有3個方塊,原因是左邊兩個是透過ECS產生的Entity物件,會獨立顯示在Entity Debugger內。雖然目前是分開顯示的,但我們最終目的是重新打造一個讓開發者接受的一個統一編輯環境。

而左邊兩個Entity物件分別採用兩種不同的方式產生,整段程式如下:



我先宣告了一個EntityManager管理器manager,然後在它底下生成了一個Archetype,這種EntityArchetype能確保存放在裡面的Component Data都緊密的相連,當你一次性產生大量的Entities並給予各種邏輯運算時,這種結構在記憶體與快取之間的移動效能直逼memcpy。

從41-49行程式就是基於Archetype結構在裡面生成一個Entity並給予座標資料與渲染資料,即指定渲染哪個Mesh和Material。最後這個方塊只帶有座標Position, 自訂的BlockTag和用來渲染的MeshInstanceRenderer三個Component Data遠遠比傳統的Game object資料少很多。這種產生方式沒有連結任何Unity編輯器上的功能,只是單純的產生Data和處理Data,可以稱為Pure ECS。

54-58行程式負責渲染最左邊的方塊,為了讓開發更直覺我希望產生Entity時引用一個Prefab,雖然Prefab是傳統Game Object,但在實例化成Entity時,Unity會自動把跟ECS無關的Component拆離,只留下Component Data產生Entity物件。這種跟Unity整合的流程,可以稱為Hybrid ECS,也是Unity在整合ECS的主要路線。

這個範例還用了一些新的功能例如新的NativeArray和定義像[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
這樣的Attributes屬性來確保函數能夠運行在想要的順序上。

如何產生世界地形


為了要讓地形能更像Minecraft的產生方式,它必須符合幾個要點:無接縫,無限大,亂數地形等等,採用Perlin noise處理這種地形是在適合不過了,Perlin noise可以有規則的亂數產生一個波形資料,作為世界的高度資料參考非常適合,我可以先產生一個足夠大的高度圖然後只用一小部分即可,如果未來要延伸地形只需要繼續生成它就會不斷延伸下去不會破掉。







具體把這個高度資料用方塊選染出來大概會長這樣



只留下高度0-6號的方塊,感覺世界形狀就出現了



從程式針對不同高度的Entity給予不同的Material資料,就可以產生我的世界,針對高度0的地表,也可以隨機產生花草或樹木。



嘗試產生了一個沒有任何優化處理,17萬個方塊的我的世界還能保持在40FPS以上,心裡有些激動。


產生物理地表


由於Unity ECS尚未支援物理系統,因此目前要實現物理要自己寫碰撞邏輯,或是沿用舊的Collider來檢測碰撞,作為一個Gamejam專案,自己寫是不太實際的而且我預期很快的Unity ECS會支援物理系統,因此我決定採用最簡單的方法,每個Entity方塊都加上一個Collider,一方面可以作為地板一方面也可以檢測挖掘和放置方塊。

這是一個全部方塊的座標同時都加放一個Collider物件的地形,除了第一幀稍微耗時之外,運行時並沒有帶來太多的消耗。



有了地形就可以放上大家熟悉的FPS Character第一人稱角色,繼續處理方塊的挖掘和放置。

製作放置方塊比挖掘方塊來得簡單,我們從鏡頭發射一個Raycast,打到任何Collider就依照當下normal的方向產生一個Collider與一個Entity物件,並依照選擇的貼圖指定給該Entity帶有的MeshInstanceRenderer內的Material即可。程式如下:



但是挖掘方塊就沒那麼容易了,主要是因為當前的Entities版本尚未支援物理系統,因此定位場景內眾多物件中某一個entity是有困難的,相信未來這不會是個問題,但現在我必須寫很多程式來實現支援ECS的Raycast,因此我採用另外的思維來實現。



首先我在Raycast打到的Collider同樣的座標產生一個只帶有一個DestroyTag的Entity物件,它除了座標之外沒有任何渲染資料,我就可以用System來檢測移除同座標的方塊。同時我也先移除了這個Collider。

我們上面聊了很多關於ECS裡面的E(Entity)和C(Component Data)但一直沒有提到S(System),現在我們要寫一個System來處理刪除方塊的系統。但在之前我想討論這個S和傳統的有什麼不同。傳統的腳本你必須要放到場景內才會隨著物件的Update, FixUpdate或OnCollisionEnter各種方法來運行,但ECS的System是不分場景的,項目內所有的場景只要帶有Entity物件System都會有反應,因此不需要特別把System腳本放入場景物件內。



當然這樣做的好處就是所有的相關系統你可以一隻腳本解決,而不是每個物件有各自的Update方法處理,造成物件數量一大就會影響效能。System不在乎你是一顆子彈或是一隻怪物或是一個角色,只要符合System過濾條件,它都會執行System內的邏輯。

我寫了一個Destroy System用來檢測那些Entity必須要刪除,


System有兩個主要的結構,一個是13-38行代碼的Query,我分別查詢哪些Entity帶有BlockTag和Position,哪些帶有DestroyTag和哪些是地表的花草,然後在OnUpdate內比對哪些事需要刪除的Entity方塊,並檢查上方是否帶有花草需要一並移除。因此這裡總共需要移除的物件有四個:該位置的Entity方塊、作為檢測的Entity物件、地上的花草和BoxCollider物件。



System的OnUpdate也是每幀執行的,你可以從Entity Debugger內看到每個系統的耗時,雖說System是全專案執行的,但你也可以透過API在不同的場景開關這些System。



以上是本次直播快樂的Minecraft ECS製作分享重點內容,後續我們還會有更多的報導。 感謝各位 !

4 則留言:

  1. 您好, 感謝您的教學分享! 想請問這個專案檔會公開給大家下載來看看嗎?

    回覆刪除
    回覆
    1. 有的,這個專案在Github上已經是公開的
      https://github.com/UnityTechnologies/MinecraftECS

      刪除
  2. 您好,您的文章對我的幫助很大,感謝

    最近我在開發Unity的WebGL的遊戲時碰到一個問題,想請教一下
    我軟體編譯都正常完成,放進網頁伺服器運行時出現以下的錯誤

    To use dlopen, you need to use Emscripten's linking support, see https://github.com/kripken/emscripten/wiki/Linking

    printErr @ UnityLoader.js:4

    想請教一下在運行WebGL時,伺服器需要的環境套件有那些呢,還是在編譯時有些選項或套件我沒安裝好

    感謝您

    回覆刪除

著作人