網絡游戲同步的算法


轉自:https://blog.csdn.net/lovemysea/article/details/78888739

不知道大家是否碰到過這種情況,當某個玩家發出一個火球,這個火球有自己的運動軌跡,那么如何來判斷火球是否打中了人呢?大部分情況,當策划提出這個要求的時候,一般會被程序否認,原因是:太麻煩了,呵呵。復雜點的還有包括兩個火球相撞之類的事情發生。

     那么網絡游戲中,是否真的無法模擬實現這種模擬呢?

     首先我們來看看模擬此種操作會帶來什么樣的麻煩:

     1,服務器必須trace火球的運行軌跡,乍一想,挺慢的。

     2,網絡延遲,傳過來有延遲,傳過去有延遲,延遲還不穩定,麻煩。

     3,都有兩點解決不了了,接下來不願意再想了。

     呵呵,實際上呢,對火球的模擬比對人物運動的模擬要輕松很多,原因很簡單,火球的方向不會變。下面來看看具體用什么樣的結構來實現:

     不知道大家是否還記得我去年這個時候提到過的Dead Reckoning算法,我們要模擬火球運動的關鍵就在於一個叫Moving Objects Tracing Server的服務器程序,這個服務器是干什么的呢。這個服務器接收主游戲服務器發過來的注冊事件的信息,比如有個玩家,開始移動了,那么主游戲服務器就 把該玩家的運動PDU,包括方向,速度,加速度,起點發給MOTS (Moving Objects Tracing Server),然后MOTS自己開始對其運行進行模擬,當游戲服務器發來第二個PDU包的時候,則對各個物件的位置進行修正,並重新開始模擬。那么,我 們模擬的目的是什么呢?當然是發生某些事件,比如說碰撞,或者掉入地圖的某個陷阱的時候,會將該事件回發給主邏輯服務器。然后邏輯服務器來處理該事件。

     那么,對於火球的處理,也和處理其他玩家的同步一樣,當接收到玩家的發火球的指令以后,產生一個火球,並指定其PDU信息,在MOTS上注冊該個運動物 體。當MOTS自行模擬到這個物體和其他玩家或者NPC物體產生碰撞,則通知主邏輯服務器,然后主邏輯服務器產生相應的動作。

     那么關於延遲呢?有些人也許會說,比如說前面有個火球,我本地操縱的小人其實躲過去了,但是因為網絡延遲,在服務器上我並沒有躲過去,那么怎么算?呵呵, 不知道大家玩過星際沒有,有沒有發現在星際中玩多人連線模式的時候,有一點最特別的地方,就是控制一個小兵的時候,點了地圖上的某個位置,但是小兵並不會 馬上開始移動,而是有一定的延遲,但是這一小點延遲並不能掩蓋星際的經典,同樣的理論用到這里也成立。對於客戶端的控制,當玩家操縱的主角改變PDU信息 的時候,確保信息發送到服務器之后,再開始處理本地的操作指令,這樣就能保證本地的預測和服務器的預測幾乎是沒有什么誤差的,即使有很小的誤差產生,以服務器為主,這樣玩家也不會有太大的抱怨。

 

網絡游戲同步詳解之一

同步在網絡游戲中是非常重要的,它保證了每個玩家在屏幕上看到的東西大體是一樣的。其實呢,解決同步問題的最簡單的方法就是把每個玩家的動作都向其 他玩家廣播一遍,這里其實就存在兩個問題:

1,向哪些玩家廣播,廣播哪些消息。

2,如果網絡延遲怎么辦。

  事實上呢,第一個問題是個非常簡單的問題,不過之 所以我提出這個問題來,是提醒大家在設計自己的消息結構的時候,需要把這個因素考慮進去。而對於第二個問題,則是一個挺麻煩的問題,大家可以來看這么個例子: 
比如有一個玩家A向服務器發了條指令,說我現在在P1點,要去P2點。指令發出的時間是T0,服務器收到指令的時間是T1,然后向周 圍的玩家廣播這條消息,消息的內容是“玩家A從P1到P2”有一個在A附近的玩家B,收到服務器的這則廣播的消息的時間是T2,然后開始在客戶端上畫圖,A從P1到P2點。這個時候就存在一個不同步的問題,玩家A和玩家B的屏幕上顯示的畫面相差了T2-T1的時間。這個時候怎么辦呢?

  有個解決方案,我給它取名叫預測拉扯,雖然有些怪異了點,不過基本上大家也能從字面上來理解它的意思。要解決這個問題,首先要定義一個值叫:預測誤差。然后需要在服務器端每個玩家連接的類里面加一項屬性,叫TimeModified,然后在玩家登陸的時候,對客戶端的時間和服務器的時間進行比較,得出來的差值保存在TimeModified里面。還是上面的那個例子,服務器廣播消息的時候,就根據要廣播對象的TimeModified,計算出一個客戶端的CurrentTime,然后在消息頭里面包含這個CurrentTime,然后再進行廣播。並且同時在玩家A的客戶端本地建立一個隊列,保存該條消息,只到獲得服務器驗證就從未被驗證的消息隊列里面將該消息刪除,如果驗證失敗,則會被拉扯回P1點。然后當玩家B收到了服務器發過來的消息“玩 家A從P1到P2”這個時候就檢查消息里面服務器發出的時間和本地時間做比較,如果大於定義的預測誤差,就算出在T2這個時間,玩家A的屏幕上走到的地點 P3,然后把玩家B屏幕上的玩家A直接拉扯到P3,再繼續走下去,這樣就能保證同步。更進一步,為了保證客戶端運行起來更加smooth,我並不推薦直接 把玩家拉扯過去,而是算出P3偏后的一點P4,然后用(P4-P1)/T(P4-P3)來算出一個很快的速度S,然后讓玩家A用速度S快速移動到P4,這 樣的處理方法是比較合理的,這種解決方案的原形在國際上被稱為(Full plesiochronous),當然,該原形被我篡改了很多來適應網絡游戲的同步,所以而變成所謂的:預測拉扯。

  另外一個解決方案,我給它取名叫 驗證同步,聽名字也知道,大體的意思就是每條指令在經過服務器驗證通過了以后再執行動作。具體的思路如下:首先也需要在每個玩家連接類型里面定義一個 TimeModified,然后在客戶端響應玩家鼠標行走的同時,客戶端並不會先行走動,而是發一條走路的指令給服務器,然后等待服務器的驗證。服務器接受到這條消息以后,進行邏輯層的驗證,然后計算出需要廣播的范圍,包括玩家A在內,根據各個客戶端不同的TimeModified生成不同的消息頭,開始廣播,這個時候這個玩家的走路信息就是完全同步的了。這個方法的優點是能保證各個客戶端之間絕對的同步,缺點是當網絡延遲比較大的時候,玩家的客戶端的行 為會變得比較不流暢,給玩家帶來很不爽的感覺。該種解決方案的原形在國際上被稱為(Hierarchical master-slave synchronization),80年代以后被廣泛應用於網絡的各個領域。

  最后一種解決方案是一種理想化的解決方案,在國際上被稱為Mutual synchronization,是一種對未來網絡的前景的良好預測出來的解決方案。這里之所以要提這個方案,並不是說我們已經完全的實現了這種方案,而只是在網絡游戲領域的某些方面應用到這種方案的某些思想。我對該種方案取名為:半服務器同步。大體的設計思路如下:

  首先客戶端需要在登陸世界的時候建立很多張廣播列表,這些列表在客戶端后台和服務器要進行不及時同步,之所以要建立多張列表,是因為要廣播的類 型是不止一種的,比如說有local message,有remote message,還有global message 等等,這些列表都需要在客戶端登陸的時候根據服務器發過來的消息建立好。在建立列表的同時,還需要獲得每個列表中廣播對象的TimeModified,並且要維護一張完整的用戶狀態列表在后台,也是不及時的和服務器進行同步,根據本地的用戶狀態表,可以做到一部分決策由客戶端自己來決定,當客戶端發送這部分決策的時候,則直接將最終決策發送到各個廣播列表里面的客戶端,並對其時間進行校對,保證每個客戶端在收到的消息的時間是和根據本地時間進行校對過的。 那么再采用預測拉扯中提到過的計算提前量,提高速度行走過去的方法,將會使同步變得非常的smooth。該方案的優點是不通過服務器,客戶端自己之間進行 同步,大大的降低了由於網絡延遲而帶來的誤差,並且由於大部分決策都可以由客戶端來做,也大大的降低了服務器的資源。由此帶來的弊端就是由於消息和決策權都放在客戶端本地,所以給外掛提供了很大的可乘之機。

  綜合以上三種關於網絡同步派系的優缺點,綜合出一套關於網絡游戲傳輸同步的較完整的解決方案,我稱它為綜合同步法(colligate synchronization)。大體設計思路如下:

  首先將服務器需要同步的所有消息從划分一個優先等級,然后按照3/4的比例划分出重要消息和非重要消息,對於非重要消息,把決策權放在客戶端,在客戶端邏輯上建立相關的決策機構和各種消息緩存區,以及相關的消息緩存區管理機構,如下圖所示:

  上圖簡單說明了對於非重要消息,客戶端的大體處理流程,其中有一個客戶端被動行為值得大家注意,其中包括對服務器發過來的某些驗證代碼做返回, 來確保消息緩存中的消息和服務器端是一致的,從而有效的防止外掛來篡改本地消息緩存。其中的消息來源是包括本地的客戶端響應玩家的消息以及遠程服務器傳遞 過來的消息。

  對於重要消息,比如說戰斗或者是某些牽扯到玩家一些比較敏感數據的操作,則采用另外一套方案,該方案首先需要在服務器和客戶端之間建立一套 Ping System,然后服務器保存和用戶的及時的ping值,當ping比較小的時候,響應玩家消息的同時先不進行動作,而是先把該消息反饋給服務器,並且阻 塞,服務器收到該消息,進行邏輯驗證之后向所有該詳細廣播的有效對象進行廣播(包括消息發起者),然后客戶端收到該消息的驗證,才開始執行動作。而當 ping比較大的時候,客戶端響應玩家消息的同時立刻進行動作,並且同時把該消息反饋給服務器,值得注意的是這個時候還需要在本地建立一個無驗證消息的隊 列,把該消息入隊,執行動作的同時等待服務器的驗證,還需要保存當前狀態。服務器收到客戶端的請求后,進行邏輯驗證,並把消息反饋到各個客戶端,帶上各個 客戶端校對過的本地時間。如果驗證通過不過,則通知消息發起者,該消息驗證失敗,然后客戶端自動把已經在進行中的動作取消,恢復原來狀態。如果驗證通過, 則廣播到的各個客戶端根據從服務器獲得校對時間進行對其進行拉扯,保證在該行為完成之前完成同步。

  至此,一個比較成熟的網絡游戲的同步機制已經初步建立起來了,接下來的邏輯代碼就根據各自不同的游戲風格以及側重點來寫了。

  同步是網絡游戲最重要的問題,如何同步也牽扯到各個方面的問題,比如說游戲的規模,游戲的類型以及各種各樣的方面,對於規模比較大的游戲,在同步方面可以下很多的工夫,把消息分得十分的細膩,對於不同的消息采用不同的同步機制,而對於規模比較小的游戲,則可以采用大體上一樣的同步機制,究竟怎么 樣同步,沒有個定式,是需要根據自己的不同情況來做出不同的同步決策的網游同步算法之導航推測(Dead Reckoning)算法。

例:

位置同步是一種典型的狀態同步.

需要提前做好對時, 國內的公網通信, 非跨網時一般在 120ms 左右, 移動端的波動很大.

考慮到防外掛的需求, 即便以 client 的通知為主, 也要做校驗, 如果 CPU 壓力比較大, 可以按照一定策略抽查.

為了保證表現平滑, 用戶體驗好, client 一般會應用 DR 算法做優化.

為了平衡流量和效果, 需要控制同步包的幀率, 這一點在手游上需要特別重視.

以 client 為主, server 只做簡單校驗, 不做邏輯運算, 優點是比較平滑, 但是仍然有安全性的風險.

  • 位置包 PKG 數據包括: 當前位置 P, 速度 V, 時間戳 T
  • client 發送 PKG 到 server, 發包的觸發條件有兩個: 超過了 DR 的閾值, 或者定時 tick.
  • server 收到 PKG, 校驗合法性, 不合法則拉回合法位置. 根據對時時差, 預測 client 當前位置, 並把計算結果廣播出去, 包括 client 自己.
  • client A 收到 PKG, 根據對時視察, 計算實際位置 P.
  • 如果 A 收到的是 client B 的位置, 需要把 B 修正到最新的位置 P. 常見的算法是 DR. 一個簡單的方法是預測一段時間(例如 1S)之后 B 的位置, 用一條直線運動去修正. 如果 B 的網絡特別差的情況下, A 看到的畫面上 B 的速度可能會突然加快一下, 但是相對瞬移來說好很多.
  • 如果 A 收到的是自己的位置, 即 server 認可得自己的狀態(並廣播給了別人). 如果有誤差, 是由於 server 得預測補償和網絡延遲的誤差造成得. 如果誤差比較大超過了預設的閾值, 直接跳轉過去, 否則不處理.

以 server 為主的處理流程:

以 server 得計算和模擬為主, client 只在變化速度或者方向的時候, 通知 server, 特點是安全性高, 邏輯簡單, 代價是 server 的 CPU 消耗大.

與 client 為主的處理流程不一樣得是第2步, server 的計算也是兩個觸發條件:

  • 時間觸發, server 定時做 server move, 來模擬計算並更新所有 client 的位置信息.
  • 消息觸發, 收到 client 的位置 PDU.

網絡游戲同步詳解之二

在了解該算法前,我們先來談談該算法的一些背景資料。大家都知道,在網絡傳輸的時候,延遲現象是很普遍的,而在基於Server/Client結構 下的網絡游戲的同步也就成了很頭疼的問題,在保證客戶端響應用戶本地指令流暢的情況下,沒法有效的保證的同步的及時性。同樣,在軍方也有類似的事情發生, 即使是同一LAN里面的機器,也會因為傳輸的延遲,導致一些運算的失誤,介於此,美國國防部投入了大量的資金用於研究一種比較的好的方案來解決分布式系統中的延遲問題,特別是一個叫分布式模擬運動(Distributed Interactive Simulation)的系統,這套系統呢,其中就提出了一套號稱是Latency Hiding & Bandwidth Reduction的方案,命名為Dead Reckoning。呵呵,來頭很大吧,恩,那么我們下面就來看看這套系統的一些觀點,以及我們如何把它運用到我們的網絡游戲的同步中。

  首先,這套同步方案是基於我那篇《網絡游戲的同步》一文中的Mutual Synchronization同步方案的,也就是說,它並不是Server/Client結構的,而是基於客戶端之間的同步的。下面我們先來說一些本文中將用到的名詞概念:
網狀網絡:客戶端之間構成的網絡
節點:網狀網絡中的每個客戶端
極限誤差:進行同步的時候可能產生的誤差的極值

  恩,在探討其原理的之前,我們先來看看我們需要一個什么樣的環境。首先,需要一個網狀網絡,網狀網絡如何構成呢?當有新節點進入的時候,通知該 網絡里面的所有節點,各節點為該客戶端在本地創建一個副本,登出的時候,則通知所有節點銷毀本地關於該節點的副本。然后每個節點該保存一些什么數據呢?首 先有一個很重要的包需要保存,叫做協議數據包(PDU Protocol Data Unit),PDU包含節點的一些相關的運動信息,比如當前位置,速度,運動方向,或者還有加速度等一些信息。除PDU之外,還有其他信息需要保存,比如 說節點客戶端人物的HP,MP之類的。然后,保證每個節點在最少8秒之內要向其它節點廣播一次PDU信息。最后,設置一個極限誤差值。到此,其環境就算搭 建完成了。下面,我們就來看看相關的具體算法:

  假設在節點A有一個小人(路人甲),開始跑路了,這個時候,就像所有的節點廣播一次他的PDU信息,包括:速度(S),方向(O),加速度 (A)。那么所有的節點就開始模擬路人甲的運動軌跡和路線,包括節點A本身(這點很重要),同時,路人甲在某某玩家的控制下,會不時的改變一下方向,讓其 跑路的路線變得不是那么正規。在跑路的過程中,節點A有一個值在不停的記錄着其真實坐標和在后台模擬運動的坐標的差值,當差值大於極限誤差的時候,則計算 出當前的速度S,方向O和速度A(算法將在后面介紹),並廣播給網絡中其他所有節點。其他節點在收到這條消息之后呢,就可以用一些很平滑的移動把路人甲拉 扯過去,然后重新調整模擬跑路的數據,讓其繼續在后台模擬跑路。

  很顯然,如果極限誤差定義得大了,其他節點看到的偏差就會過大,如果極限偏差定義得小了,網絡帶寬就會增大。如果定義這個極限誤差,就該根據各 種數據的重要性來設計了。如果是回合制的網絡游戲,那么在走路上把極限誤差定義得大些無所謂,可以減少帶寬。但是如果是及時打斗的網絡游戲,那么就得把極 限誤差定義得小一些,否則會出現某人看到某人老遠把自己給砍死的情況。

  Dead Reckoning的主要算法有9種,但是只有兩種是解決主要問題的,其他的基本上只是針對不同的坐標系的一些不同的算法,這里就不一一介紹了。好,那么我們下面來看傳說中的最主要的兩種算法:
第一:目標點 = 原點 + 速度 * 時間差
第二:目標點 = 原點 + 速度 * 時間差 + 1/2 * 加速度 * 時間差
呵呵,傳說中的算法都是很經典的,雖然我們早在初中物理的時候就學過。

  該算法的好處呢,正如它開始所說的,Latency Hiding & Bandwidth Reduction,從原則上解決了網絡延遲導致的不同步的問題,並且有效的減少了帶寬,不好的地方就是該算法基本上只能使用於移動中的同步,當然,移動 的同步是網絡游戲中同步的最大的問題。

  該方法結合我在《網絡游戲的同步》一文中提出的綜合同步法的構架可以基本上解決掉網絡游戲中走路同步的問題。相關問題歡迎大家一起討論。

     有關導航推測算法(Dead Reckoning)中的平滑處理:

  根據我上篇文章所介紹的,在節點A收到節點B新的PDU包時,如果和A本地的關於B的模擬運動的坐標不一致時,怎么樣在A的屏幕上把B拽到新的 PDU包所描敘的點上面去呢,上文中只提了用“很平滑的移動”把B“拉扯”過去,那么實際中應該怎么操作呢?這里介紹四種方法。

  第一種方法,我取名叫直接拉扯法,大家聽名字也知道,就是直接把B硬生生的拽到新的PDU包所描敘的坐標上去,該方法的好處是:簡單。壞處是:看了以下三種方法之后你就不會用這種方法了。

  第二種方法,叫直線行走(Linear),即讓B從它的當前坐標走直線到新的PDU包所描敘的坐標,行走速度用上文中所介紹的經典算法:
目標點 = 原點 + 速度 * 時間差 + 1/2 * 加速度 * 時間差算出:
首先算出從當前坐標到PDU包中描敘的坐標所需要的時間:
T = Dest( TargetB – OriginB ) / Speed
然后根據新PDU包中所描敘的坐標信息模擬計算出在時間T之后,按照新的PDU包中的運動信息所應該達到的位置:
_TargetB = NewPDU.Speed * T
然后根據當前模擬行動中的B和_TargetB的距離配合時間T算出一個修正過的速度_S:
_S = Dest( _TargetB – OriginB ) / T
然后在畫面上讓B以速度_S走直線到Target_B,並且在走到之后調整其速度,方向,加速度等信息為新的PDU包中所描敘的。

  這種方法呢,非常的土,會讓物體在畫面上移動起來變得非常的不現實,經常會出現很生硬的拐角,而且對於經常要修改的速度_S,在玩家A的畫面上,玩家B的行動會變得非常的詭異。其好處是:比第一種方法要好。

舉個例子:

  1. 飛機的運動速度固定, 兩台游戲機聯網玩, 分別是 A 和 B.
  2. 對於 A 和 B 來說, 是否發送 PDU (protocol data unit, 協議數據), 是通過閾值決定的.
  3. 實際運動位置 P1, DR 算法模擬得到的位置是 P2, 如果 P2 - P1 超過閾值就該發送 PDU 通知對方.
  4. 每次收到對方的 PDU, 就更新對方的位置.
  5. 沒有收到對方的 PDU 時, DR 提供了集中經典算法來模擬對方的運動:
  • 位置1 = 位置0, 即位置不變.
  • 位置1 = 位置0 + 速度 * (T1 – T0) 相當於根據PDU中的數據, 做勻速運動
  • 位置1 = 位置0 + 速度 (T1 – T0) + 1/2 加速度 * (T1 - T0)\^2, 多了加速度

DR 的閾值是一個需要測試衡量的關鍵參數, 閾值設置的越低, 游戲畫面會越平滑, 但是網絡帶寬也會消耗的越快.

DR 可以忍受一定程度上的丟包, 所以可以用 UDP 來提高時效性, 避免 TCP 重傳的消耗.

網絡游戲的位置同步比較復雜, 在應用 DR 算法的基礎上, 還需要在細節處進一步完善, 才能提升網絡波動下的用戶體驗.

  第三種方法,叫二次方程行走(Quadratic),該方法的原理呢,就是在直線行走的過程中,加入二次方程來計算一條曲線路徑,讓Dest( _TargetB – OriginB )的過程是一條曲線,而不是一條直線,恩,具體的實現方法,就是在Linear方法的計算中,設定一個二次方程,在Dest函數計算距離的時候根據設定的 二次方程來計算,這樣一來,可以使B在玩家A屏幕上的移動變得比較的有人性化一些。但是該方法的考慮也是不周全的,僅僅只考慮了TargetB到 _TargetB的方向,而沒有考慮新的PDU包中的方向描敘,那么從_TargetB開始模擬行走的時候,仍然是會出現比較生硬的拐角,那么下面提出的 最終解決方案,將徹底解決這個問題。

 

網絡游戲同步詳解之三

最后一種方法叫:立方體抖動(Cubic Splines),這個東東比較復雜,它需要四個坐標信息作為它的參數來進行運算,第一個參數Pos1是OriginB,第二個參數Pos2是 OriginB在模擬運行一秒以后的位置,第三個參數Pos3是到達_TargetB前一秒的位置,第四個參數pos4是_TargetB的位置。

Struct pos {
     Coordinate X;
     Coordinate Y;
}
Pos1 = OriginB
Pos2 = OriginB + V
Pos3 = _TargetB – V
Pos4 = _TargetB
運動軌跡中(x, y)的坐標。
x = At^3 + Bt^2 + Ct + D
y = Et^3 + Ft^2 + Gt + H
(其中時間t的取值范圍為0-1,在Pos1的時候為0,在Pos4的時候為1)
x(0-3)代表Pos1-Pos4中x的值,y(0-3)代表Pos1-Pos4中y的值
A = x3 – 3 * x2 +3 * x1 – x0
B = 3 * x2 – 6 * x1 + 3 * x0
C = 3 * x1 – 3 * x0
D = x0
E = y3 – 3 * y2 +3 * y1 – y0
F = 3 * y2 – 6 * y1 + 3 * y0
G = 3 * y1 – 3 * y0
H = y0

  上面是公式,那么下面我們來看看如何獲得Pos1-Pos4:首先,Pos1和 Pos2的取值會比較容易獲得,根據OriginB配合當前的速度和方向可以獲得,然而Pos3和Pos4呢,怎么獲得呢?如果在從Pos1到Pos4的 過程中有新的PDU到達,那么我們定義它為NewPackage。

   Pos3 = NewPackage.X + NewPackage.Y * t + 1/2 * NewPackage.a * t^2
Pos4 = Pos3 – (NewPackage.V + NewPackage.a * t)

     如果沒有NewPackage的情況下,則Pos3和Pos4按照開始所規定的方法獲得。

     至此,關於導航推測的算法大致介紹完畢。


免責聲明!

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



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