快速多人遊戲(2) - 客戶端預測和服務器校驗


原文鏈接:Fast-Paced Multiplayer (Part II): Client-Side Prediction and Server Reconciliation

介紹

在第一篇文章中,我們介紹了一種權威服務器的C-S模型,在這種模型中,客戶端只將輸入發送到服務器中,當收到來自服務器的遊戲狀態更新的時候再將結果渲染出來。


單純地依賴這種模型會導致玩家輸入命令和畫面更新之間的延遲感,比如,當玩家按下向右的按鈕,然後玩家等了半秒鐘纔開始移動,因爲首先客戶端需要把輸入傳給服務器,然後服務器處理了出入之後計算出新的遊戲狀態,然後再將新的遊戲狀態傳回給客戶端。



網絡延遲的效果


在實際的網絡環境中,延遲可能會達到零點幾秒,這時遊戲就有點感覺延遲了,最壞的情況是直接沒法玩了。在這篇文章中,我們將找到減小這種延遲感的方法,甚至能夠消除這種延遲。


客戶端預測

雖然存在着作弊的玩家,但是大部分時候服務器是處理有效訪問的(來自非作弊玩家的),這也意味着服務器收到大部分的輸入都是有效的,而且遊戲狀態的更新也是按照預期的,也即是說:如果你的角色在(10,10),然後方向鍵右被按下了,那麼你的角色就會移動到(11,10)。


我們可以利用這一點,如果遊戲世界的確定性足夠(給定一個遊戲狀態和一些列的輸入,得到的結果是完全可預測的)。


現在假設有100ms的延遲,角色移動一格需要花費100ms,使用之前說的實現方式,那麼整個動作完成需要花費200ms:



網絡延遲和動畫


因爲遊戲世界是確定的,我們假設傳送到服務器的輸入都能夠成功執行。在這種假設下,客戶端能夠預測遊戲世界在接受輸入後的狀態,並且絕大部分情況下,結果是正確的。

與之前說的將客戶端的輸入發送到客戶端然後等待服務器的響應再在客戶端做出反應,我們可以將兩者同時進行,即當在發送輸入信息的時候,就當作它們已經正確執行,通常服務器返回的結果和客戶端的執行的結果是一致的:



在服務器確認行爲的同時執行動畫


現在玩家輸入和運行結果之間就沒有任何延遲存在了,同時服務器還是權威服務器。(如果有作弊的客戶端發送無用的消息,他可以把角色放在他想要的任何地方,但這並不會影響到服務器,也就是其他玩家也不會受到他的影響)。


同步的問題

在上面的例子裏,我選用了一些比較特例的數字來讓所有事情看起都很完美,現在假設另一種情況,假設現在服務器延時250ms,移動一格花費100ms,現在玩家按了兩下方向鍵右,想要往右移動兩格。


使用之前我們說的技術,情況如下圖所示



客戶端預測和服務器返回的狀態不匹配


當 t = 250ms的時候,也就是收到服務器第一個包的時候,有趣的事發生了,客戶端預測的狀態是x=12,但是服務器說新的遊戲狀態是x=11,因爲服務器是權威的,客戶端必須把玩家拖回到x=11的位置,然後再t=350的時候,又來了一個新包,說角色應該在x=12,角色又移動了一次 。


從玩家的角度看,它按下了兩次按鍵,角色向右移動了兩次,然後過了50ms之後,又向左跳了一格,站了100ms之後,又向右跳了一格,這當然是沒法接受的。


服務器調和

解決上面說的問題的辦法就是你得意識到,客戶端所看到的世界是當前的,但是但是因爲延遲,客戶端得到的遊戲狀態都是過去的,當服務器在發送更新的時候,它並沒有把客戶端發來的所有的命令都處理掉。


這就很麻煩了,首先,客戶端在發送請求包的時候,給每個包打上幀序,在上面的例子裏,第一個按鍵請求的編號是#1,第二個按鍵請求時#2,然後當服務器回覆的時候,包的包含着請求的序列號。




客戶端預測+服務器調和


當t = 250的時候,服務器說“基於#1的輸入請求,你的位置是11”.因爲服務器是權威的,所以角色的位置是x=11, 現在假設客戶端保存了一份發送到服務器的請求,基於新的遊戲狀態,它知道服務器已經處理了請求 #1,所以他將這個備份丟棄掉,但是客戶端知道服務器還需要將#2請求的回覆發過來,所以再一次進行客戶端預測,客戶端可以基於上次服務器發來的狀態和#2輸入來計算當前的狀態。


所以當t=250ms的時候,客戶端得到“經過#1的輸入之後,x=11”,它將#1輸入的拷貝丟棄掉,但是還有一份並沒有得到服務器返回的#2的拷貝,客戶端將內部遊戲狀態更新爲服務器所發來的遊戲狀態,也就是x=11,然後將所有未收到回覆但已經發送給服務器的輸入用於預測,在上面的例子,輸入就是向右移動,最終狀態就是x=12,這是正確的。當t=350的時候,一個新的遊戲狀態從服務器獲得;這種情況它說“基於#2輸入,x=12”。在這是,客戶端將#2請求之前的請求都丟棄,然後將遊戲狀態更新爲x=12,沒有需要預測的輸入,所以不需要預測,現在得到的結果都是正確的。


一些其他的問題

上面討論的例子裏只討論了移動,但是這個方法在其他方面也可以使用。比如,在一個回合制的遊戲裏,當玩家攻擊其他玩家的時候,你可以播放飆血的特效和傷害值,但是你不應該在收到服務器回覆之前直接更新玩家的血量。


因爲遊戲狀態的複雜性,它並不能保證是絕對可逆的,你也許希望只有再收到服務器確認的時候纔將一個玩家殺死,即使這個玩家的血量降到負的了。(但假象一種情況,一個玩家實際上已經死了,但是在沒有收到服務器確認之前他使用了藥包,這個時候你該怎麼處理?)


這又帶來了一個有趣的問題,即使世界是絕對的確定的,並且沒有玩家在作弊,還是有客戶端的預測和服務器不吻合的情況。上面說的情況在單人遊戲中不會出現,但是在多人接入同一個服務器的時候就會發生,這個問題會在下面的文章中進行討論。


總結

當使用權威服務器的時候,你需要給玩家即時的響應,即使是當在等待服務器處理出入。爲了達到這樣的目的,客戶端模擬出輸入的結果,當收到服務器的回覆時,客戶端的遊戲狀態需要通過服務器的回覆和還未處理的輸入進行重新計算。










介紹

在第一篇文章中,我們介紹了一種權威服務器的C-S模型,在這種模型中,客戶端只將輸入發送到服務器中,當收到來自服務器的遊戲狀態更新的時候再將結果渲染出來。


單純地依賴這種模型會導致玩家輸入命令和畫面更新之間的延遲感,比如,當玩家按下向右的按鈕,然後玩家等了半秒鐘纔開始移動,因爲首先客戶端需要把輸入傳給服務器,然後服務器處理了出入之後計算出新的遊戲狀態,然後再將新的遊戲狀態傳回給客戶端。



網絡延遲的效果


在實際的網絡環境中,延遲可能會達到零點幾秒,這時遊戲就有點感覺延遲了,最壞的情況是直接沒法玩了。在這篇文章中,我們將找到減小這種延遲感的方法,甚至能夠消除這種延遲。


客戶端預測

雖然存在着作弊的玩家,但是大部分時候服務器是處理有效訪問的(來自非作弊玩家的),這也意味着服務器收到大部分的輸入都是有效的,而且遊戲狀態的更新也是按照預期的,也即是說:如果你的角色在(10,10),然後方向鍵右被按下了,那麼你的角色就會移動到(11,10)。


我們可以利用這一點,如果遊戲世界的確定性足夠(給定一個遊戲狀態和一些列的輸入,得到的結果是完全可預測的)。


現在假設有100ms的延遲,角色移動一格需要花費100ms,使用之前說的實現方式,那麼整個動作完成需要花費200ms:




網絡延遲和動畫


因爲遊戲世界是確定的,我們假設傳送到服務器的輸入都能夠成功執行。在這種假設下,客戶端能夠預測遊戲世界在接受輸入後的狀態,並且絕大部分情況下,結果是正確的。

與之前說的將客戶端的輸入發送到客戶端然後等待服務器的響應再在客戶端做出反應,我們可以將兩者同時進行,即當在發送輸入信息的時候,就當作它們已經正確執行,通常服務器返回的結果和客戶端的執行的結果是一致的:


在服務器確認行爲的同時執行動畫


現在玩家輸入和運行結果之間就沒有任何延遲存在了,同時服務器還是權威服務器。(如果有作弊的客戶端發送無用的消息,他可以把角色放在他想要的任何地方,但這並不會影響到服務器,也就是其他玩家也不會受到他的影響)。


同步的問題

在上面的例子裏,我選用了一些比較特例的數字來讓所有事情看起都很完美,但是




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