介紹
作為乙個程式,你想過網路多人對戰遊戲是怎麼做出來的嗎?
從外行的角度來看多人對戰遊戲是很神奇的:2個或者更多的玩家在同乙個時間經歷了相似的遊戲經歷,感覺他們就像在同乙個虛擬世界中遊戲一樣。但是作為程式設計師我們卻知道事實並不是他們想象的那樣的,他們看到的絕大多數其實都是假象。他們所認為玩家間同時經歷的很多事件其實只是一種逼真的模擬。不同玩家間或多或少會存在不同步,程式設計師就是讓這些不同步在玩家眼中變得同步起來。 by rellikt
p2p的回合類通訊遊戲
最開始的時候遊戲的網路拓撲模型是p2p結構的,所有的機器都是p2p網路中等價的節點,他們互相傳送需要的資訊,不需要任何中轉。這種網路拓撲模型在很多rts遊戲中還很常見,甚至很多程式設計師在思考網路的時候第乙個想到的模型就是這個模型,因為這個模型和我們平時人與人間的交流模型的確很像。by rellikt
這個模型的基礎思路是把遊戲抽象成為一輪一輪的回合,在每個回合中把所有的輸入轉換成乙個個指令,然後在每個回合的開始時候處理這些指令,這些指令導致的行為自然會推動遊戲狀態機的執行。我們在乙個類似sc的rts中最常見的指令會有:移動,攻擊,建築等。選用這個模型的話,我們只要保證所有的使用者都有相同的指令集,並且他們的初始狀態相同就不會有問題了。
以上提到的網路拓撲模型看上去的確是簡單實用,但是同時這個模型的簡單也帶來了以下的一些限制:
這個模型的在可重演性上是要求相當高的:因為我們只負責指令的同步,不負責指令具體執行結果的同步,我們很可能會發現因為乙個步兵在尋路的時候稍微走的有點不一樣的地方,就導致了一場戰鬥的結果大不相同。而這樣情況導致的蝴蝶效應已經足夠讓我們遊戲體驗崩潰了。 by rellikt
這個模型的第二個缺點就是必須保證在一回合開始的時候,所有玩家的指令都已經到齊,不然這回合的指令就無法開始模擬。這就是說,所有玩家的延遲其實是取決於延遲最大的玩家的延遲。rts為了解決這個問題,通常會設定一些前搖動畫和**來讓玩家以為指令已經開始模擬了,但是實際上真正指令開始模擬的時間肯定是會和玩家輸入時間有延遲的。這些偽裝對玩家很好的掩蓋了這點。
這個模型的最後乙個缺點就是玩家必須從一開始就加入遊戲。也就是說通常這裡遊戲的模式是在大廳中開乙個房間,然後玩家加入遊戲開始玩,這種模式不會允許玩家中途加入。雖然把從開始到中途加入這段時間的指令存起來,然後讓想加入的玩家進行模擬,然後再加入是一種在理論上可行的解決方案,但是這個方案牽涉到的問題對遊戲中的可**性,可同步性的要求非常高,另外指令多了模擬時間也是問題,至少現在還沒看到哪個這類模型的遊戲做出了類似的嘗試。 by rellikt
儘管我們上面提到的這個模型有種種令人不滿意的缺點,但是在現實中的rts遊戲(比如星際1,紅警1,帝國1)裡面基本都採用了這個模型,因為這類遊戲中我們會操作成千上萬的單位,要乙個個的去同步每個單位的狀態是不科學的,我們只能去同步指令,然後通過指令去同步遊戲,推動遊戲狀態機的正常執行。
事實上在最新的rts遊戲中可能已經拋棄了p2p模式,但是回合類通訊的概念還是會得到保留,否則是無法完成成千上萬個單位的同步的。
但是時代在進步,遊戲的型別也是千差萬別的。原始的p2p的回合制在現代的很多其他型別遊戲中已經基本被拋棄了。接下來我們來看看在unreal,quake,doom中引入的fps遊戲的網路模型吧。
客戶端/伺服器端模型(c/s)
在最初的quake遊戲中,doom採用的也是p2p的回合制通訊模型,結果發現除了區域網低延遲高頻寬的情況,其他的情況下遊戲性都不能讓人滿意。by rellikt
事實上,玩家的確可以通過虛擬區域網的軟體來模擬區域網,然後通過區域網的模式進行連線。但是連線的情況實在是很悲催。那些用蜘蛛網上網的玩家就不提了(14.4kbps ppp connection或28.8kbps貓上網)。他們肯定是最杯具的。就是有寬頻的玩家也不見得能好到那裡去。因為p2p模型帶來的高延遲在fps類遊戲中是無法被很好的掩蓋的,對玩家輸入的正式模擬必須等到所有玩家的指令到達以後才能處理,也就是說你的延遲取決於當前玩家中延遲最爛的那個,如果說你的隊友中有300ms延遲的,那麼你點一下射擊鍵,就得過300ms以後才才會做出射擊模擬。這樣的情況讓很多網路不好的玩家只能望洋興嘆了。
為了讓更多網路不好的玩家也能正常的玩。2023年,john carmack在推出quake的時候使用了c/s架構取代掉了傳統的p2p架構。c/s架構和p2p架構不同,p2p在每個客戶端都會執行遊戲邏輯,遊戲顯示等完整的遊戲,因此p2p對於遊戲的可重演性要求是相當高的。c/s架構的概念就是在伺服器端跑所有的遊戲邏輯和輸入響應,在客戶端只跑所有的遊戲顯示,這樣的話客戶端只需要把自己需要的一些狀態同步下來,把使用者輸入發給伺服器端,然後顯示結果就可以了。 by rellikt
拿傳統的fps來說,理想的c/s結構中,客戶端只需要傳送自己的輸入比如移動,轉身,**給伺服器端,然後再從伺服器端把自己和周圍可見玩家的位置,朝向,動畫狀態等資訊同步下來,做一下合理的插值,使其各個角色看起來足夠流暢,然後顯示出來。乙個基本c/s架構的fps就完成了。
比較一下上面兩個模型,我們發現c/s架構最大的優點就是把延遲從最卡的玩家的延遲改變為和伺服器連線的延遲。另外使用這個架構中途加入玩家的概念也很容易就能實現了。最後在發包上面來說,在頻寬上的要求也低了不少。只需要把輸入發給伺服器端就夠了。
但是純理想的c/s模型還是有不足的地方,那就是延遲,fps對於延遲的要求是相當高的,網際網路上兩個端點間的lag有300ms是很正常的,如果說乙個轉身指令要等300ms以後才能響應的話,以9s/m跑步的速度來算,玩家就已經跑出將近3m了,也許早就掉到溝裡面去了。
為了讓更多使用爛網路的玩家能夠加入到遊戲中,john carmark在推出quake的時候也引入了客戶端**的新技術。by rellikt
客戶端**
其實在早期的fps遊戲中,我們的確會碰到按乙個鍵要等半天才能反應的情況,而這個時間就是和你的網路延遲有關的,有些強的玩家甚至能夠適應這種情況,提前做出預判操作。但是在現代的fps遊戲比如cod等遊戲中,你已經再也不會有這種體驗了,那我們現代的fps遊戲是用什麼手段來移除這些延遲的呢?
這部分的技術一般分兩塊:客戶端**和延遲補償。這些技術在unreal引擎中的網路部分中都有介紹。這裡我們著重先討論一下關於客戶端**的概念。
john carmack在推出quake的時候提到:我會在新的quake中引入客戶端**的概念,也就是說客戶端不只是簡單做一些同步和顯示。他們還會做**,也就是說在得到伺服器端資料之前,客戶端就會預先對輸入的結果做**並且**其他可見玩家的行動,並且立即進行顯示,這會使得現在的客戶端需要物理,遊戲邏輯在本地執行。原本完美的c/s模型在這裡就顯得不那麼完美,但是還是讓我們面對現實吧,現實本來就不完美。by rellikt
我們現在的情況就是客戶端在接受到本地輸入以後會直接執行一部分的遊戲邏輯**,對使用者狀態進行判斷,然後進行模擬,再顯示出來,客戶端不會等到伺服器端的資料到達以後再進行模擬了,這樣說來對客戶端來說延遲已經不存在了。
但對於客戶端**來說,重點其實不在**上,而是在同步上。我們只需要使用相同的**,**自然就不會有問題了。但是當伺服器和客戶端對於結果出現分歧的時候怎麼同步就是乙個大問題了。
談到這裡你就會想,如果說客戶端已經**了遊戲的程序,為什麼不讓伺服器端去同步客戶端的結果呢?這樣的方案看上去是不錯的,但是附帶而來的作弊問題卻是很嚴重的,如果伺服器不進行模擬,而是簡單的同步玩家狀態,那麼瞬移,無敵等外掛程式就會很常見了。玩家只需要模擬這些,發出對應的包就可以了。現在流行的許多mmorpg中的外掛程式就是這種同步而產生的結果。事實上由於對於伺服器效能上的考慮,mmo中往往只會最簡單同步一些狀態資訊和事件資訊。 by rellikt
因此在unreal的實現中tim sweency決定讓伺服器和客戶端分別模擬遊戲,來實現使用者端**來消除延遲。tim sweency在unreal networking architecture中寫道「the server is the man」.
既然這樣的話,我們很容易想到乙個有趣的問題。我們的原則是使用者狀態以伺服器模擬為準,客戶端必須即時同步伺服器上的狀態,使自己的行為和伺服器上的一致。現在的問題是:由於延遲是客觀存在的,所以伺服器上的資料總是比客戶端的要慢。事實上客戶端能夠同步到的資料只能是ping值(資料報在客戶端和伺服器端乙個來回的時間)以前那個時間點的資料。
如果客戶端只是簡單同步當前獲得的伺服器端的資料,那麼結果就是,客戶端會把ping值以前的狀態給同步回來,而做出的修改就正好是我們要做的客戶端**的那部分,如果真的這麼做,那麼我們的客戶端**就完全是無意義的存在了。by rellikt
這裡tim sweency採用的方法是採用兩個環形的緩衝,乙個記錄客戶端的狀態,我們稱其為狀態緩衝,乙個記錄客戶端的操作,我們稱其為操作緩衝,這兩個緩衝的長度應該長到至少可以容納ping值這段時間的狀態和操作。我們每過乙個固定的時間把客戶端狀態寫入狀態緩衝,每個操作都會被寫入操作緩衝。當伺服器端同步來的資料到達客戶端以後,我們先提取這個伺服器端資料所帶時間點,然後把這個時間點以前的資料從客戶端的兩個緩衝緩衝中釋放掉,然後再把狀態緩衝中最接近的那個時間點狀態資料提取出來,從那個狀態開始用操作緩衝中存的運算元據進行操作模擬,最後得到現在的狀態,再用得到的這個狀態來進行插值,同步。事實上在客戶端為了消除延遲我們一直在進行回滾然後重演的過程,這個ping值越大,我們需要回滾和重演的時間就越多,同時在得出的新狀態中可能需要插值和同步的幅度也會越大。
unreal中就是這麼處理延遲的,這個技巧運用得當的話,可以很有效的把延遲給掩蓋掉。tim sweency在unreal的***中說,如果我們的遊戲的可重演性越好,我們需要的插值就越少,甚至在其他客戶端和環境變數不變的情況下,我們是幾乎不需要任何的插值和修改的。事實上在unreal中,往往只有撞上敵人或者被火箭彈打飛了才會用到插值修改。
換句話來說只要不涉及到其他玩家的操作或者有人作弊,那麼我們採用的客戶端**往往是很準確的。by rellikt結論
關於客戶端**我現在只想談這麼多了。如果下期有時間我會再寫一些關於延遲補償的概念或者在本文中直接修改。就是延遲補償技術的存在,是使用者能在延遲的情況下照樣彈無虛發,體驗不到延遲的感覺
網路遊戲同步問題
介紹 作為乙個程式,你想過網路多人對戰遊戲是怎麼做出來的嗎?從外行的角度來看多人對戰遊戲是很神奇的 2個或者更多的玩家在同乙個時間經歷了相似的遊戲經歷,感覺他們就像在同乙個虛擬世界中遊戲一樣。但是作為程式設計師我們卻知道事實並不是他們想象的那樣的,他們看到的絕大多數其實都是假象。他們所認為玩家間同時...
網路遊戲同步問題綜述
最近看了比較多的網路同步問題因為我只做過回合制,沒做過arpg,所以稍微去看看大致是怎麼實現的。基本上可以歸為兩個問題 玩家位置同步和戰鬥指令同步。玩家位置同步的情況要比戰鬥指令同步的情況簡單,不過正因為如此,你為了更好的同步,就會採取複雜的同步策略。另外,這兩種同步其實是息息相關的,戰鬥肯定會涉及...
再談網路遊戲同步
呵呵,一年前的這個時候發過一系列討論網路遊戲同步的帖子。一年後的今天,再重新討論討論這個問題。不知道大家是否碰到過這種情況,當某個玩家發出乙個火球,這個火球有自己的運動 軌跡,那麼如何來判斷火球是否打中了人呢?大部分情況,當策劃提出這個要求的時 候,一般會被程式否認,原因是 太麻煩了,呵呵。複雜點的...