《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

遊資網發表於2019-10-18
9月21日,由騰訊遊戲學院舉辦的第三屆TGDC(騰訊遊戲開發者大會)在深圳舉行。在大會的技術論壇中,《無限法則》伺服器主程式唐駿以《無限法則》的專案經驗為例,分享了在特性、物理引擎應用、移動模擬等技術層面的開發經驗。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

以下為演講實錄:

射擊遊戲後臺開發:《無限法則》的物理引擎應用和移動模擬

大家好,我來自騰訊互娛北極光工作室群,我們開發的《無限法則》是一款射擊類的遊戲,開發過程中有一些創新,也踩了一些坑,獲取了一些經驗,想趁這個機會分享給大家。

《無限法則》是一款射擊類的端遊,2018年9月19日在海外的Steam平臺上線,英文名叫Ring of Elysuim,簡稱ROE,屬於戰術競技的遊戲,但有一些玩法上的創新。

第一,我們有很多的移動方式,有抓鉤、滑翔翼等;第二,我們採用區域崩塌的方式,通過改變物理場景來促使玩家到一個集中的區域進行更激烈的對抗;第三,我們並不是Last Man Standing的獲勝方式,而是由多個人合作或者組隊去完成一個最終的逃生,涉及到很多人和人之間的博弈,你先過去了未必是贏家,你在後面等著也未必是一個失敗者。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

▌《無限法則》後臺開發技術難點

對於《無限法則》後臺開發來說,有一些關鍵的要素,是我們的技術難點。

第一,大場景中海量的物理破壞,在玩家的射擊的時候,很多的固態物件都可以被破壞掉。

第二,伺服器觸發瞬時大量破壞,具體來說是:山上有一個雪崩,需要在很短的時間內把大量的建築全部摧毀掉。對我們最大的挑戰是效能問題,大家知道對物理場景進行描寫或者修改其實是相當消耗CPU的,而伺服器是對多個玩家進行服務,我們需要提供一個非常流暢的體驗環境。所以這個時候我們需要保證伺服器的邏輯延遲應該足夠小,不會跳幀,也就是不卡頓。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

另外,我們有複雜的多維立體機動的移動方式。

第一,我們有各種各樣的移動方式,有滑翔傘,有鉤鎖。第二,我們有相對平臺的多維運動,比如,一艘很大的船在海面上快速的移動,玩家在船上進行相互的射擊,這個難點在於同步的問題:我們需要確保是各個客戶端位置是一致的,而船本身是在快速運動並且在一個波濤洶湧的海面上下顛簸的,一點延遲抖動就會造成客戶端之間很大的偏差,所以我們解決手感、精度和誤差容忍的問題。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

▌《無限法則》物理引擎的應用

首先從物理引擎的方面來切入介紹一下我們是怎麼做的。在物理場景中,一個物體,不管有多複雜,比如下圖的房子,其實都是用各種幾何形狀拼接起來的。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

有了這些形狀以後,我們就可以真實的拼接出一個物理場景。物理場景具體來說是可以由以下幾個部件來搭成的:

-地形資訊,由籃色的比較稀疏的線條組成;

-韌體,我們認為不太可能會被破壞的東西,比如說一個塔,一個很高的房子,一個倉庫之類的;

-可破壞物,一個牆體、木樁、水泥墩還有一些木頭房子之類的;

-可移動物,簡單來說就是場景裡面的人、車這些東西可以隨便移動的。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

有了這些基礎物件之後,就可以搭建出一個物理場景。下圖是我在編輯器截出來的,大概相當於我們真實場景的四十分之一的大小。我們把這個物件放大看,右邊的小紅框是放大出來的場景,可以看到幾何形狀是非常之多的。

在遊戲場景裡,包含了上百萬個shapes,中間有遇到一個問題,我們查詢的時候發現查詢的結果總是跟預期不符,後來抓了原始碼,發現是因為單場景超過十萬個shapes,查詢會失效。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

怎麼解決這個問題呢?用分治的思路,把一個大的問題拆成N個等價的小問題來解決。所以我們在這裡把一個大的場景切成N個分片我們叫做Sector,每個Sector包含不超過上限的物理幾何形狀,通過拆分來解決場景過大的問題。

有一點需要注意,如果一個物件恰好跨在邊界兩邊,就需要做一個分片儲存,這個物件跨到哪裡是就需要儲存到哪裡。另外,如果有一個查詢的起點正好跨過了兩個分片就需要查詢兩遍,這是為了確保在射線射出的過程中,從起點和終點在真實的場景裡面不會碰到阻擋或者說找到這些阻擋。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

解決了海量物件的問題以後,接下來又遇到了載入和銷燬問題。因為我們是做了釐米級精度的東西,他的載入大概消耗是18秒鐘,銷燬一個場景大概需要兩三秒鐘。

我們發出指令,需要建立一個房間讓我繼續往下玩,但是建立的時候,需要CPU全力執行十幾秒鐘的時間才能建立好,玩家會卡頓了十幾秒鐘,這簡直是不能接受的。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

所以,對於這些問題,只是單純解決載入和銷燬房間有一些比較通用的做法。一個是用程式池或者執行緒池的方式解決這個問題,也就是說,我預先建立了N個多程式或者執行緒,每個程式或者執行緒持有一個場景池。

我們可以用類似的思路做了一個場景池,也就是載入執行緒不斷生產物理場景,物理場景放在一個場景池裡面,保證場景池永遠都有一定量的可用的物理場景,主執行緒在需要的時候從這個場景池裡面摘一個出來。

中間有一些問題是需要特殊考慮的,一個是資源排程模型的問題,因為載入是需要時間的,需要知道這個場景池或者這個程式池裡面需要有多少空閒的場景預分配出來給後面的玩家所使用。

還有一個問題是在冷啟動的時候。我們的系統剛啟動的時候會有一段時間不可用,所以對這些資源排程和下載模型來說,都需要進行一些邏輯上的考量,這方面業界都有一些標準的做法了。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

接下來是開發過程中遇到的另一個問題,記憶體消耗。我們做了一個測試,建了十個場景,反覆不斷地重建,發現記憶體消耗是比較穩定的,但單一場景消耗達到1.6G。

主要是因為我們的第一是模型多,第二是地圖大,第三精度達到釐米級。伺服器不像客戶端,客戶端原則上對於這些問題它是用Streaming機制,也就是按需載入。但是對伺服器來說,伺服器需要服務於所有的玩家。在這種情況下我們就需要把場景全部載入出來,最終統計我們單個物理機大概在120G記憶體,所以它裡面只能容納60個房間。60個房間按照道理來說應該還可以,但是後來有一個新的要求,我們要做一個訓練模式。

所謂訓練模式,就是隻有一個玩家,其它全是AI,我們單臺物理機120G只能為60個玩家服務。為了解決這個問題,我們處理可破壞物的時候,如果要把這個物件給刪除掉,一般的做法是把它給移除掉以後,做一個模擬update,把這個物件的改變應用到物理場景裡面去。

我們為什麼需要去做一個房間一個場景呢?因為玩家在不同房間中可破壞的東西是不一樣的,那我們去做這種移除,最核心、最根本的是期望這個物件在物理場景的計算中不起效。

那是不是可以通過標註,把標註為失效的物件,不改變它的物理場景,只改變它的狀態資訊,也就是說,在這個計算裡面它不會產生阻擋。如果按照這個思路,就可以把整個物理場景分成兩個層級來處理。靜態資料,這是個不會改變的物理場景;還有一種是動態資料,隨著房間戰鬥的程式的變化而變化,而且每個房間是不一樣的。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

有了這個動靜分裂之後,我們分成了兩個執行緒,一個載入執行緒不斷的去產生靜態物件,然後靜默載入一個靜態的場景池;然後主執行緒在開啟和銷燬房間的時候,動態的繫結這些資料。好處是,靜態的物理場景是多路複用的,也就是說,一個物理場景可以被N個房間所使用,開啟房間的速度和銷燬房間的速度是非常之快的。

也就是說我們只要在一瞬間改變標註的物理場景就可以了,不需要去真正的在物理世界裡把這個物件給刪除掉,這是一個很討巧的做法。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

再回顧一下我們的物理場景的做法:第一是分片,解決海量物件的問題;第二是用場景池來解決載入和銷燬耗時的問題;第三是動靜分類減少記憶體消耗,減少開關房間的時間消耗,也使我們真正能夠容納這種海量物件瞬間可破壞的要求。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

▌《無限法則》的移動模擬

大家知道,做射擊類的遊戲,最大的麻煩在於反外掛。一方面我們要保證流暢的體驗,另外一方面,我們也需要保障公平性。我們專案中的移動、動作、環境、場景都極為複雜,所以對我們做移動模擬,是一個很大的挑戰,需要解決精確度,容忍度誤差的問題。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

我們常用的做法是這樣的,客戶端做預表現,伺服器拿上它的動作資料、移動資料,在後臺做一個一模一樣的模擬。模擬出來以後,如果跟伺服器模擬結果差距不大就通過,如果差距很大的話就拒絕它,讓客戶端在我拒絕的點上重新做模擬。

這種情況下,伺服器的效能壓力很大,因為伺服器需要模擬它的動畫,引用所有動畫的複雜資料;另外一個是狀態恢復的問題,就是Rewind的時候怎麼來恢復原來狀態。比如,我繩子射出去了,打到牆上去了,然後客戶端就會立即的向目標點進行快速移動。但這個時候如果伺服器拒絕它的包,客戶端就會卡在半空中,會變得非常尷尬,導致很難去做一個長距離的拖拽。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

還有一點,非剛體的環境運動。比如,一個玩家在波濤洶湧的船上運動,浪高很高,大概可以達到最高十幾米。這種情況下,是一個典型的非剛體運動、柔性運動,我們怎麼樣去做這種模擬,我們初步的想法是把這個連續的路徑拆成N個離散的點,但是在點裡面我們會加入一些附加資訊。比如說它的狀態資訊,它的附著物資訊,在真實的場景中我們首先拿到了附著物資訊對它進行物理場景的校驗,看一個附著物是不是合適的,然後根據它的姿態資訊判斷一下連通性。

本質上來說,我們要求客戶端來同步模擬的中間資料,而不是伺服器去做一些連續運算。缺點是流量上有一定的增長,但是相比較來說,增長的量不是特別大,基本上是一種比較平衡的狀態。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

剛剛提到說我們在一個複雜的波濤洶湧的海面上運動,就是一個複雜的非剛體環境柔性運動。對於這種方式來說,我們要做的確保它的移動模擬的方式是客戶端和伺服器的演算法一致、環境一致,具體來說我們是隨機數是一致的,我們的時間是一致的,我們的海浪的高度是用了同樣的一種演算法就是快速傅立葉變化解決這個問題的。

還有一點是,相對運動玩家在波濤洶湧的船上移動的時候,我們其實做了個座標對映。我們用區域性座標,區域性物理來判斷船上本身的移動行為,最終對映到全域性座標裡去,用了這樣的方式去解決。其實它還是一個分治的思想,只不過說是把一個水平的問題拆成了一個縱向的問題而已。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

剛剛說了我們通過物理模擬的方式,或者是通過演算法一致的問題解決了玩家和玩家之間,以及玩家和伺服器之間的空間一致性的問題,就說是我們解決了大家在同一個座標點上的問題。

但是時間一致性怎麼來保證呢?舉個例子比如說C1客戶端,它發向伺服器,發訊息的時候出現了網路擁塞,在網路擁塞的時候C2也就是第三方客戶端看到這個擁塞以後它的表現就是連續地、短時間會收到一串的移動資料。

要怎麼做呢?在這裡出現一個擁塞資料,第三方客戶端還是按照原樣去模擬,儘量的去追趕,如果說實在追不上了,就直接執行一個拖拽。但是這種行為會帶來的問題是,第一方很容易出現拖拽,而且兩個不同的第三方如果需要精確同步位置的話,實際是上難做到的。

在這種情況下,我們的做法是在伺服器加了一個移動視窗,也就是說,這種擁塞控制是由伺服器進行延遲隔離、延遲計算的,如果說伺服器發現了它到達了一定範圍之後,伺服器去發rewind訊息告訴客戶端我拒絕你的包,然後你自己來進行一次回退。在這個期間伺服器是拒絕所有客戶端移動的,中間這一段就是伺服器做的一個延遲的模擬行為。

伺服器的平滑視窗除了解決剛說的延遲擁塞第三方不一致的問題以外,最重要的一點是我們不會連續地放過,而且根據第三方客戶端的姿態的形式不一樣,我們設計不一樣的引數,所以比如說對鉤鎖這種行為,我們寧願放過它,不要卡的狀態,然後對於一般的移動行為我們可能校驗會更加嚴格一點。所以這就是做一個伺服器的滑動視窗做一個平滑的操作。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

接下來有這麼幾點需要強調的是,我們有非常複雜的一個物理環境,我們有非常複雜的動作,大家可以看到圖上有滑翔傘,鉤鎖什麼非常複雜的動作,還有一個東西是我們因為作延遲補償,所以在這個時候我們對使用者的速度控制,其實要放大的。我們不能和精確的卡到速度,帶來一個很明顯,或者說很容易發現的問題,是它的第一方會經常出現拖拽。因為它很容易超過邊界速度。

在這種情況下我們就把它的單幀容忍放大了,比如說它本來我們預計兩幀之間它的移動速度是5米每秒,然後我們給它放大到10米每秒,這樣地保證第一放方的流暢性,但是帶來一個很尷尬的問題是說這個給外掛的加速帶來一個作弊空間了。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

對於這些問題我們怎麼辦?還是拿剛剛那個滑動視窗,滑動視窗我們一方面是來伺服器控制平滑,另外一方面我們可以用伺服器這種累加的速度來進行校驗比如說我們T1和TN之間我們計算的平均速度,根據它每個單點的姿態,所需要用的速度之類的資訊進行疊加,然後它算到他精確速度在這個方面就可以做到一個精算的校驗。而且玩家的獲利空間是不大的,具體來說是這樣的。

隨著時間的推移,假設有人作弊的話,他比正常速度所能獲取的收益是逐漸增大的,當他增大到一定範圍以後,伺服器就開始拒包了,拒包了以後它的速度會逐漸地下降,逐漸地下降到一個閾值以後伺服器允許它重新的上包。這樣一方面解決了第一方在複雜場景下的拖拽問題,同時也壓縮了外掛的作弊空間。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

物理模擬我們用了離散的方式附加多帶了一些附帶引數來解決位置移動的問題,還有一個是延遲補償也就是伺服器的滑動視窗來減少第一方的拖拽。另外用累積校驗來精確使玩家的速度控制在一定的合理範圍之內,達到了反外掛的一個效果。

《無限法則》開發經驗分享:射擊遊戲的物理引擎應用和移動模擬

最後,回顧我們所有的做法,本質上都是在做各種各樣的平衡,無非是拿空間換時間,拿時間換空間,拿流量來換計算機的效能。簡單來說,面多了加水,水多了加面,僅此而已,沒有什麼特殊的地方。

怎麼知道水多了還是面多了呢?你得先和麵,只有在專案當中遇到了你才能去做取捨,也就是說別怕手髒,先做了再說,工程本身就是一個在各種限定的條件下做各種平衡的操作,只能先做了再說。

來源:騰訊遊戲學院
原地址:https://mp.weixin.qq.com/s/MnNj8KzhJZa8NbgWx9Lo4g

相關文章