與“延遲”抗爭,射擊遊戲如何做到更好的體驗?
網遊都是採用典型的C-S通訊模式,客戶端必須持續地與伺服器通訊才能正常運作,網路通訊的round-trip time明顯就是我們不想要的延遲。網路傳輸的丟包、重傳等,都可以納入網路延遲的範疇。
但遊戲中的延遲遠不止網路延遲這麼簡單,計算機系統(包括手機)的運作、遊戲的實現機制都會引入延遲。點選滑鼠產生開火指令,這個電訊號的傳輸、被系統捕獲都需要時間,客戶端邏輯執行緒通常要到下一幀才會開始處理這次輸入,邏輯處理中拋事件到渲染執行緒,渲染執行緒最快也要下一幀才能去渲染結果,渲染結果要顯示到螢幕上又受限於螢幕的重新整理率。可見,即使是沒有網路的純客戶端遊戲,延遲都不可避免地存在。很多遊戲在實現中會用到Buffering技術,這還會進一步加劇延遲。
衡量遊戲品質的一項重要指標就是玩家常說的“手感”,而手感的關鍵組成部分就是反饋的及時性。對於射擊遊戲、競速遊戲,玩家對手感有著極高的期待,職業玩家甚至能感知到10ms級別的延遲差距,所以遊戲開發者都會在這方面下很大的功夫。
2、降低網路延遲
網路遊戲中,網路延遲是延遲的主體部分,降低網路延遲是優化體驗的關鍵,也是開發商都會重視的地方。例如,Valorant 做到了為70%玩家提供小於35ms的RTT。
2.1 搭建專用網路
網路包在傳輸中要經過複雜的路由,這導致了高延遲以及更多的不確定性。所以,有實力的廠商都會在網路基礎設施上做投入,例如Riot有去搭建自己的ISP。這一點可以簡單理解為:廠商會圍繞玩家就近部署很多接入伺服器,內部的伺服器之間由專用的高速通道相連,就像修了很多專用的高速公路,所以整體的通訊非常穩定、高效。
2.2 使用UDP
越來越多的遊戲都從TCP轉向了UDP,在剔除/簡化了擁塞控制以後,包傳輸的及時性有了明顯的提升,尤其是在弱網環境下。
由於UDP不可靠的特性,一般都會實現Reliable-UDP。實際遊戲中,一般會混用可靠與不可靠的UDP,純表現的訊息、狀態同步等可能會用不可靠的UDP,使用者輸入、邏輯事件等重要的資訊則會用可靠的UDP。
3、使Buffer儘可能小
3.1 Buffering機制
網路遊戲中,客戶端和伺服器幾乎每一幀都在通訊,玩家的操作永遠來自於客戶端,權威的資訊永遠來自於伺服器。但網路通訊是不穩定的,伺服器可能連續5幀都沒有收到客戶端A的數,但在下一幀又一口氣收到5個。這種輸入的缺失和爆發增長會導致卡頓,導致不平滑的遊戲體驗。無論是幀同步還是狀態同步,都會面對這個問題。
經典的解決辦法是使用buffering機制:增加一個buffer,快取幾幀資料以後再以穩定的頻率向業務系統提供輸入。這個機制良好地解決了網路傳輸帶來的輸入抖動問題,網路視訊播放器中一直在使用該技術。
3.2 縮小buffer
很多遊戲中都有用buffering機制,它確實讓遊戲變得平滑了,但卻以增加延遲為代價。追求極致體驗的遊戲都不得不在buffer的大小上做折中,有的只存1幀的資料,有的甚至完全消除buffer。
縮小buffer的同時還要保證平滑的遊戲體驗,這意味著遊戲要提供非常穩定的幀率、避免毛刺,還必須要在沒有輸入的情況下自己去預測玩家的行為。假設玩家在持續地移動,客戶端每幀都在上報移動指令,後來由於網路故障,伺服器在幾幀內都未收到新的移動指令,這時伺服器要盡最大的努力去預測,從而使網路恢復以後玩家的移動仍然是平滑的。
4、更高的幀率
幀率的影響到底大不大?幀率高到一定值以後是否值得繼續提高?這是一個有一定爭議的話題。
不管是否值得,但毫無疑問 更高的幀率必然是有好處的。幀率越高,系統運作的延遲就越低,響應越及時,表現也會更平滑、更自然。客戶端幀率超過100的遊戲並不少見,但伺服器幀率幾乎都小於64。伺服器的幀率有必要更高嗎?
Riot說有必要,Valorant 的伺服器是以128幀執行的,這讓人印象深刻。客戶端與伺服器都在模擬角色的移動,它們的運算結果會無可避免地出現分歧。假設伺服器是1秒1幀,兩端位置的最大分歧是1米,然後被強制糾正到一致。若兩端都是1秒128幀,兩端位置的最大分歧可能就只有10釐米了,然後也被強制糾正到一致。幀率高了就像是小步快跑,每次產生很小的誤差,然後不斷地糾正,所以整體的表現會很平滑。另一方面,即使某個客戶端只以60幀的頻率上報移動指令,其他9個客戶端仍然可以以128幀的頻率收到伺服器下發的位置更新,所以其他9個人看到的人物移動仍然是非常平滑的。
在 Valorant 中,客戶端與伺服器的移動和物理都是保持128幀執行的,這使得兩端的每一幀都可以一一對應上,這極大地方便了服務端的命中判定,伺服器只需把其他人的位置回退固定的幀數,就可以與客戶端上的位置匹配。
高幀率縮短了系統運作的延遲,提供了更加平滑、細膩且一致的角色移動,這對FPS遊戲是很有意義的。射擊遊戲給玩家的反應時間是極短的,1秒就決定了生死,職業玩家對10毫秒的延遲都很敏感。每個人都是非常平滑地移動,這是很利於玩家追蹤和預測對方移動軌跡的,所以玩家的射擊體驗會好很多。要是人物的移動忽快忽慢,甚至還有位置瞬移,玩家的射擊體驗就會大打折扣。
但是,一切皆有代價。要做到這樣的高幀率本身就很困難,需要花很大的力氣去優化效能。就算技術上做到了,伺服器執行的成本也是非常高的。
5、狀態同步+客戶端預測+糾正
幀同步要求每個客戶端的運算輸入嚴格一致,這樣才能保證每個世界始終處於一樣的狀態。這意味著它們的輸入一定來自於同一個伺服器,意味著玩家輸入到邏輯執行之間要包含網路延遲,這個延遲通常是幾十毫秒,這會立即讓玩家覺得“遊戲的手感不好”。
FPS遊戲大多都是使用狀態同步,因為這樣很容易做客戶端預測,從而實現更好的及時性。玩家按鍵操作之後,客戶端把該輸入發給伺服器,同時客戶端本地直接處理該輸入、產生部分結果/表現,而不用等待伺服器的返回,這就是客戶端預測。伺服器處理完輸入以後會通知客戶端結果,如果本地運算結果與伺服器下發的不一致,肯定要以伺服器的為準,故客戶端要做糾錯處理。
由於是狀態同步,即使客戶端本地計算出錯了,也總是能恢復到一致的、正確的狀態,UE引擎的移動同步就是這樣做的。
6、隱藏lag
對於網路遊戲,重要的資訊必須以伺服器為準、不能客戶端自主決定,否則會外掛氾濫,例如子彈命中的判定、造成的傷害量、技能的生效等等。但如果玩家的輸入總是要等伺服器返回後才給玩家反饋的話,玩家會覺得遊戲的體驗很差。公平性與及時性都很重要,但卻存在衝突,需要我們做一些細緻的特殊處理。
6.1 扔手雷
接下來,我們來看一個實際的例子(此處的例子節選自 Halo 的分享),假設玩家扔手雷的粗略過程如下:
按鍵是客戶端行為,前搖動畫可以看作是一段固定的延遲,飛出手雷代表施法的正確結束、產生了效果。
我們是網遊,生成手雷這麼重要的事情當然得伺服器說了算,所以我們會很自然地這樣做:
圖中黃色的文字標記出了延遲的位置,玩家按鍵以後 隔一段時間才出現前搖動畫,這體驗當然是不可接受的,我們要更早地給玩家反饋。
想要及時反饋,可以在按鍵後直接播前搖動畫,並且在時間結束後直接飛出手雷:
這違背了伺服器做決策的原則,明顯更無法接受。伺服器上前搖過程還未走完,但客戶端已經飛出手雷了,若伺服器上前搖過程被他人打斷,客戶端必須要把已經飛出的手雷給刪掉,這種詭異的表現會讓玩家非常困惑和不滿。
Halo 最終的實現方案是:
按鍵後,客戶端立即播放前搖動畫,但等伺服器流程結束後再通知客戶端飛出手雷。相當於在客戶端把前搖給拉長了。這樣做解決了上述問題,提供了良好的使用者體驗:
玩家按鍵後立即獲得了反饋,所以操作體驗很好。
手雷的生成遵從了伺服器的權威,杜絕了被打斷導致的奇怪表現。
雖然手雷的飛出有延遲,但動畫末尾的手臂佔據了大部分畫面,玩家幾乎感知不到這個延遲。即使玩家發現了這個延遲,影響也不大。
6.2 開啟無敵
另外一個例子,是 Halo 釋放無敵技能的過程。我們仍然期望按鍵後就立即播放前搖動畫,但卻不能再像上面一樣延遲出現無敵效果。因為無敵狀態對施法者太重要了,晚0.1秒就可能是生與死的差別,玩家對這段延遲非常在意、幾乎無法容忍。這時還可以把延遲藏在哪呢?
為了給施法者提供極致的體驗,這裡修改了遊戲機制——把伺服器端的施法延遲給縮短了,即把網路延遲藏在了這裡。
這種修改遊戲機制的做法是特例,應該謹慎使用。雖然施法方的體驗變好了,但對他的對手是不公平的。這裡之所以可以這麼做,是因為玩家普遍不能接受自己的無敵晚一點出現,但卻相對能容忍對方的無敵早一點出現。
7、在割裂的世界中愉快地玩耍
7.1 螢幕上看到的都是假象
我們再來看看射擊遊戲中的經典問題——命中判定。延遲的存在,使這個問題變得更加棘手。
子彈是否打中敵人、打中的部位是頭還是腳,這些都是非常關鍵的資訊。命中的判定可以在客戶端做,然後伺服器做校驗,也可以只在伺服器做。無論用哪種方式,我們都必須要面對一個事實:玩家螢幕上看到的敵人位置,很可能不是此刻敵人的真實位置,或者說不是其他世界中對方的位置。
同一局遊戲內,每個客戶端和伺服器都各自是一個獨立的世界。理想情況下 所有世界都應該是同步的、一致的,在任一給定時刻,玩家A在每個世界中的位置和姿態(Pose)都應該是相同的。但由於延遲的存在,這種理想情況永遠都不可能實現!
一個簡化後的、現實中的模擬如下圖:
一個框代表遊戲執行的一幀,圖中只畫出了部分幀,並假設客戶端與伺服器的邏輯幀能穩定地一一對應。紅線代表客戶端上報的自己當前幀的位置/移動,藍線代表伺服器每幀下發的其他角色的位置/移動。假設A和B都在持續地移動:
t1時刻,客戶端A把自己本幀移動的結果(PAe)上報給伺服器,客戶端B同樣把PBe上報給伺服器。
t2時刻,伺服器上A和B的位置分別為PAe和PBe。伺服器把當前幀的位置資訊同步給所有客戶端。
t3時刻,客戶端A收到玩家B的位置為PBe,但自己已經移動到了PAi。
這是很多遊戲的角色位置模擬過程,在任一時刻,客戶端看到的自己的位置始終是領先於伺服器的,但看到的其他人的位置又都是落後於伺服器的。這導致——戰鬥中,我們始終是在用將來位置的自己去打過去位置的敵人。例如客戶端A在t3時刻瞄準了B,並開槍射擊,伺服器收到射擊事件時已經是第i幀了,但t3時刻和t4時刻 玩家B在伺服器上的位置都不是PBe。
假設命中判定在客戶端做,t3時刻客戶端A認為自己打中了B,但伺服器需要校驗這一次射擊的合法性,伺服器收到射擊事件時 玩家B的位置已經變成PBi了,這時伺服器很可能認為A打不中B。把命中判定交給伺服器來做,仍然有同樣的問題。
不同世界間的不同步是由延遲導致的,延遲導致的結果是:只要敵人持續在移動,並且移動速度比較快,你瞄準後的射擊永遠也打不中敵人。
7.2 伺服器回退玩家的位置
解決命中問題的經典方案是——伺服器做玩家位置回退。伺服器記錄一段時間內每個人的歷史位置和Pose,收到A的射擊事件時,伺服器把A之外的所有玩家都挪回到一個恰當的歷史位置去,使伺服器上其他人的位置與A看到的其他人位置基本一致。這樣,客戶端上能打中的 伺服器上就也能打中了。如圖所示:
但這種方案也有問題:它對被打的那一方不公平。例如t3時刻,玩家B明明已經躲起來了,在客戶端B的世界裡 玩家A是打不到自己的(位置PAe與位置PBi),結果你t3時刻的開火還是打死了自己。
要解決這個問題,一般是給回退的時長加一個約束,例如最多隻允許回退一個RTT。
8、讓網路卡的人自己拉扯
UE4預設的移動同步過程是:客戶端和伺服器都模擬角色的移動,客戶端每幀上報自己移動後的結果和當前的移動輸入,伺服器收到移動輸入後也去模擬移動。若伺服器的移動結果與客戶端上報的結果誤差在一定容忍範圍內,則直接採納客戶端的結果,使伺服器上玩家的位置等於客戶的上報的位置。若誤差超出了容忍範圍,則以伺服器為準,強制重置客戶端的位置。當容忍範圍設定得較大的時候,伺服器上會大幅度重置角色的位置,當同步到其他客戶端以後,這個角色的移動必然會出現波動,要麼是直接位置重置,要麼是插值過去(加速移動過去)。無論用哪種處理方式,最終的結果都是:一個玩家B卡了,然後其他9個人看到的B的移動都是不平滑的。
Valorant 認為:一個人卡了,卻導致其他9個人的瞄準體驗變差了,這是不可接受的。Valorant 的方案是:若伺服器未收到移動輸入,仍然根據當前的狀態每幀做預測,當出現分歧時始終以伺服器的模擬為準,並且每幀都下發角色的位置。這樣做,無論個人怎麼卡,其他9個人看到的角色移動始終是平滑的,但卡的那個人自己會被頻繁地拉扯。
9、解決Peeker’s advantage
9.1 什麼是Peeker’s advantage?
這是戰術射擊遊戲的經典話題,玩家在轉角處來回晃悠可以獲得先手優勢(會先看到對方),是一種網路延遲導致的不公平現象。
如圖所示,衝出角落的人會先看到對方,而蹲角落的人明顯要晚一些看到對方。說明:這裡是用了比較大的ping值來演示該效果,實際中不一定有這麼明顯。
產生這個問題的本質原因就是網路延遲,正如上面已經講過的:若是自己在移動,自己客戶端上自己的位置是領先於其他所有世界的,而靜止不動的人的位置在所有世界中都是一致的。移動的人的位置要同步到其他客戶端,這是需要經過網路傳輸的:
9.2 它到底有多大的影響?
這個優勢其實是可以用數學公式來描述的:
如圖,holder要想打贏對方,就必須要在server applies kill shot之前把開火指令傳送到伺服器,這樣才能實現比peeker先開火。進而可以得到這樣的關係:
holder reaction time < peeker reaction time - (holder RTT + holder buffering latency + server buffering latency)
所以peeker方領先的時間主要取決於holder方的網路延遲,以及buffering延遲。
更進一步,我們可以帶入具體的數字來量化這個優勢。按幀率60、單邊通訊30ms延遲來算,peeker方大約可以獲得140ms的先手優勢。
9.3 技術層面能做什麼?
① 降低網路延遲。無非就是就近部署、搭建高速專用網路、使用UDP,上面已經講過了。
② 降低buffering延遲。最小化buffer大小,上面也已經講過了。
③ 高幀率也是有幫助的。在 Valorant 中,如果客戶端能跑140幀的話,這個差值可以降低到71ms。
9.4 從設計的角度弱化Peeker’s advantage
技術上沒辦法徹底杜絕這個問題,而且也很難取得明顯的改善,至少代價是很大的。但從設計上 我們有很多方法來優化它:
地圖設計:例如一個區域只留一兩個進入的入口(供他人peek around),但場地內有多個地方可以設伏、蹲守入口。這樣的設計使得holder的位置高度不確定,但peeker的位置高度確定,變相地增加了peeker方的難度。
降低移動中射擊的準確率。在這一點上每個遊戲的取捨都不同,Valorant在這方面做得比較狠,移動中幾乎很難打中。
讓peeker的身體先暴露,但相機晚一點暴露。這使得peeker先暴露出自己的部分身體,然後相機才能看到holder。人的移速慢一點、系統執行幀率高一點、身體做大一點,都會有利於peeker先暴露出部分身體。
來源:騰訊遊戲學院
原文:https://mp.weixin.qq.com/s/vhgnhEc-W0czI7-I6LvHFw
相關文章
- 幽默:Twitter延遲降低到400ms是怎麼做到的?
- 延遲繫結與retdlresolve
- ORACLE密碼錯誤驗證延遲Oracle密碼
- win10 xbox遊戲體驗:延遲狀況需改善Win10遊戲
- RabbitMQ延遲訊息的延遲極限是多少?MQ
- Network Kit與三七遊戲共創流暢遊戲體驗,無懼網路延遲遊戲
- 谷歌I/O大會談及Stadia 如何保證雲中心低延遲體驗?谷歌
- 美國伺服器延遲高怎麼辦,如何解決延遲問題伺服器
- Oracle資料庫密碼延遲驗證Oracle資料庫密碼
- 取消 11G延遲密碼驗證密碼
- 如何為夜遊專案提供更好的體驗效果
- 密碼延遲驗證導致的系統HANG住密碼
- 我是如何找到 Express 應用延遲原因的Express
- 如何利用網路延遲穿越時空
- 如何用RabbitMQ實現延遲佇列MQ佇列
- GCD延遲執行如何在中途取消GC
- script的延遲執行
- oracle的延遲約束Oracle
- 延遲釋出
- 社交遊戲玩法設計—競爭與對抗遊戲
- SQL資料庫開發中的SSIS 延遲驗證方法SQL資料庫
- 密碼延遲驗出現大量library cache lock密碼
- oracle 11g 密碼延遲驗證問題Oracle密碼
- RocketMQ系列(五)廣播與延遲訊息MQ
- JMeter定時器設定延遲與同步JMeter定時器
- 如何利用PostgreSQL的延遲複製實現災備SQL
- 伺服器延遲問題如何解決伺服器
- PostgreSQL中的複製延遲SQL
- Laravel 延遲佇列Laravel佇列
- WebGL之延遲著色Web
- Mybatis延遲查詢MyBatis
- redis 延遲佇列Redis佇列
- 疫情延遲 題解
- 如何才能讓Spring Boot與RabbitMQ結合實現延遲佇列Spring BootMQ佇列
- 如何巧用柵格檢視打造更好的APP產品體驗APP
- 第7 章、解釋與延遲有關的等待事件事件
- 實現簡單延遲佇列和分散式延遲佇列佇列分散式
- 基於rabbitmq延遲外掛實現分散式延遲任務MQ分散式