網遊中的物理

來源:互聯網
上載者:User

網遊中的物理

作者:Glenn Fiedler

譯者:trcj

原文:http://gafferongames.com/game-physics/networked-physics/

 

引言

    大家好,歡迎閱讀《遊戲物理》系列的最後一篇,我是格倫·菲德勒。

    上一篇,我們討論了如何使用類彈簧力來類比基本的碰撞、關節和馬達。

    現在我們將論述如何在網路環境中進行物理類比。

    網路中的物理類比是多人線上遊戲的至寶,而在第一人稱射擊類遊戲裡的廣泛應用,是其出色表現的佐證。

    本文將展示如何把源自第一人稱射擊遊戲的網路處理關鍵性技巧運用到你的網遊中。

 

第一人稱射擊遊戲

    第一人稱射擊遊戲的物理非常簡單。世界是靜態,角色的操作也基本上局限於跑、跳與射擊之間。為了防止作弊,第一人稱射擊遊戲通常採用用戶端-伺服器模型,即在伺服器上進行物理類比運算,由用戶端將運算結果的近似值展示給玩家。

    問題是如何讓每個用戶端在控制自己玩家的同時,儘可能合理地展示其他玩家的行為。

    為了簡潔優雅地實現這點,我們對物理類比進行如下構架:

    1. 角色物理完全由玩家輸入來驅動;

    2. 物理狀態可以完全封裝在一個結構中;

    3. 給定初始狀態及相同輸入,物理類比可以合理重現。

    如此,我們需要將驅動物理的玩家輸入封裝到一個結構中,將用於展示玩家行為的狀態資訊封裝到另一個裡。下面是一個僅含跑步與跳躍的射擊遊戲的簡單例子:

struct Input{bool left;bool right;bool forward;bool back;bool jump;};struct State{Vector position;Vector velocity;};

    接下來我們需要確保在相同初始狀態及輸入的情況下,物理類比可以給出儘可能相同的結果。並非要精確到浮點精度,1到2秒內能保持結果基本一致就可以了。

 

網路編程基礎

    在解決發送何種資料這個重要問題之前,我想簡要討論一下網路編程的幾個小麻煩。其實網路就是一根管道,一點兒也不複雜對吧?錯!忽略了網路原理你會痛不欲生。這裡有兩個你必須要知道的基本知識:

    其一,如果你們的網路程式員技術高明,他會使用UDP這個不可靠的資料協議,並在此之上建立某種特定的應用網路層。重點是作為一個物理程式員,你所設計的物理系統從該網路層擷取輸入及狀態資訊的同時,更應具備處理丟包的能力。否則在網路不好時,你的系統將會阻塞僵死。

    第二,由於頻寬所限,你將不得不壓縮資料。作為一個物理程式員,你需要謹慎處理此項。準確起見,一些資料絕對不能被縮減,而其他的則不然。任何經有損壓縮過的資料都應該能在彼端儘可能地被量化出來以保證雙方一致。在不破壞類比的情況下儘可能高效,是此時應遵循的底線。

    更多細節請參見我的最新系列文章《遊戲程式員的網路編程》。

 

用戶端輸入驅動伺服器端物理類比

    我們的伺服器與用戶端通訊時所使用的基本單元,是一個不可靠的資料區塊,如果你喜歡,可以稱之為一個不可靠的非阻塞遠端程序呼叫(rpc)。非阻塞即用戶端發送rpc到伺服器後,不會等待伺服器執行,而是直接開始執行其餘代碼。不可靠的意思是儘管用戶端有序發送rpc,但是一些調用並不會到達伺服器,另一些到達時可能會亂序。這些在設計時都需要考慮進去以適應網路傳輸層(UDP)的規則。

    可見用戶端與伺服器間的通訊是通過連續的rpc調用來完成的,我將其稱之為“輸入資料流”。這個輸入資料流能夠處理丟包和亂序的關鍵性技巧,是在每個rpc包裡加入一個時間戳記。伺服器依據本地時間,忽略掉那些早於該時刻的包,這樣可以有效地排除亂序的包。至於那些丟失的包,直接忽略即可。

    回到第一人稱射擊遊戲的例子,從用戶端發往伺服器端的資料結構我們早先已有定義:

struct Input{bool left;bool right;bool forward;bool back;bool jump;};class Character{public:void receiveInput(float time, Input input);          // rpc method called on server};

    這是在網路中描述一組簡單的包含跳躍的地面運動所需要的最基本的資料。如果還想支援玩家射擊,你需要在此結構中加上滑鼠操作,因為開火也需要在伺服器端判斷。

    注意到我把rpc作為一個類的成員函數了嗎?我假設你的網路程式員在UDP層之上設計了某種管道結構,某種能夠將rpc和遠程用戶端一一對應的結構。

    接下來,伺服器端該怎麼處理這些rpc調用呢?基本上它會在一個迴圈裡輪詢各個用戶端的輸入。接收到來自用戶端的rpc時,伺服器方計算其對應角色的物理狀態。這意味著用戶端的角色狀態會和伺服器有些微出入,有的超前,有的滯後。總的來說,不同的角色在保持大致同步的情況下進行著更新。

    讓我們來看看在伺服器代碼中這些rpc調用是如何?的:

void receiveInput(float time, Input input){if ( time < currentTime )return;const float deltaTime = currentTime - time;updatePhysics( currentTime, deltaTime, input );}

    這段代碼的要點在於,伺服器對角色物理狀態的更新,是在接收到對應用戶端的輸入時才進行的。這保證了其對rpc發送過程中產生的延遲或抖動具有容錯能力。

 

用戶端演繹伺服器的運算結果

    現在到了伺服器向用戶端回傳訊息的時候。由於要廣播給所有的用戶端,這裡伺服器將產生大量的通訊。

    在由用戶端rpc驅動的每一次物理更新運算完畢之後,伺服器需要把最新的物理狀態廣播給所有的伺服器。

    這些資訊仍然是以不可靠的rpc形式發送給用戶端的:

void clientUpdate(float time, Input input, State state){Vector positionDifference = state.position - currentState.position;float distanceApart = positionDifference.length();if ( distanceApart > 2.0 )currentState.position = state.position;else if ( distanceApart > 0.1 )currentState.position += positionDifference * 0.1f;currentState.velocity = velocity;currentInput = input;}

    上述代碼的意思是:如果雙方的位置相差過大(>2m),直接將角色強置於伺服器位置;如果位置差在10cm以上,角色由當前位置向伺服器位置移動10%;否則就不予處理。

    由於伺服器的更新rpc需要向用戶端廣播,先僅朝目標移動一小段會產生一個平滑校正的效果,這種技巧被稱為指數平滑移動平均線。

    這種平滑處理的副作用是會產生一定程度的位置滯後,無奈世間萬物皆無十全十美。這裡建議僅對直觀資料進行平滑處理,如位置、旋轉,而諸如速度、角速度之類的衍生資料則大可不必,因為數值驟變在這些衍生資料上的體現並不顯眼。

    當然,這些只是經驗之談,你應該摸索出最適合自己的做法。

 

用戶端預判

    到目前為止,我們的方案是使用用戶端輸入驅動伺服器端進行物理運算,廣播運算結果,用戶端再據此維護一份伺服器的近似值。這套做法很完美,但它有一個主要缺點。延遲!

    當玩家按下前方向鍵時,這個輸入需要去伺服器上兜一圈,再次回到用戶端後玩家的角色才能開始移動。熟悉Quake的同學對這一效果應該不會陌生。這個問題在隨後的QuakeWorld裡被修正,引入了一種叫做用戶端預判的方法。這種技術完全消除了移動延遲並成為之後第一人稱射擊遊戲的標準網路處理技巧。

    用戶端預判法在玩家輸入後直接演算物理結果,而非等待其去伺服器兜完那一圈。伺服器定期發送正確資料到用戶端以供其校對。任何時候,角色的物理狀態都以伺服器為準,這樣一來即便用戶端作弊,也只是自欺欺人,伺服器的物理體系並不會受到影響。因為所有的遊戲邏輯都在伺服器端運行,用戶端作弊基本上可以被消除。

    用戶端預測的複雜之處在於如何處理來自伺服器的校對資訊。由於用戶端/伺服器的通訊延遲,來自伺服器的校對資訊總是“過時”的。我們需要回到“過去”校對這些資料,然後依此演算當前的確切位置。

    標準做法是在用戶端維護一個環形緩衝區用於儲存使用者輸入,每個輸入都對應一個從用戶端到伺服器的rpc調用:

struct Move{float time;Input input;State state;};

    每當用戶端收到一個校對資料,它會將資料中的物理狀態與緩衝區中同一時刻的那個物理狀態進行比對。如果二者之差超過了某種閾值,用戶端會倒回該時刻,在正確資料的基礎上對緩衝區中儲存的後續輸入依次進行重新演算:

const int maximum = 1024;Move moves[maximum];void advance(int &index){index ++;if (index>=maximum)index -= maximum;}int head = 0;int tail = 100;          // lets assume 100 moves are currently storedvoid clientCorrection(float time, State state, Input input){while (time>moves[index].time && head!=tail)advance(head);          // discard old movesif (head!=tail && time==moves[head].time){if ((moves[head].state.position-currentState.position).length>threshold){// rewind and apply correctioncurrentTime = time;currentState = state;currentInput = input;advance(head);          // discard corrected moveint index = head;while (index!=tail){const float deltaTime = moves[index].time - currentTime;updatePhysics(currentTime, deltaTime, currentInput);currentTime = moves[index].time;currentInput = moves[index].input;moves[index].state = currentState;advance(index);}}}}

    有時,丟包和亂序會導致伺服器的輸入與用戶端所存不一致。這種情況下進行回倒和重算會將角色強置到正確位置。這種強置瞬移過於明顯,所以我們可以通過與之前相同的平滑校正法進行處理。該處理應在回倒和重算結束後進行。

 

用戶端預判的缺點

    用戶端預判法似乎完美得讓人難以置信。一個簡單技巧就能讓我們完全消除延遲。但有沒有什麼代價呢?答案就是每當伺服器上有兩個物理體相互作用時,就會導致強置瞬移。為什麼會這樣?事實上,用戶端使用自己掌握的數值進行物理演繹,卻得到了和伺服器截然不同的結果。瞬移。

    在我們僅有跑步和跳躍的簡單fps遊戲裡,一名玩家穿過另一名玩家、企圖站在另一名玩家頭頂、或者被爆炸掀翻,這種情況都會發生。說到底,任何非玩家輸入引起的物理狀態改變都會導致瞬移。實際上沒有任何辦法能避免捨棄用戶端預判結果,而直接採用伺服器結果。

    這讓我想起一個有趣的問題。從靜態世界中玩家移動互射的第一人稱射擊遊戲,到動態世界裡玩家與他人及周邊環境互動,網遊正在悄然進化。鑒於這種趨勢,我願大膽預測:本文介紹的用戶端預判法,可能很快就會過時。

 

連網物理概覽

    到目前為止,本文展示的解決方案在第一人稱射擊遊戲中都運行良好。其最主要的限制在於每個用戶端對每個角色都有一個明確的所有權,意即絕大多數情況下,該用戶端是影響該角色物理的唯一因素。

    這種簡化性的假設,是第一人稱射擊遊戲慣用技術的基礎。如果你的物理系統與此相似,那麼這些技巧正合你胃口。例如,每個玩家控制一輛車的賽車遊戲中,你只需稍微加入點額外的物理狀態和使用者輸入,擴充一下該系統即可。

    但如果你想製作的物理遊戲沒有明確的物體歸屬。比如,設想有一堆方塊,玩家可以點擊拖動任意方塊。此時沒有哪個物體是特屬某個玩家的,甚至有可能多個玩家同時拖拽同一方塊。也許一個玩家站在方塊上面,而其他玩家正決定開車撞飛這堆方塊!夠複雜吧!

    這種情況需要引入更多的技術。用戶端預判法很明顯是不能勝任的,因為它會導致嚴重的瞬移。問題的關鍵在於伺服器不能再等收到用戶端輸入後才進行物理更新,因為物體不再像第一人稱射擊遊戲裡那樣,明確歸屬於某個用戶端。

    這意味著伺服器端的物理更新看起來更接近於傳統做法,所有物體都依據各自最終收到的用戶端輸入進行同步更新。這讓伺服器對諸如包延遲、包堆積等網路問題更加敏感,同時需要更多的工作以確保用戶端和伺服器同步。

    解決這些問題將是一個挑戰,我也希望以後能給出實現多人玩轉方塊堆的解決方案及原始碼。

 

總結

    在網遊中進行物理類比比較複雜,掌握第一人稱射擊遊戲中使用的核心技術會讓它理解起來更容易一些。

    我製作了一個示範以與本文配套,示範中我用了一個立方體來代替FPS角色,你可以操縱立方體進行跑步及跳躍,抱歉不能射擊!

    示範程式裡有很多可視化內容幫你理解回倒、重算、平滑處理等概念,現在就下載下來把玩一番吧!

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.