Unity手游實戰:從0開始SLG—客戶端技術選型


如何在立項前做好客戶端的技術選型?騰訊資深開發工程師給你答案,一起來看這篇Unity手游實戰:從0開始SLG—客戶端技術選型。

項目背景

所謂選型,我認為就是為了實現某(些)個需求或者解決某(些)個問題所使用的解決方案。它可能是一個技術方案,也可能是一個管理方案,也可以是一個軟件、工具或者是流程規范。

這篇的主題是技術選型,所以主要會分析項目客戶端部分的技術解決方案。那么做選型分析之前就要先收集需求,分析需求,搞清楚我們的項目需要什么,達到什么效果,實現什么功能。除了項目本身章程和范圍之外,還要看同項目的其他部門分工和合作方案(比如服務器的技術對接,美術資源的規范流程以及部分效果的實現對接),在這之外還需要考慮公司的大環境,比如資源的支持程度,項目的編制和人員的支持力度,開發環境甚至是市場和法律條件。

世界地圖

我們現在的項目是一個沙盤類型的SLG。主要玩法也是世界地圖的資源掠奪。這是比較經典的SLG玩法,包括《列王紛爭》《王國紀元》《亂世王者》《真龍霸業》等等國內外數據良好的游戲都是這種核心玩法。大地圖很大,一個大服甚至會有幾十萬的地形數據。在地形編輯、行軍尋路(需要支持關隘和高地)、服務器數據同步等諸多地方都會有比較大的挑戰。

主城

接下來是城市發展。這部分和市面上大多數的同類型游戲設計都不一樣了。大部分的沙盤類游戲都是采用靜態城市的布局策略,即每個建築的坑都留好了,你達到等級之后只要點擊坑位建造指定的兵營、伐木場、訓練營等建築就好了。

而且建築本身也只是一個功能系統的入口,並不會有建築和建築之間產能影響,亦或是按照自己的習慣建造建築和道路。我們的建築后期加起來會有140多個,每個建築都是可以自由移動和布局,在這個功能點上更像《部落沖突》的表現形式。

但是和它不同的是,我們的主城不會參與戰斗,所以也不會有防御性的建築,取而代之的是服務器性的建築,比如水井、醫院、教堂、公園等等這些服務器性的建築會影響到生產建築的產能,所以相同等級的情況下,合理的布局會讓你的產能超出別人一截。另外我們的道路也是可以自定義編輯的,和道路相連的建築也會有加成。如果有人玩過《城市:天際線》應該能夠更明白一些主城的玩法模型。但是和《天際線》相比我們又沒有那么復雜的計算和影響,畢竟人家是PC上的純單機模擬養成類型。

除了建築和道路的的自由編輯之外,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,並且人物比較小,所以動作幅度和尋路狀態機械一點反而好看,但是世界地圖是功能性的,雖然建築和資源點都是基於網格的,但是我們計算路徑的時候卻不能使用網格,會影響到行軍的時長和路徑。因為行軍是由服務器計算的,所以這塊我們的打算也是制作一個世界地圖的尋路系統庫,然后丟到服務器去跑,也就是說功能是客戶端做,但是丟在服務器去運行,是不是很酷。

收尾

選型是個很大的課題,這篇文章只講了技術部分的方案,后面會針對各種技術細節做探討,以及講解項目中遇到的實際問題從什么維度去思考解決方案,但在這之前還需要先講一下客戶端的目錄分布。看看一個實際的大項目是怎么在幾十個人之間合作有序,各司其職的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM