如何从零开始开发一个实时联机游戏?
本文作者為明星團隊漢家松鼠游戲工作室的CEO成功(CG),他將于11月10日在深圳舉辦的第四期騰訊游戲?qū)W院品鑒會上,分享漢家松鼠旗下《漢家江湖》、《江湖X》等游戲從立項、研發(fā)到上線的實戰(zhàn)經(jīng)驗,并談?wù)劒毩⑿F隊該如何長線經(jīng)營。
這是一篇嚴肅的聯(lián)機游戲開發(fā)入門介紹,本文所述代碼開源,文末可獲得地址。
關(guān)于游戲的實時聯(lián)機對戰(zhàn),目前是很多游戲開發(fā)者研究課題,也延伸出了很多概念,如“狀態(tài)同步”、“幀同步”,目前很多游戲開發(fā)框架也提供了這樣的開發(fā)一些理念和組件,比如unity的The High Level API等等。
本文不希望傳授如何用框架、服務(wù)端引擎等來搭建一套商業(yè)級的框架。而是希望從最基本的網(wǎng)絡(luò)通信原理開始,一點點的進行樸素的分解和搭建,旨在從原理上概述聯(lián)機游戲的設(shè)計思路以及抽象于計算機網(wǎng)絡(luò)的通信框架如何設(shè)計和構(gòu)建。
我個人編寫這套DEMO包括完整的客戶端和服務(wù)器,也是用于調(diào)研工作室游戲《漢家江湖》后續(xù)的實時聯(lián)機部分如何搭建和開發(fā),并且后續(xù)作為我們自己的一個實時聯(lián)機通信框架的測試程序。
?
?
實際完成的DEMO如上,每個玩家是一個點,只有一個操作:使用左下的虛擬搖桿進行方向控制。在移動過程中不斷噴射子彈,命中對方即可扣血,在規(guī)定時間內(nèi)比誰的擊殺的人數(shù)多。由于是一個簡單的DEMO,我們不在美術(shù)上做太多東西(請原諒我的五毛錢PS技術(shù)),主要是為了講解程序如何來設(shè)計。
工具部分,客戶端我們使用unity開發(fā),服務(wù)端我們直接從0開始寫一個基于socket的服務(wù)器。
我們使用狀態(tài)同步來做這個DEMO(理論上幀同步機制是更加適合這種IO類游戲的,我們?yōu)榱诉壿嬊逦唵?#xff0c;先用狀態(tài)同步來做,后續(xù)有時間我再補幀同步的方案。)
狀態(tài)同步
什么叫狀態(tài)同步?
簡單的來理解就是所有的數(shù)據(jù)在服務(wù)端進行計算和校驗,客戶端將操作上發(fā)到服務(wù)器,服務(wù)器不斷的告訴客戶端計算結(jié)果,由客戶端進行展現(xiàn)。
?
客戶端邏輯結(jié)構(gòu)
客戶端的整體框架邏輯非常簡單,unity是典型的單線程編程,我們只需要在FixedUpdate里出來所有的消息隊列即可。
?
其實按照socket的非阻塞模型來說,我們客戶端也可以不使用接收線程而使用純粹的單線程。我們在這里不再贅述,再次重申,我們只是很簡單粗暴的一切為了編程簡單易于理解。
客戶端維護一個消息隊列的原因是,我們每次收到的數(shù)據(jù)需要順序的執(zhí)行。在unity中我們所有改變UI或者界面相關(guān)的邏輯需要寫在主線程中。所以我們使用一個消息隊列來進行傳遞,每次FixedUpdate時依次處理所有的隊列中的消息。然后處理客戶端應(yīng)該發(fā)送的指令。需要注意的是,由于消息隊列被兩個線程同時訪問,作為臨界區(qū)數(shù)據(jù),需要加線程鎖。
服務(wù)端邏輯結(jié)構(gòu)
我們這里通信協(xié)議使用tcp(大家實現(xiàn)也可以使用udp,都類似),那么服務(wù)端是一個典型的處理連接、處理請求并分發(fā)數(shù)據(jù)的邏輯結(jié)構(gòu)。我們?yōu)榱司幊毯唵?#xff0c;也先不顧效率的每一個客戶端連接我們起一個專門的接收線程,然后統(tǒng)一分發(fā)處理,邏輯結(jié)構(gòu)如下:
?
使用單線程來處理整個游戲的主邏輯,是一個典型樸素且最簡單的編程思想。當(dāng)然,這里可能會存在瓶頸問題,所以具體中間有很多可以優(yōu)化的點,比如一些物理計算實際可以拆分成多線程或者跟進一步使用GPU、比如接收客戶端數(shù)據(jù)可以使用非阻塞模型或者線程池……這里不再多做說明。
其實我們抽象一下,以上整個框架實際上適用于任何類型的聯(lián)機游戲。對于網(wǎng)絡(luò)連接層,我們需要很清楚幾個概念:
每一個客戶端連接,在服務(wù)端維護一個session,這個session主要處理與客戶端的通信(收發(fā)數(shù)據(jù))以及該客戶端的一些臨時狀態(tài)(我們這個游戲沒有)。
通信協(xié)議
服務(wù)端維護了一份完整的數(shù)據(jù)結(jié)構(gòu),在本游戲就是當(dāng)前游戲中剩余時間、一共有多少個玩家、玩家的HP、玩家們所在位置和移動方向及速度、一共有多少顆子彈、子彈的移動方向和速度……
我們稱整個以上數(shù)據(jù)為一份全量狀態(tài),它描述了整個游戲二手QQ當(dāng)前的情況。
最樸素的思想就是服務(wù)端不斷的將整個全量狀態(tài)分發(fā)到每個客戶端,這樣客戶端就只用管顯示就行了。但實際的開發(fā)過程中會發(fā)現(xiàn)這樣飛快就會達到性能瓶頸(因為發(fā)送的數(shù)據(jù)量太多,網(wǎng)絡(luò)IO吃不住。而且也可能因為不斷的要生成全量數(shù)據(jù)快照,CPU的計算量也非常大。)所以需要優(yōu)化,接下來我們具體探討一下通信協(xié)議如何來做。
通信協(xié)議是客戶端和服務(wù)端共同約定的一個數(shù)據(jù)結(jié)構(gòu),其包含了雙方可以發(fā)送并對方可以識別處理的數(shù)據(jù)包。
在設(shè)計網(wǎng)絡(luò)協(xié)議的過程中,我們需要有一個的分層概念,我們更多的只需要來關(guān)心業(yè)務(wù)邏輯,也就是具體發(fā)送什么樣的數(shù)據(jù),底層的話這里我使用protobuf(性能最高的開源序列化、反序列化庫)。
我們使用protobuf將數(shù)據(jù)結(jié)構(gòu)序列化為二進制數(shù)據(jù),并且通過socket來進行發(fā)送,接收方收到后使用protobuf反序列化為業(yè)務(wù)協(xié)議,提供給上層邏輯代碼進行解析。所以實際上藍色部分都是使用開源庫來進行的,我們只用關(guān)注實際的游戲業(yè)務(wù)協(xié)議(綠色部分)。
我們拆解一下實際的業(yè)務(wù)協(xié)議,如下:
客戶端 -> 服務(wù)器:
1、加入游戲
2、角色行動+開火
服務(wù)器 -> 客戶端:
1、新玩家加入游戲
2、某個玩家被擊中/擊殺
3、玩家移動+開火
4、全量狀態(tài)同步(用于游戲進行到一半有玩家加入,發(fā)送給他當(dāng)前對局的整體情況)
5、時間流逝
所以實際上我們游戲的編程,就是在客戶端和服務(wù)端互相生成并處理以上數(shù)據(jù)包的過程。對應(yīng)前面的流程圖就是所有的消息隊列處理和生成。由于具體和游戲內(nèi)容相關(guān),各位有興趣可以看代碼,這里不再多做描述。
代碼結(jié)構(gòu)
另外,我需要更進一步說一下關(guān)于代碼結(jié)構(gòu)的一些思考。
由于服務(wù)端和客戶端實際上有大量的數(shù)據(jù)結(jié)構(gòu)交換,我認為一個比較好的方式是一份代碼兩邊使用。所以我服務(wù)端也是使用C#編程開發(fā),將一個脫離框架的dll(主要是業(yè)務(wù)通信協(xié)議和各種兩邊使用的數(shù)據(jù)結(jié)構(gòu)、常量和計算工具)同時分發(fā)給兩邊使用。
具體可見服務(wù)端代碼中的ShootGameServer.SharedData,這份代碼同時會生成dll到客戶端Unity的/Assets/Plugins目錄下。
?
另外關(guān)于網(wǎng)絡(luò)層的適配,我整個抽象了出來,所以大家如果有興趣的話,可以使用UDP重寫或者自己來編寫底層的通信鏈路(下圖橙色部分)。其TCP實現(xiàn)位于ShootGameServer.SharedData.Network.Impl
?
其中服務(wù)器我寫了一個通信鏈路的集成單元測試,位于:
?
開源代碼中通信鏈路KCP部分代碼我尚未集成完畢,大家可以忽略。
未來的一點點計劃
目前我們工作室計劃針對性的開發(fā)一套業(yè)務(wù)無關(guān)的網(wǎng)絡(luò)鏈路層框架,主要實現(xiàn)的功能是“開房間”-“加入游戲”-“游戲”-“結(jié)束”的一套基于云服務(wù)分布式調(diào)度的管理框架。
通俗來說就是可以開發(fā) 實時聯(lián)機的IO類游戲、百人聯(lián)機的吃雞類游戲、MOBA類游戲這種高實時性互動性要求的游戲。
我們計劃將各個模塊性能消耗優(yōu)化到極致,并且未來提供多種高度封裝易用的編程模型。
此模塊我們會先在自己的內(nèi)部的游戲項目中實踐使用,未來考慮開源+分享出來。或者提供一套便捷易用的SDK,供外部使用。
總結(jié)
以上是生活随笔為你收集整理的如何从零开始开发一个实时联机游戏?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创造开放世界——《看火人》游戏场景设计
- 下一篇: 揭秘重度MMORPG手游后台性能优化方案