遊戲中的網絡同步機制——Lockstep

0x01 爲什麼要有同步機制

一致性

在虛擬世界中,保證遊戲的一致性是一個基本前提。什麼是一致性?通俗的說就是虛擬世界中的事實,比如在一個FPS遊戲中,大家的延遲都很高,A、B兩個玩家同時發現了對方,並向對方射擊,如果沒有很好的同步機制,那麼A的屏幕上顯示B還沒有開槍就被擊殺,而B的屏幕上顯示A還沒有開槍就被擊殺,這就出現了不一致的問題,那麼這個遊戲還怎麼愉快的進行下去?

可以這麼說,延遲是造成不一致問題的主要原因。如果延遲都爲0(即A玩家作出行動的同時B玩家就能看到),那麼也就不存在不一致的問題了,就像在真實世界中一樣。而同步機制除了基本的通信作用外,最重要的任務就是解決不一致問題,即保證遊戲的一致性。同步機制有許多種,根據遊戲類型、技術條件甚至時代背景的不同,選擇的同步機制也會不同。

分類

遊戲的網絡同步機制有很多,國外也有這方面的論文,拋開具體實現細節,總體來看可以分爲下面幾類

  • Peer-to-Peer,在這類方法中,沒有服務器,遊戲參與者的身份是對等的,依靠參與遊戲的玩家電腦自行解決同步問題的,最爲典型的就是Lockstep

  • Client-Server,在這類方法中,Server端是絕對的權威,所有計算基本在Server上完成。例如,在遊戲中向前移動一步,要等待服務器確認“你向前移動了一步”之後,纔可以在客戶端上進行這個行爲。(延遲較低的時候是察覺不到這個過程的,延遲高時會有明顯的卡頓現象)

  • Client-Side Prediction,嚴格來說這並不是一類方法,而是對第二類方法的改進。試想如果所有的操作都必須在得到服務器的確認,然後纔在客戶端上進行,在延遲較高時用戶體驗會非常的差。這時可以把常用一部分計算轉移到客戶端進行,服務器輔助校正即可。

0x02 什麼是Lockstep

Lockstep最初是軍隊行進中使用的,後來在19世紀的時候廣泛在美國監獄使用,成爲那個時期美國監獄的一個標識。就像這樣

或者這樣

意思就是大家同步的走,誰超前了要等待,落後了的要趕上。後來就引申到遊戲的網絡同步機制上了

上一章節中我們說到Lockstep是Peer-to-Peer架構中的一種同步方式,而我們平時在局域網中玩Dota時,也的確沒有大型的遊戲服務器,只有一臺所謂的主機,那麼你可能會想,是不是所有的計算都是在那臺主機上完成的呢?也就是說其他玩家的機器只發送施放了某某技能這樣的數據包給主機,而造成多少傷害、某某效果是由主機計算並返回的。

但事實並不是這樣的,玩Dota所有的一切都是在本地計算完成的。包括技能傷害、效果,命中與否,隨機刷新野怪等等。也就是說每個玩家的電腦都完整計算了整盤遊戲的全過程,且計算過程與計算結果都一模一樣。而主機只是負責把每個玩家的操作指令(鼠標點擊、鍵盤按鍵等等)廣播給其他玩家。

是不是感到難以理解?

想要理解Lockstep的機制,先看看下面三個問題。

  • 什麼是動畫?是會動的畫嗎?當然不是,人眼的記憶時間爲0.1s,也就是大約100ms,只要把一幀一幀的靜態圖像快速播放一遍,我們就會感覺畫面就動了起來,比如下面這樣

  • 最容易實現同步的遊戲類型是什麼?當然是回合制遊戲,比如棋類遊戲和卡牌遊戲,它們有嚴格的先後順序,不容易出現邏輯錯誤,更不會出現不一致的情況;而且回合時間較長,能夠容忍高延遲。

  • 什麼是狀態機?狀態機是表示有限個狀態以及在這些狀態之間的轉移和動作等行爲的數學模型。給定一個狀態機模型F,處在某一個狀態S1,這時給定一個輸入I,此時狀態機會轉移到一個新的狀態S2。在這個過程中,只要FS1I是確定的,那麼S2就是確定的。

其實把以上三點結合起來,就是Lockstep的基本思路

我們來看下面這張圖

圖中是A、B、C三個玩家的時間軸,這個時間軸不是電腦上的本地時間,而是A、B、C聯機時定義的一個時間軸。虛線分隔出來時間片稱爲turn,可以理解成一個回合。箭頭表示該玩家將自己的操作指令廣播給其他玩家。我們把一盤遊戲看成一個大型的狀態機,因爲大家玩的是同一款的遊戲,因此F是相同的,初始狀態S0也是相同的。在第一個turn結束時,所有玩家都接收到了完全一樣的輸入I,注意這裏的I不是一個值,而是包含了當前遊戲中所有玩家的操作指令集合。t1時刻所有玩家的電腦自行計算結果。由於FS0I是固定的,所以每個玩家電腦上計算出的下一個狀態S1一定是相同的。

同理,第二個turn也是如此

可以看出,Lockstep其實也是“回合制”的,當然這個所謂的回合與我們理解的棋類、卡牌遊戲的回合是不太一樣的。Lockstep的回合(也就是turn)中,所有玩家都可以採取行動,最終結果是在回合結束時統一計算的。在同一個turn接收到的操作指令,是不分行動先後順序的,只要是在同一個turn裏,就認爲是同時發生的。

舉個例子,假設A、B、C是遊戲中3個互相敵對的單位,攻擊力都爲100。在某一個turn內,A和B都右鍵點擊了C(warcraft這類遊戲好像都是右鍵普攻),C右鍵點擊了A,這些操作指令都廣播到了其他玩家電腦上,則該turn的輸入爲“A攻擊C、B攻擊C、C攻擊A”。那麼該turn結束後,每個人的電腦都開始計算,且計算結果是相同的,即“A損失100生命值,B不變,C損失200生命值”。

這就是Lockstep同步機制,其實也沒有多複雜是吧~這裏還有幾點需要注意:

  • Lockstep把遊戲過程劃分成了一個個turn,爲什麼遊戲不會出現卡頓的現象呢?回到前面動畫的那個問題,人眼是容易被欺騙的,人的反應其實也是很慢的(相對電腦來說)。人眼的記憶時間爲0.1s,只要每秒進行10turn,我們是感覺不出卡頓的。而通常遊戲的幀數爲60fps,即每秒60幅圖像在屏幕上顯示,你會感覺遊戲非常流暢~當然“幀”和Lockstep中的“turn”並不是一一對應的,這裏只是想說明一個turn的時間是非常短的,至少比我們的反應要快的多。

  • Lockstep對網絡延遲的要求是非常高的,因爲每個turn要向所有玩家廣播操作,同時也要接收來自其他玩家的操作。只有當每個turn集齊了所有玩家的操作指令,也就是輸入確定了之後,纔可以進行計算,進入下一個turn,否則就要等待最慢的玩家。當然局域網可以很好的滿足這個要求,延遲基本都在1ms左右。

  • Lockstep中會不會出現延遲導致的不一致問題?顯然不會,從上面的分析可以知道,使用Lockstep的遊戲是嚴格按照turn向前推進的,如果有人延遲比較高,其他玩家必須等待該玩家跟上之後再繼續計算,不存在某個玩家領先或落後其他玩家若干個turn的情況。使用Lockstep同步機制的遊戲中,每個玩家的延遲都等於延遲最高的那個人。(當然這個說法還有待討論,後面還會說到這個問題)

  • Lockstep是非常嚴格的,要求每一步的的計算結果都完全一樣,任何計算錯誤都有可能導致蝴蝶效應,產生嚴重的後果,因爲狀態不能同步的話遊戲根本就沒有辦法進行下去,最終將崩潰退出。

  • Algorithms and Networking for Computer Games》這本書中關於Lockstep的部分與本文的說法有些不同,我覺得也沒有誰對誰錯之分,Lockstep更多的是一種思想,算是不一樣的理解吧,感興趣的同學也可以看看書中的章節。書中將Lockstep分爲commit和reveal兩個階段,並且採用了流水線的方式。如下圖所示

0x03 與Lockstep無關的細節問題

爲什麼要討論與Lockstep無關的問題呢?因爲這些問題雖然與Lockstep無關,但卻與遊戲本身有關,而且很多問題是由Lockstep引起的

野怪刷新與暴擊

我們都知道,Dota中有許多問題是與概率相關的,比如整點時野怪是隨機刷新的,出了水晶劍之後是有概率暴擊的。那麼按照Lockstep同步機制,計算都是在每個玩家自己電腦上完成的,那麼在有概率存在的情況下,怎麼可能保證每臺電腦的計算結果一致呢?!

這時就輪到僞隨機數派上用場了,我們來看下面這個Java程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Random;
public class PseudoRandom {
public static void main(String[] args) {
Random r1 = new Random(10);//這裏的10就是隨機種子(Random Seed)
for (int i = 0; i < 10; i++) {
System.out.print(r1.nextInt(100) + "\t");
}
System.out.println();
Random r2 = new Random(10);
for (int i = 0; i < 10; i++) {
System.out.print(r2.nextInt(100) + "\t");
}
}
}
//Output
//13 80 93 90 46 56 97 88 81 14
//13 80 93 90 46 56 97 88 81 14

如果你的jdk版本沒有什麼問題的話,那麼你看到的結果一定也是上面那樣,而且無論你運行多少次都是這個結果。大部分編程語言內置庫裏的隨機數都是利用線性同餘發生器產生的,如果不指定隨機種子(Random Seed),默認以當前系統時間戳作爲隨機種子。一旦指定了隨機種子,那麼產生的隨機數序列就是確定的。

所以,遊戲開始前,參與遊戲的玩家電腦協商確定一個隨機種子,就可以保證在遊戲進行過程中大家產生的隨機數序列是相同的,也就可以保證計算結果一致。

例如一個英雄的暴擊率爲30%,對某個目標持續普攻,如果隨機數序列爲12 32 90 25,小於等於30判定暴擊,大於30判定不暴擊,那麼每個玩家電腦的計算結果都是暴擊 不暴擊 不暴擊 暴擊。(這裏只是舉例說明原理,遊戲中的實現細節不一定是這樣)

外掛問題

在對戰平臺打Dota的人都見識過全圖掛,而且屢禁不止,那麼爲什麼Warcraft III中會有全圖掛,而沒有其他遊戲中的變態掛(如加強攻擊力,瞬間移動等等)呢?爲什麼無法從根本上杜絕全圖掛呢?

前面已經說過了,使用Lockstep同步機制,所有計算都是在本地完成的,每輪turn都必須接收來自其他所有玩家的操作指令才能完成計算,也就是說其他玩家的一舉一動你的電腦其實是知道的,只不過沒有在你的屏幕上展現出來罷了(被戰爭迷霧遮擋了)。因此,全圖掛只要修改Warcraft III的內存數據,就可以去除戰爭迷霧,達到開全圖的效果。而對戰平臺不可能改變Lockstep這種同步機制,只能在本地檢測是否有其他程序修改Warcraft III的內存數據(就像病毒查殺一樣),而外掛程序總有辦法繞過檢測,所以全圖掛總是層出不窮。

而另一方面,恰恰因爲Lockstep同步機制,其他比較變態的外掛根本不可能在Warcraft III上存在。比如強化攻擊力,我們同樣可以利用修改內存的方式將增加自己的攻擊力,可以直接秒殺其他任何單位,但是別忘了其他玩家電腦也在同步的進行計算,而我們的攻擊力在其他玩家電腦上仍然是不變的,這就造成了狀態不一致。前面說過了,Lockstep中出現狀態不同步的情況時很容易產生蝴蝶效應,最終崩潰退出。

斷線重連

這個問題一直被廣大玩家詬病,因爲其他網遊從來沒有斷線之後連不回去的情況,爲什麼Warcraft III不支持斷線重連呢?

Lockstep同步機制是非常嚴格的,中途加入遊戲是從技術上來講是非常困難的。首先中途加入的玩家要進行狀態同步,而狀態同步本身包含的內容太多了,其中包括時間戳、僞隨機數序列、所有單位的位置屬性信息等等,不是簡單的複製粘貼就能同步的。這說起來容易,要具體實現起來沒那麼簡單,而且在局域網中掉線情況並不常見,因此早期暴雪的開發人員可能也就忽略這個問題了。

事實上使用對戰平臺的人才會經常遇到這個問題,在互聯網中,延遲高、掉線的情況時有發生。那麼11平臺的掉線重連功能是怎麼來的呢?個人觀點,那並不是真的掉線重連,只是我們的丟包情況比較嚴重,暫時卡了而已。11平臺可能做了一些優化,將其他玩家的操作指令保存起來,等延遲穩定的時候再發送給我們。但由於此時我們電腦所處的turn可能落後了其他玩家,所以此時遊戲就會像快放一樣趕上其他玩家的進度。如果是真的掉線,如停電、程序崩潰等直接退出的情況,我們是無法重新加入遊戲的。

Warcraft III是不是使用嚴格的Lockstep機制?

接上一個問題,如果Warcraft III是嚴格的Lockstep同步機制,那麼一定會出現一人卡、大家都卡的情況,而事實上只有當我們是主機時纔會出現這種情況,其他情況下我們延遲高甚至掉線都不影響其他玩家的操作。

因此,Lockstep實際是一種理想的模型,如果在實際中使用會造成非常差的用戶體驗。那麼Warcraft III使用的到底是什麼同步機制呢?參考What every programmer needs to know about game networking這篇文章後面一個評論的說法

TOADCOP
FEBRUARY 10, 2010 AT 9:15 AM
btw afaik StarCraft don’t use peer-to-peer it uses client-server model with lockstep (at least warcraft 3 does so). It has the advantage what theoreticaly laggers will not affect gameplay/response latency at all (but to not let them fall behind server do timeouts so the lagger can catch up, also doing temporary local game speed increasing) and imo it’s the only and true way to do sync in RTS like games. (and for some reasons this technic isn’t good covered in the web)

然後是作者的回覆

GLENN FIEDLER
FEBRUARY 10, 2010 AT 10:20 AM
You are correct. Also something cool is that in a C/S RTS model the server could also theoretically arbitrate to ignore turns from lagging players, and kick them if they don’t catch up – removing various exploits where you can time-shift your packets and lag out other players.

也就是說Warcraft III使用的是基於Client-Server的Lockstep模型。這就是爲什麼Warcraft III中有主機這個概念,當然這裏主機的作用並不是完成所有計算。

(以下爲個人觀點)

Warcraft III中的主機的主要功能是廣播並設置timeout,也就是說在每個turn內,遊戲玩家並非直接將自己的操作指令廣播給其他玩家,而是先發送給主機,由主機負責廣播,且每個turn都有timeout,如果超過了timeout仍然沒有收到某個掉線玩家的操作指令,則忽略該玩家在該turn的行爲,即認定他什麼都沒有做,並與其他延遲正常的玩家同步進入下一個turn。而當掉線玩家網絡恢復時,主機會將之前保存的turn中操作指令集合發送給該名玩家,而該名玩家爲了趕上進度,就會出現遊戲快放的情況。

所以Warcraft III中只有在主機延遲高或掉線時,其他玩家纔會受影響,否則不受影響。在局域網中,如果主機是正常退出的,那麼會選定另一玩家電腦作爲主機,如果是崩潰退出的,則所有人都會直接掉線。至於在對戰平臺上是否有優化就不太清楚了。

0x04 總結

Lockstep是出現較早的一種同步機制,不過現在很多RTS遊戲中依然能夠看到它的影子,當然都對它進行了一定程度的改進。

國內關於遊戲編程和網絡同步的教材、文獻寥寥無幾,不知道是文化因素還是什麼其他原因,難道與遊戲相關的技術都是玩物喪志、不學無術?

在我查詢資料的過程中,發現國外不僅有遊戲編程和網絡同步的理論教材,還有大學開設的遊戲課程,甚至還有碩士論文是關於設計一個MMORPG遊戲的……看看國外繁榮的遊戲(使命召喚、刺客信條、魔獸世界),再看看國內繁榮的遊戲市場(頁遊?手遊?),看看國外知名的遊戲公司(Blizzard、EA、UBISOFT),再看看國內知名的遊戲公司(……企鵝?)

這裏不是想黑什麼,而是覺得計算機作爲一門多樣化的學科,我們不能固步自封,排斥其中的一些東西。有人說研究遊戲有什麼用?真的沒用嗎?看看下面這些應用

  • 網絡遊戲中廣泛應用的航位推測法(Dead Reckoning)美國軍方也在使用
  • 遊戲中的自動尋路算法,在機器人和自動駕駛中也有應用
  • 遊戲中的決策與博弈論也有關聯
  • 遊戲服務器集羣之間的負載均衡與一致性,和現在熱門的大數據息息相關

雖然不一定是由遊戲本身推動的,但是這些研究之間是相輔相成的,不會做無用功的,何況遊戲本身就是增加GDP的一個好手段╮( ̄▽ ̄)╭

本文計劃寫成一個系列,後續還會介紹其他同步機制~

0x05 相關資料

原博客地址: http://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章