項目背景git
所謂選型,我認爲就是爲了實現某(些)個需求或者解決某(些)個問題所使用的解決方案。它多是一個技術方案,也多是一個管理方案,也能夠是一個軟件、工具或者是流程規範。github
這篇的主題是技術選型,因此主要會分析項目客戶端部分的技術解決方案。那麼作選型分析以前就要先收集需求,分析需求,搞清楚咱們的項目須要什麼,達到什麼效果,實現什麼功能。除了項目自己章程和範圍以外,還要看同項目的其餘部門分工和合做方案(好比服務器的技術對接,美術資源的規範流程以及部分效果的實現對接),在這以外還須要考慮公司的大環境,好比資源的支持程度,項目的編制和人員的支持力度,開發環境甚至是市場和法律條件。編程
世界地圖緩存
咱們如今的項目是一個沙盤類型的SLG。主要玩法也是世界地圖的資源掠奪。這是比較經典的SLG玩法,包括《列王紛爭》《王國紀元》《亂世王者》《真龍霸業》等等國內外數據良好的遊戲都是這種核心玩法。大地圖很大,一個大服甚至會有幾十萬的地形數據。在地形編輯、行軍尋路(須要支持關隘和高地)、服務器數據同步等諸多地方都會有比較大的挑戰。服務器
主城微信
接下來是城市發展。這部分和市面上大多數的同類型遊戲設計都不同了。大部分的沙盤類遊戲都是採用靜態城市的佈局策略,即每一個建築的坑都留好了,你達到等級以後只要點擊坑位建造指定的兵營、伐木場、訓練營等建築就行了。網絡
並且建築自己也只是一個功能系統的入口,並不會有建築和建築之間產能影響,亦或是按照本身的習慣建造建築和道路。咱們的建築後期加起來會有140多個,每一個建築都是能夠自由移動和佈局,在這個功能點上更像《部落衝突》的表現形式。架構
可是和它不一樣的是,咱們的主城不會參與戰鬥,因此也不會有防護性的建築,取而代之的是服務器性的建築,好比水井、醫院、教堂、公園等等這些服務器性的建築會影響到生產建築的產能,因此相同等級的狀況下,合理的佈局會讓你的產能超出別人一截。另外咱們的道路也是能夠自定義編輯的,和道路相連的建築也會有加成。若是有人玩過《城市:天際線》應該可以更明白一些主城的玩法模型。可是和《天際線》相比咱們又沒有那麼複雜的計算和影響,畢竟人家是PC上的純單機模擬養成類型。app
除了建築和道路的的自由編輯以外,NPC也是主城的主要功能。NPC會有10幾種,每種的AI都不同,而且要求可以在兩個徹底緊連的建築縫隙中穿插和移動,還要考慮道路優先。框架
城市會有自定的保存模板,還要有可破壞和不可破壞的裝飾機制等等。
因此主城的難點除了實現各個功能以外,還須要解決100多個建築+幾十個NPC+場景自己和UI部分的全部性能消耗。
戰鬥
戰鬥以前也有說過,須要支持同屏500+的單位同時戰鬥,這些單位每一個都是獨立的個體。這表示,每一個AI都須要有本身的AI機制和獨立的動做表現。一個單位大概會有5-6種動做,小型單位600+面,大型單位1000+面。這對GPU和CPU的壓力都很是的大。
戰鬥還須要支持錄像回放,而且在任何設備任什麼時候候播放出來的結果和過程都要一致。
戰鬥須要支持倍速功能。
若是有人熟悉《全面戰爭》系列會比較容易理解咱們戰鬥模式。不過和全戰不一樣的是,咱們的士兵在出戰以後就不能手動控制了,畢竟是移動遊戲,太複雜了傷害玩家。。。不過,其實仍是能夠手動釋放英雄技能的。【手動滑稽】
三塊重點內容分析完成以後,技術方案就須要根據需求去挑選了。用一句歸納遊戲就是:輕經營、重策略的沙盤SLG。
技術選型
嗯,下面就正式入活了。
引擎版本
技術選型要服務於產品。但在挑選技術方案以前還要作一件事情,引擎版本的選擇。
早在2018年底,咱們就收到了谷歌商店上架APP強制要求64位版本的需求,具體強制時間在19年8月1日。當時和Unity團隊溝通的時候,反饋是必須2018以上的版本才能支持64位(不過一段時間以後又說2017.3也能夠)。加上當時手裏有Unity2018.3的引擎源碼,因此就把版本定在了Unity2018.3(不過將來可能會升級到2019,裏面有分幀GC的功能我會比較感興趣)。
版本選定以後,就開始真正的技術選型了,這裏我大體羅列了一下,其中有些是框架方向,有些是工具插件,有些是設計思路。但整體仍是囊括了客戶端該有的技術部分。
Sproto
網絡遊戲,首先要考慮的是如何與服務器進行通訊。做爲SLG類型,對於響應速度需求並不會像FPS或者MOBA類型那麼的強烈。因此就挑選了TCP的方式進行鏈接。而後,使用了Sproto做爲協議的載體進行消息傳遞和RPC封裝。
TCP的部分就不用過多講解了,作網絡遊戲都會接觸和了解。這裏講一下Sproto。可是在講Sproto以前呢,還必須先拓展另一個東西:skynet。
skynet是雲風大神建立的開源服務器框架,使用C和Lua結合的技術搭建的基於Actor模式的引擎。這裏不會拓展講解skynet的技術細節,有興趣的能夠去看下我同事對於skynet的源碼賞析。
回到剛纔SProto的問題上來,Skynet原本是支持PB(proto buffer)的。可是隻支持2.X的版本,而且已經再也不維護了。出於優化的目的,skynet使用了一套自定義的格式Sproto。它實際上是基於proto的改良,將proto裏的冗餘表達進行了簡化,讓它更知足於skynet在Lua端的性能表現。那麼咱們綜合考慮下來也是選取了sproto的方式進行協議傳輸。
這其實又涉及到一個問題,Sproto實際上是設計個skynet用的,可是客戶端用的是Unity,開發語言是C#,確定不能直接使用。不過不要緊,我央求了服務端大佬給咱們寫了C#的轉譯工具,能夠將Sproto的描述文件轉爲CS文件,而後再寫了一套序列化和反序列化工具,呃~能夠像PB同樣正常序列化了。
通常客戶端關心數據分爲兩個部分,一個部分來自於服務器端,另外一個部分來自於策劃配置表。如今網絡端搞定了,數據表怎麼辦呢?對,我又去央求了咱們的服務端大佬,給咱們寫了一個excel轉Sproto的工具(過程很是複雜。。嗯先把Excel轉成Lua格式,再Lua轉成Sproto的描述文件,再把描述文件轉爲CS),這樣咱們的策劃數據也搞定了。
GPUSkin+GPUInstance
咱們的戰鬥場景須要顯示500+的單位,每一個單位攜帶本身獨立的AI和動做。大型單位約有100面,小型約600面。那麼同屏顯示以後,CPU和GPU都面臨巨大的性能壓力。用小米5S作過一次測試,當使用skinmesh的時候,4000單位的幀率就只能到20了,換了GPUSkin方案,8000個單位仍然可以保持50幀。這部分的選型是爲了解決同屏渲染壓力。
ECS
與傳統的面向對象的編程理念不同,ECS(Entity-Component-System)是面向數據的編程思想。若是不理解概念的能夠本身先去翻閱下資料,也能夠等後面講技術細節的時候再去了解。這裏簡單的類比一下幫助理解。就比如Unity的開發模式,一個GameOject能夠理解爲一個Entity,單獨放在場景裏它什麼都不是。若是你給它綁定了一個Text組件,那麼它立刻就會變成一個Text 組件;若是綁定一個Button組件那麼它就是一個Button。那麼這個時候你能夠理解爲Unity就是一個EC的思想。至於爲何引入S的概念就是爲了解決耦合和數據冗餘。讓一個Component裏只有數據而沒有方法,全部的方法都寫在System。讓數據在內存裏的排布更加緊密,增長緩存命中率,特別善於處理大批量的數據。
同時,由於數據和系統分離,那麼作回放的時候數據很是便於保存。這又符合了咱們常規的邏輯和表現分離的設定,因此這套機制完美契合了咱們戰鬥需求。配合GPUSkin和GPUInstance既優化了性能,又能實現回放和解耦,同時還會帶來另一個優點,邏輯和表現分離。
咱們還作了一個大膽的嘗試,將邏輯和表現分離以後,將邏輯層接入到服務器中(服務器是基於Actor的,因此擴展一個戰鬥服很容易),客戶端則既跑邏輯又跑表現。這樣帶來的好處就是,只要咱們給定的輸入一致,由於邏輯是一套,跑出來的結果也一定一致。因此世界離線戰鬥的時候咱們調用服務器秒算結果,PVE副本的時候,客戶端展示戰鬥過程,很是美妙。
XLua
Lua在客戶端集成的主要做用仍是用來解決熱更新問題的,它帶來了便捷的同時固然也帶來了性能問題。通常來講,Lua和C#的性能差距在40倍左右。移動開發一路走來有不少Lua相關的框架,好比toLua,uLua,slua,Xlua等。
因此有的時候就會想,有沒有既能夠實現熱更新又能提升性能的方法,那麼Xlua就是這種。開發用C#,熱更新修復用XLua。固然這也不是徹底免費的,取而代之的是要在開發的過程當中作好各類標識,增長了開發管理難度同時包的代碼段會增加不少。
說點題外話,移動遊戲剛起步的階段,除了Lua以外確實沒有更好的熱更新手段。因此你們才考慮將Lua接入到開發中,甚至一度接管項目的總體外圍開發。可是如今除了Lua以外,也還有不少其餘方式能夠作到熱更新,好比騰訊的潘多拉。固然項目的開發過程當中要使用防護性編程是確定的,除了作好各項QA驗收以外,還要對每一個功能作出屏蔽入口,甚至在一些運營活動上作好模板參數,能夠經過快速調節參數就能變成另一個活動。
咱們使用XLua的想法也會趨近於這個思惟。平時開發都會在C#上,可是仍然會在Lua層面維護一整套的功能系統,讓Lua層面有能力解決大部分的突發狀況和新增需求,可是這僅僅是一個後備手段。全部一切仍是以C#爲主,哪怕是上線階段用lua修了某些問題,那麼再下一個版本里也會把功能修復到C#層面,並從lua層移除。
UGUI
這個其實如今可選擇性不是很大。目前能與之一戰的是NGUI和FairyGUI。NGUI和UGUI是一個爸爸,可是在層級處理方面十分複雜,對於一些新手小朋友的理解尚不友好,不像UGUI保證在一個Canvas下能按照樹狀層級顯示。FairyGUI是一個第三方的GUI,它須要接入SDK。而且它本身內部保證接入了SDK會在不一樣平臺表現一致。這對於可能須要轉引擎(COCOS轉Unity之類的)的項目可能更好,可是咱們並不會轉因此並不須要。
Wwise
Wwise是一個音效框架,其實這裏能選擇的餘地不大,基本就是fmode和Wwise兩種。可是近幾年fmode有些沒落,操做、性能和工具鏈都跟不上了,之前但是一枝獨秀。
GCloud
GCloud是騰訊雲產品的一種,起初是爲了服務內部遊戲產品所孵化出的統一平臺。
國內遊戲經常使用的遊戲內語音,電臺等均可以接入這個實現。另外功能還覆蓋了遊戲更新,區服導航,微端puffer等遊戲內經常使用的功能設定。
這一套接入起來真真兒是極好的,爲手遊的幾個難搞部分提供了統一化的服務,後臺的操做也是極其簡單,有興趣的能夠去官網瞭解。
Addressable Asset System
這套東西是我目前極力推薦的,它起於2018版本(預覽版),在2019已是正式版本功能,提供了一套極其強大的資源打包和加載的管理方案。
以往咱們的資源打包方案都須要本身去實現,諸如在編輯器下使用編輯器接口,在實機狀態下打包成bundle形式加載,而後還須要咱們本身去收集和管理資源的依賴關係,維護自定義的資源列表,而這套通通幫咱們作好了,而且提供了可視化的界面操做,管理資源媽媽不再用爲我費心了。
依稀記得4.x的版本,要作資源管理須要本身指定目錄或者資源,而後根據是不是依賴項的方式調用打包的API。甚至若是作資源更新,你須要本身維護一份資源列表,本身自定義MD5值比對差別,若是須要告知用戶下載的資源大小,你還要本身統計單個資源的大小,彙總告知玩家。
5.X的時候,資源管理作過一次大的升級,讓每一個資源都帶有Asset Bundle標籤,這樣在Unity的工程目錄就能夠經過自定義標註資源的方式標識資源,而且在生成的每一個bundle的同時爲bundle生成一個manifest文件,用來標識該bundle的內容和依賴項等大概長這樣:
在運行時進行資源加載的時候也是先加載這個文件查找依賴項,遞歸加載直至完成。比起4.x以後確定是好了不少,可是仍然是極度的麻煩。
如今是一個這樣的可視化面板,全部資源均可以經過拖拽完成,另外代碼裏也提供了完整的加載方案,讓你在編輯器和真機的都不用關心資源格式只使用同一個接口調用就好。
詳細的技術內容會放在指定章節去講解。
Tiled
Tiled是一個老牌的基於瓦片的2D編輯器。功能很是之強大,以致於我就不在這裏講述它的強大之處了。
其實Unity2017以後也針對性的提供了tileMap功能組件,用於給2D遊戲提供一些周邊輔助。甚至在github上還提供了擴展筆刷和Demo來支撐。但儘管如此,它在功能實現上仍是不如Tiled來的快捷。
另外咱們的世界地圖很是之大,有幾十萬格,因此單用模型或者地形去刷就太耗費資源了。因此這裏會選用2D的方式來展示世界地圖的地形,至於地圖上的奇觀、主城、資源點、玩家部隊、怪物等等就用3D的形式去展示。
Tiled編輯器生成的格式Unity並不能直接用,因此還須要藉助一些插件,這個咱們放在後面去講解。
TimeLine
這個很簡單,是2017之後提供的一套線性編輯工具,咱們有可能會在劇情,鏡頭等方面使用它,另外它和cinemachine是一對CP,成對出現。
TextMeshPro
TMP是早在5.x就存在的一個優秀插件,後來由於表現過於優異被吸取爲Unity正式功能。咱們都知道UGUI對於字體計算上很是的耗時。同時UGUI的渲染原理也決定了對於一些常常變更的UI節點有着較大的性能問題。因此對於一些戰鬥飄字,小地圖、聊天等變動頻繁功能來講,UGUI表現是很是糟糕的。另外UGUI對於字體的內存處理上面也是有比較大的問題的,當字號不一致或者差別大的時候,內存消耗嚴重,這在聊天功能裏表現尤爲顯著。
另外還有一個問題是聊天圖文混排,這個在NGUI裏作的比較好,可是UGUI自己卻不支持,不過不要緊TMP支持!
除了支持圖文混排以外,它還支持各類富文本,超連接、相似平方的上標,化學表達式的下標、 各類文字效果好比打字機或者遮罩等等很是強大。
而且最重要的是,它能夠和UGUI徹底混用,甚至直接替代UGUI裏的Text、DropDown、Inputfield等使用到文字的組件。
嗯,它除了處理文字以外,咱們的血條,建築頭頂圖標等各類HUD也能夠用它表現,是否是很是驚奇!
A*PathFinding
這個就是前段時間翻譯的的A*PathFinding 教程系列。以前的總結篇也有對這個插件作過總結,總的來講這是一個很是很是值得推崇的插件。不只僅在於它的功能強大,也在於它的軟件架構,和文檔教程支持程度。是一個教科書般的第三方庫。
咱們的戰鬥其實並無用到尋路模塊,可是在表現層須要作動態規避。由於對於邏輯層(服務器運算的時候)來講,單位是沒有碰撞和體積的,可是對於客戶端來講,咱們確定不能讓單位所有重疊在一塊兒,這就使用到了A*插件的動態規避(RVO)。
主城部分由於涉及到經營,那麼就必須模擬大量的NPC行爲,有NPC就要有各類尋路和目標表現,好比一個送牛奶的農夫,去公園玩耍的孩子,送貨的、送酒的,去市政廳辦事的,去醫院看病的,城裏巡邏的等等。那麼尋路這塊就極爲重要。
世界地圖這塊咱們也涉及到行軍,由於咱們會考慮作關隘和高地,因此須要使用到分層尋路。另外與主城的NPC表現不同的是,主城是裝飾性的NPC,而且人物比較小,因此動做幅度和尋路狀態機械一點反而好看,可是世界地圖是功能性的,雖然建築和資源點都是基於網格的,可是咱們計算路徑的時候卻不能使用網格,會影響到行軍的時長和路徑。由於行軍是由服務器計算的,因此這塊咱們的打算也是製做一個世界地圖的尋路系統庫,而後丟到服務器去跑,也就是說功能是客戶端作,可是丟在服務器去運行,是否是很酷。
收尾
選型是個很大的課題,這篇文章只講了技術部分的方案,後面會針對各類技術細節作探討,以及講解項目中遇到的實際問題從什麼維度去思考解決方案,但在這以前還須要先講一下客戶端的目錄分佈。看看一個實際的大項目是怎麼在幾十我的之間合做有序,各司其職的。
PS:這篇文章是去年10月份發表在知乎上的,其中涉及的技術選型是在項目立項以前就作好的。如今項目開發已經接近尾聲,除了GCloud這個部分由於市場緣由沒有接入以外,其他項目技術均在預約的選型之下。
本文分享自微信公衆號 - 壹種念頭(OneDay1Idea)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。