程序員都應(yīng)該了解游戲開發(fā)中的一些小常識(shí)

作為一個(gè)程序員,你有沒有想象過多人游戲是如何實(shí)現(xiàn)的?

在外行人看來游戲很神奇:兩個(gè)或者更多的玩家在網(wǎng)絡(luò)上分享共同的經(jīng)歷,就像他們真實(shí)的存在于相同的虛擬的世界一樣。游戲看起來猶如一個(gè)巨大的魔術(shù),奇妙而又刺激,但作為一個(gè)開發(fā)人員我們知道,真實(shí)的情況和我們所看到的并不一樣,那只是一種錯(cuò)覺。你感受到的共享現(xiàn)實(shí),實(shí)際上是在那個(gè)時(shí)刻內(nèi),由你自己的獨(dú)特視角和位置所感知的近似情況。

084635_zdwT_1859679.jpg

Peer-to-Peer 幀同步

最初的游戲是通過peer-to-peer來聯(lián)網(wǎng)的,每個(gè)計(jì)算機(jī)通過網(wǎng)狀拓?fù)涞慕Y(jié)構(gòu)的彼此連接并交換信息。你仍然可以看到這種模型存在于RTS游戲中,而且基于某些原因它還很有趣,也許是因?yàn)樗谴蠖鄶?shù)人認(rèn)為游戲網(wǎng)絡(luò)工作方式的第一種方式。
處理游戲信息的基本思想就是把游戲的數(shù)據(jù)抽象并轉(zhuǎn)換成一系列命令消息,當(dāng)處理每個(gè)轉(zhuǎn)換的時(shí)候就直接演變?yōu)橛螒虻臓顟B(tài)。比如:移動(dòng)單位、攻擊物體、建造建筑。這一切都需要在線的每個(gè)玩家機(jī)器,從一個(gè)初始化命令開始之后,都運(yùn)行完全相同的命令和轉(zhuǎn)換數(shù)據(jù)。
當(dāng)然了,這只是一個(gè)過于簡單的解釋,同時(shí)也隱去了很多細(xì)節(jié),不過我們通過這個(gè)基本的思路可以知道RTS游戲的網(wǎng)絡(luò)是如何工作的。如果你想知道更多網(wǎng)絡(luò)模型,請(qǐng)點(diǎn)擊:1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond,這些看起來是如此簡單和優(yōu)雅,但不幸的它們有幾個(gè)因素限制者我們:

  • 要保證游戲狀態(tài)完全確定一致的是異常困難,特別是保持每臺(tái)機(jī)器上每個(gè)轉(zhuǎn)換輸出都保持相同。比如,一個(gè)單位在兩臺(tái)機(jī)器上有略微不同的路徑,在一臺(tái)機(jī)器上早一些到達(dá)并開始了戰(zhàn)斗,結(jié)果反敗為勝,而在另一臺(tái)機(jī)器上,由于稍微晚一些到達(dá)而失敗。就像一只蝴蝶扇動(dòng)了翅膀,然后在世界的另一邊導(dǎo)致了颶風(fēng)的出現(xiàn),隨著時(shí)間的推移,一個(gè)微小的區(qū)別就會(huì)導(dǎo)致兩邊完全的不同步。

  • 為了保證游戲的所有玩家輸出一致,這就需要等到所有玩家的當(dāng)前回合數(shù)據(jù)都到達(dá)之后才可以模擬播放這一回合動(dòng)作。這就意味著游戲中的每一個(gè)玩家都需要等待網(wǎng)絡(luò)延遲最高的那個(gè)玩家。RTS游戲通常代表性地通過立即提供音頻反饋與(或是)播放吟唱(過渡)動(dòng)畫來掩蓋這段延遲,但是最終真正影響游戲的動(dòng)作要在這段延遲過去之后才能進(jìn)行。

  • 因?yàn)橛螒蛑袪顟B(tài)改變的同步是通過發(fā)送命令信息來同步的。所以為了游戲中玩家狀態(tài)都一致,需要所有的玩家都要從相同的初始狀態(tài)來開始游戲。這意味著每個(gè)玩家必須在開始游戲之前先加入房間然后一起開始游戲,盡管理論上也可以支持讓某些玩家晚些加入游戲,但是在一場進(jìn)行中的游戲中獲得一個(gè)完全確定的起始點(diǎn)的難度相當(dāng)大,所以這種情況并不常見。

盡管有這些因素限制困擾者我們,不過這個(gè)模型還是很適合RTS游戲的,并且它仍然存在于今天的游戲當(dāng)中,例如“Command and Conquer”、“Age of Empires”與“Starcraft”等。原因就是在RTS游戲中,里面包含了上千多的單位,這些單位都有自己的狀態(tài)需要同步,而且他們數(shù)據(jù)量都太大了,很難用來在玩家之間交換。別無選擇,我們只能通過這些游戲狀態(tài)改變的命令來同步。

所以以上這些就是 peer-to-peer 幀同步的網(wǎng)路游戲模型的介紹了,對(duì)于其他類型的游戲,最先進(jìn)的技術(shù)已經(jīng)開始出現(xiàn)了。讓我們現(xiàn)在從Doom, Quake 以及 Unreal經(jīng)典游戲中開始一起觀察動(dòng)作游戲的技術(shù)演化。

客戶端/服務(wù)器(c/s架構(gòu))

在動(dòng)作游戲的時(shí)代,以上幀同步的限制在Doom 游戲中變得更加明顯,盡管在局域網(wǎng)中體驗(yàn)還不錯(cuò),但在對(duì)于互聯(lián)網(wǎng)的用戶來說它體驗(yàn)太糟糕了:

盡管可以使用一個(gè)貓(調(diào)制解調(diào)器)把兩個(gè)Doom 機(jī)器通過互聯(lián)網(wǎng)連接在一起,但他們一起游戲會(huì)異常緩慢。范圍從無法游戲(例如:14.4Kbps PPP 連接)到稍微可以玩(例如 :28.8Kbps 貓運(yùn)行一個(gè)被SLIP驅(qū)動(dòng)壓縮的數(shù)據(jù))之間游戲聯(lián)機(jī)都異常緩慢。由于這些連接方式只是邊際效用,本文將僅關(guān)注直接的網(wǎng)絡(luò)連接。

這個(gè)問題是因?yàn)镈oom網(wǎng)絡(luò)部分本來就是只為局域網(wǎng)而設(shè)計(jì)的,并且使用了前面介紹的peer-to-peer 幀同步模型。每一回合每個(gè)玩家的輸入的信息(比如關(guān)鍵按鍵等)都與其他人進(jìn)行同步通知,并且任何玩家在播放這一幀動(dòng)畫之前,必須得等到所有其他玩家的關(guān)鍵按鍵信息都被接收到,才可以去模擬播放。

也就是說,在你可以轉(zhuǎn)身(轉(zhuǎn)換),移動(dòng)或者射擊之前,你必須等待延遲最大的貓(調(diào)制調(diào)解器)玩家的輸入。只是想想上述那個(gè)人所寫的“這些連接方式只是邊際效用”就會(huì)讓人咬牙切齒和沮喪了。

為了改變這種現(xiàn)狀,只能在局域網(wǎng)以及大學(xué)網(wǎng)絡(luò)和大型企業(yè)才能獲得良好連接而進(jìn)行游戲,是需要改變這種網(wǎng)絡(luò)模型了。在1996年,這變成了現(xiàn)實(shí)并被實(shí)現(xiàn)了,John Carmack當(dāng)時(shí) 發(fā)布雷神之錘,他采用客戶端/服務(wù)器(C/S)架構(gòu)代替了P2P模型。

如今游戲中的玩家可以不必再運(yùn)行相同的代碼以及直接相互通信,每個(gè)玩家的機(jī)器是都是一個(gè)“客戶端”,他們都通過一臺(tái)叫做“服務(wù)器”的機(jī)器進(jìn)行通信交互。游戲的最終狀態(tài)確定不再依賴于每臺(tái)客戶端機(jī)器來共同確認(rèn),而是由服務(wù)器來確定最終結(jié)果。每個(gè)客戶端如同一個(gè)啞終端,用來展示一個(gè)近似值的表演,真是的游戲狀態(tài)是運(yùn)行于服務(wù)器之上。

在一個(gè)純粹的c/s架構(gòu)中,你不必在本地運(yùn)行游戲代碼,而是把一些例如按鍵、鼠標(biāo)移動(dòng),點(diǎn)擊等輸入信息發(fā)送到服務(wù)器。服務(wù)器會(huì)在游戲世界中更新你的玩家狀態(tài),然后再封包一個(gè)包含你角色信息以及臨近玩家數(shù)據(jù)的包回復(fù)給你的客戶端。所有的客戶端在每個(gè)消息更新的間隙做一個(gè)插值預(yù)測,以改善在每個(gè)狀態(tài)更新期間,物體可以平滑的移動(dòng),如此,你就有一個(gè)可以聯(lián)網(wǎng)的客戶端/服務(wù)器架構(gòu)的游戲了。

這已經(jīng)是向前邁出了極大的一步。游戲的體驗(yàn)依賴于客戶端和服務(wù)器的連接,而不是游戲中延遲最大的那個(gè)玩家。如此可以支持玩家在游戲中自由的進(jìn)入和退出,同時(shí)由于客戶端/服務(wù)器降低了平均每位玩家的帶寬,從而可以增加更多的在線玩家。

但是這里仍然有一些問題存在于 c/s 架構(gòu)中:

  • 我記得我交代了所有從DOO到Quake中關(guān)于網(wǎng)絡(luò)的決策,但是重要的是我正在使用錯(cuò)誤的假設(shè)來做一個(gè)好的網(wǎng)絡(luò)游戲。我原先設(shè)計(jì)的目標(biāo)是網(wǎng)絡(luò)延遲<200ms。人們通過一個(gè)好的網(wǎng)絡(luò)供應(yīng)商連接互聯(lián)網(wǎng),從而可以獲得一個(gè)好的游戲體驗(yàn)。但事與愿違,世界上99%的用戶使用貓(調(diào)制調(diào)解器)通過 slip或者ppp 進(jìn)行連接,而他們常常都會(huì)通過槽糕而又擁擠的ISP。這會(huì)帶來最低300+ms 的 網(wǎng)絡(luò)延遲。一個(gè)消息要經(jīng)過,客戶端>用戶貓>ISP貓>服務(wù)器>ISP貓>用戶貓>客戶端。上帝,這太遜了。

OK,我做了一個(gè)錯(cuò)誤的設(shè)定。我在家里使用T1 寬帶,所以我只是不了解在PPP網(wǎng)絡(luò)下的生活。我現(xiàn)在就解決它。這個(gè)問題當(dāng)然是延遲,接下來John在他發(fā)布QuakeWorld的時(shí)候?qū)⒏淖冞@個(gè)行業(yè)。

客戶端預(yù)測(Client-Side Prediction)

在原來的Quake游戲中,你會(huì)感覺到電腦與服務(wù)器之間的延遲。比如,你按鍵向前移動(dòng),在你真正移動(dòng)之前,你需要等到數(shù)據(jù)包發(fā)送服務(wù)器然后再回復(fù)到你的客戶端,你才可以真正的移動(dòng)。按鍵開火,在你的射擊之前同樣需要相同的等待。

如果你玩過任何FPS游戲,比如:Modern Warfar,你會(huì)發(fā)現(xiàn)并沒有延遲發(fā)生。那么fps游戲是如何做到在多人情況下,你的動(dòng)作看起來并沒有延遲?

這個(gè)問題被分為兩個(gè)部分來解決。第一個(gè)部分是客戶端移動(dòng)預(yù)測,這事John Carmack 為 QuakeWorld游戲多開發(fā)的,后來被合并到了Tim Sweeney的虛幻網(wǎng)絡(luò)模塊。第二個(gè)部分就是延遲補(bǔ)償,它是有Valve公司的Yahn Bernier在Counterstrike所開發(fā)。那么在這個(gè)章節(jié),我們把焦點(diǎn)放在第一部分——隱藏用戶移動(dòng)的延遲。

當(dāng)寫到關(guān)于他即將發(fā)布的QuakeWorld計(jì)劃的時(shí)候,John Carmack 講到:
我現(xiàn)在允許客戶端可以預(yù)測用戶的移動(dòng),直到服務(wù)器的權(quán)威信息回復(fù)之前。這是一個(gè)重大的結(jié)構(gòu)變更。客戶端需要知道關(guān)于對(duì)象的硬度、摩擦力、重力等一系列基礎(chǔ)屬性。我很傷心的看到,客戶端僅作為一個(gè)終端存在將會(huì)離開,但作為一個(gè)實(shí)用主義者,我必須超越這種理想情懷。

那么現(xiàn)在我們?yōu)榱讼舆t,客戶端需要運(yùn)行更多的代碼。它現(xiàn)在不再是一個(gè)只把輸入發(fā)送給服務(wù)器然后再把返回信息進(jìn)行插入的啞終端。現(xiàn)在客戶端的機(jī)器可以運(yùn)行一部分游戲代碼,它可以在本地預(yù)測你的角色移動(dòng)并且可以即時(shí)響應(yīng)你的輸入。

現(xiàn)在當(dāng)你即刻按鍵向前,你的游戲會(huì)立刻向前移動(dòng),不會(huì)再去等待數(shù)據(jù)往返一次客戶端和服務(wù)器之間才來回應(yīng)你的操作。

這種方式的難點(diǎn)不在于預(yù)測,這種預(yù)測工作,就像正常的游戲代碼一樣 —— 根據(jù)玩家的輸入,及時(shí)地更新游戲角色的狀態(tài)。而難點(diǎn)在于,當(dāng)客戶端和服務(wù)器對(duì)于玩家角色所做的事情(動(dòng)作)核檢不一致的時(shí)候,客戶端如何基于服務(wù)器信息進(jìn)行更正。

現(xiàn)在你會(huì)想,hey,如果代碼運(yùn)行在客戶端——為何不以客戶端的信息為準(zhǔn)?客戶端可以自己的為角色模擬運(yùn)行代碼,并且只需要在每次發(fā)送數(shù)據(jù)包時(shí)告知服務(wù)器這些信息。如果每個(gè)客戶端都對(duì)服務(wù)器發(fā)送相同的信息,告訴服務(wù)器“這是我現(xiàn)在的位置信息”,那么將會(huì)帶來這樣的問題。客戶端會(huì)很容易被黑客攻擊并控制,這樣在RPG游戲中,一個(gè)作弊便可以立即躲避對(duì)方技能擊中,或者當(dāng)你射擊的時(shí)候瞬間移動(dòng)到你的身后。

所以在FPS游戲中,盡快每個(gè)玩家的客戶端可以預(yù)測他們自己的角色進(jìn)行操作移動(dòng),但最終每個(gè)玩家的角色狀態(tài)絕對(duì)以服務(wù)器為準(zhǔn)。就像Tim Sweeney 在所寫的文章The Unreal Networking Architecture中描述的一樣:“服務(wù)器才是主人”。

這就是有趣的地方。如果客戶端和服務(wù)器產(chǎn)生了不一致,客戶端必須基于服務(wù)器的信息為準(zhǔn)并更新,但是由于客戶端和服務(wù)器之前有延遲,服務(wù)器的修正必然是過去的動(dòng)作。比如,如果信息從客戶端到服務(wù)器耗時(shí)100ms,然后返回又耗時(shí)100ms,那么任何服務(wù)器的的修正都是客戶端200ms之前的行為動(dòng)作,這個(gè)時(shí)間正好是客戶端預(yù)測角色移動(dòng)的時(shí)間。

如果客戶端每個(gè)動(dòng)作都會(huì)被服務(wù)器修正,那么你將會(huì)看客戶端被拉回了原先的位置,如此客戶端將做不了任何預(yù)先預(yù)測的運(yùn)算。那么我們?nèi)绾谓鉀Q這個(gè)問題,依然可以保持客戶端提前預(yù)測?

解決方案就是在客戶端創(chuàng)建一個(gè)buffer,然后用來循環(huán)保持角色的狀態(tài)以及本來玩家的輸入。當(dāng)客戶端收到了服務(wù)器的更正信息時(shí)候,它首先丟棄掉buffer里面比(服務(wù)器回復(fù)的)更正狀態(tài)要老的狀態(tài)信息,然后基于(更正的)正確的狀態(tài)重放存儲(chǔ)在buffer里面的輸入信息,重發(fā)的這些輸入信息的范圍是從正確狀態(tài)到當(dāng)前預(yù)測時(shí)間之間。如此實(shí)際上,客戶端只是看似無形中“倒帶和重放”當(dāng)?shù)赝婕医巧\(yùn)動(dòng)的最后n幀,同時(shí)保持世界其他地方?jīng)]有變化。

這種方法可以讓玩家感覺在控制游戲的時(shí)候沒有延遲,同時(shí)也改善了客戶端和服務(wù)器之間代碼運(yùn)行的一致性——在同等輸入的情況下保持一致的結(jié)果。當(dāng)然了,修正的情況很少發(fā)生,Tim Sweeney 如此描述:

對(duì)于客戶端和服務(wù)器最好的是:所有情況下,服務(wù)器都是權(quán)威的。幾乎所有的時(shí)間,客戶端模擬的和服務(wù)器的數(shù)據(jù)都是一致,所以客戶端的位置很少被修正。只有的少數(shù)罕見的情況下,例如一個(gè)玩家被火箭擊中,或者和一個(gè)敵人(怪物)碰撞上,那么客戶端本地的情況有可能需要被修正。

也就是說,只有當(dāng)玩家的角色被一些外部事件影響玩家的輸入,并且這些不能被客戶端預(yù)測時(shí),玩家的位置(行為)需要被服務(wù)器修正。當(dāng)然,如果玩家試圖作弊,必然會(huì)被服務(wù)器修正。

這是一篇翻譯文章,主要針對(duì)游戲的網(wǎng)絡(luò)設(shè)計(jì),目前主流的網(wǎng)絡(luò)游戲?qū)崿F(xiàn)方案都有講解,如果對(duì)英文更感興趣,請(qǐng)查看文章尾部鏈接,你若是覺得有翻譯不妥的地方,歡迎留言指正。原文地址:[點(diǎn)此看原文](http://gafferongames.com/networking-for-game-programmers/what-every-programmer-needs-to-know-about-game-networking/)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容