(本文的讀者大部分為本公司程序員,文中有些地方保留了他們?cè)谌粘9ぷ髦袝r(shí)常用到的英文術(shù)語。望其他讀者見諒。)
基于Source Engine的多人游戲使用的是Client-Server網(wǎng)絡(luò)架構(gòu)。通常,server是指專門用來跑游戲的主服,把控world stimulation,游戲規(guī)則,玩家輸入的處理。client是指連接到game server的玩家電腦。client和server通過高頻率傳送小的data packets(下文中簡(jiǎn)稱為“包”)的方式進(jìn)行溝通(通常為20-30個(gè)每秒)。client從server上接收到當(dāng)前world state,然后根據(jù)更新內(nèi)容生成聲音和視頻輸出。client也會(huì)從輸入設(shè)備(鍵盤,鼠標(biāo),話筒等)上進(jìn)行數(shù)據(jù)采樣,然后把樣本送回server做進(jìn)一步處理。clients只和game server溝通,彼此之間不溝通(和在peer-to-peer應(yīng)用中一樣)。與單人游戲相比,多人游戲需要處理很多基于傳遞數(shù)據(jù)包溝通方式而引發(fā)的問題。
由于受到網(wǎng)絡(luò)帶寬的限制,server不可能做到只要有world change,就發(fā)一個(gè)內(nèi)容更新包到所有clients。所以,server會(huì)遵照一定頻率給當(dāng)前游戲狀態(tài)拍快照然后廣播給clients。網(wǎng)絡(luò)數(shù)據(jù)包在client與server之間傳送是需要時(shí)間的(ping值)。這樣使得client時(shí)間總會(huì)比server時(shí)間晚一點(diǎn)。進(jìn)而言之,來自client輸入數(shù)據(jù)包傳送回server也會(huì)delay,所以server是在處理暫時(shí)delay的用戶指令。外加上其它背景流量和client自身frame rate影響,每個(gè)client會(huì)有不同程度的network delay。server和client之間的這些時(shí)差會(huì)導(dǎo)致邏輯問題,并且伴隨著network latnecies的增加而更嚴(yán)重。在快節(jié)奏的動(dòng)作游戲里,即使是微妙的延遲也讓玩家感覺操作滯后,讓擊中對(duì)手或者與移動(dòng)中的物體互動(dòng)變難。除了帶寬限制和網(wǎng)絡(luò)延遲,傳送過程中丟包也會(huì)導(dǎo)致信息丟失。

為了處理網(wǎng)絡(luò)溝通帶來的問題,Source engine server引入了data compression和lag compensation技術(shù),對(duì)于client來說是隱形的。client是通過prediction和interpolation來進(jìn)一步改善游戲體驗(yàn)的。
Basic networking
server在離散的時(shí)間段里模擬游戲,這些時(shí)間段被叫做ticks。默認(rèn)的時(shí)間段是15ms,每一秒中有66.666...次ticks,不過mods可以指定自己的tickrate。在每個(gè)tick中,server要處理來自用戶的指令,運(yùn)行physical stimulation步驟,檢查游戲規(guī)則,更新所有物體狀態(tài)。完成一個(gè)tick模擬后,server決定哪個(gè)client需要world update,必要時(shí)會(huì)給當(dāng)前的world state拍快照。高tickrate會(huì)增加stimulation精確度,但是需要CPU性能好,并且server和client有足夠帶寬。server管理員可能會(huì)通過the-tickrate command line parameter更改默認(rèn)tickrate,但我們不推薦這種方式,因?yàn)橐坏﹖ickrate被修改了,mod可能不會(huì)按照既定方式工作了。
Note:CSS, DoD S, TF2, L4D and L4D2不支持the-tickrate 指令行參數(shù),因?yàn)樾薷膖ickrate會(huì)導(dǎo)致server timing出問題。CSS, DoD S and TF2, and 30 in L4D and L4D2中tickrate被設(shè)定為66。
clients帶寬通常都是有限的。最糟糕的情況是,玩家的modem的接收能力也就是5-7KB/sec。如果server是以較高的data rate給他們發(fā)送更新,就無法避免丟包。因此,client需要通過設(shè)定console variable rate (in bytes/second),把它的輸入帶寬能力告訴server。這是clients最重要的網(wǎng)絡(luò)變量,為了最佳游戲體驗(yàn)一定要把它設(shè)定正確。client可以通過改變cl_updaterate (默認(rèn) 20)來要求一定數(shù)量的快照,不過server將不會(huì)發(fā)出比stimulated ticks更多的更新,或者超出client需求的client rate。server管理員用sv_minrate和sv_maxrate(單位都是bytes/second)對(duì)clients要求的data rate values進(jìn)行限制。snapshot rate也可以通過sv_minupdaterate和sv_maxupdaterate(單位是snapshots/second)進(jìn)行限制。
clients按照server運(yùn)行相同的tick rate,通過對(duì)輸入設(shè)備進(jìn)行取樣來創(chuàng)造用戶指令。一個(gè)用戶指令基本就是當(dāng)前鍵盤和鼠標(biāo)狀態(tài)的快照。但是client并沒有為每一個(gè)用戶指令向server發(fā)送一個(gè)新包,它是按照一定速率(通常是每秒鐘30個(gè)包)發(fā)送命令包。這意味著兩個(gè)或以上的用戶指令會(huì)在同一個(gè)包中傳送。clients可以用cl_cmdrate來提升command rate。雖然這樣做可以增加更多的回應(yīng),但也需要更多的輸出帶寬。
用delta compression來壓縮游戲數(shù)據(jù)有助于減少網(wǎng)絡(luò)負(fù)荷。也就是說,server每次不是發(fā)整個(gè)world snapshot,只會(huì)把一次已知更新之后所發(fā)生的變化(a delta snapshot)發(fā)送出去。在client和server之間傳輸?shù)陌?,都有編?hào),為的是可以追蹤到它們的data flow。通常,server只會(huì)在游戲剛啟動(dòng),或者幾秒鐘內(nèi)client發(fā)生了嚴(yán)重?cái)?shù)據(jù)包丟失,這些情況下發(fā)送全部(non-delta)快照。clients也可以用cl_fullupdate指令手動(dòng)要求。
響應(yīng),也就是用戶輸入后到他在游戲中看到結(jié)果之間的時(shí)間,會(huì)受很多因素影響,包括server/client CPU load和stimulation tickrate,data rate以及快照更新設(shè)定,最主要的是受到network packet傳輸時(shí)間的影響。從client發(fā)出一個(gè)用戶指令開始,到server對(duì)其做出響應(yīng),然后client接收到該響應(yīng),整個(gè)過程所耗時(shí)間,被叫做latency或者ping (or round trip time)。在進(jìn)行多人在線游戲時(shí),延遲低的用戶會(huì)有突出優(yōu)勢(shì)。prediction和lag compensation都是為了盡可能削弱這個(gè)優(yōu)勢(shì),使得網(wǎng)絡(luò)連接質(zhì)量不高的用戶也能夠得到比較好的游戲體驗(yàn)。在帶寬和CPU性能允許的情況下,調(diào)節(jié)networking setting有助于獲得更好的游戲體驗(yàn)。我們推薦保持默認(rèn)設(shè)置,因?yàn)椴磺‘?dāng)?shù)男薷目赡軐?dǎo)致糟糕的副作用。
支持Tickrate的服務(wù)器
可以用the-tickrate parameter調(diào)整tickrate
- Counter Strike: Global Offensive
- Half-Life 2: Deathmatch
以下servers tick rate不能調(diào)整,否則會(huì)導(dǎo)致server timing出問題。
Tickrate 66
- Counter Strike: Source
- Day of Defeat: Source
- Team Fortress 2
Tickrate 30
- Left for Dead
- Left for Dead 2
Entity interpolation
默認(rèn)情況下,client每秒鐘會(huì)收到20個(gè)快照。如果游戲世界中的物體(entities)只在server接收到的那些點(diǎn)上進(jìn)行render,那么移動(dòng)的物體以及動(dòng)畫效果會(huì)看起來不連貫。丟掉的數(shù)據(jù)包同樣會(huì)引發(fā)明顯的問題。解決這個(gè)問題的辦法是及時(shí)返回去render,那么這些點(diǎn)和動(dòng)畫就能在最近兩次接收到快照點(diǎn)之間,連續(xù)地被插值(be continuously interpolated)。每秒20次快照的情況下,每50毫秒就會(huì)到達(dá)一個(gè)新的更新。如果client render time能在50毫秒內(nèi)shift回來,entities就能總是在上一次接收到的快照以及它之前的那次之間被插值。
默認(rèn)情況下,client每秒鐘會(huì)收到20個(gè)快照。如果world中的物體(entities)只在server接收到的那些位置上進(jìn)行render,那么移動(dòng)的物體和動(dòng)畫就會(huì)看起來不連貫。丟掉的數(shù)據(jù)包同樣會(huì)引發(fā)明顯的問題。解決這個(gè)問題的辦法是及時(shí)返回去render,那么這些位置和動(dòng)畫就能在最近兩次接收到快照點(diǎn)之間不斷地interpolated。每秒20次快照的情況下,每50毫秒就會(huì)到達(dá)一個(gè)新的更新。如果client render time能在50毫秒內(nèi)移回來,entities就能總是在最近一次接收到的快照以及它之前的那次之間interpolated。
Source默認(rèn)的interpolation period (‘lerp’)是100毫秒(cl_interp 0.1); 這樣,及時(shí)有一個(gè)快照丟了也能始終保證兩個(gè)有效快照可供插值。下圖展示了incoming world快照的到達(dá)次數(shù):

client接收到的最后一次快照是在tick 344也就是第10.30秒。在這次快照和client frame rate基礎(chǔ)上client time繼續(xù)增加。如果render了一個(gè)新的video frame,那么rendering time是當(dāng)前client time 10.32秒減去view interpolation延遲掉的0.1秒。也就是圖中的10.22秒,所有entities以及它們的動(dòng)畫會(huì)按照修正后的時(shí)間段,在快照340和342之間被插值。
因?yàn)閕nterpolation有100毫秒的延遲,即便快照342因?yàn)閬G包的原因找不到了,interpolation也不會(huì)受影響。這時(shí),interpolation可以用快照340和344。如果一列快照中丟失的不止一個(gè),那么interpolation就不能順暢執(zhí)行了,因?yàn)闅v史buffer中的快照用光了。這種情況下,renderer會(huì)用extrapolation(cl_extrapolate 1),嘗試一個(gè)簡(jiǎn)單的線性extrapolation of entities,基于到目前為止它們已知的歷史記錄。當(dāng)丟包時(shí)間為0.25秒時(shí)就會(huì)執(zhí)行extrapolation (cl_extrapolate_amount),否則prediction錯(cuò)誤可能在此之后變得很大。
Entity interpolation通過默認(rèn)(cl_interp 0.1)引發(fā)一個(gè)100毫秒constant view ”lag”,即使你是在一個(gè)listenserver(server與client在同一臺(tái)機(jī)器)上玩。當(dāng)你在向別的玩家射擊時(shí),server-side lag compensation會(huì)知道client entity interpolation并且修正該錯(cuò)誤,但這不意味著你要早于瞄準(zhǔn)。
Tip:近期很多Source games都有cl_interp_ratio cvar。有了它,你可以通過設(shè)定cl_interp為0,增加cl_updaterate 數(shù)值(由server tickrate決定的非常有用的數(shù)值限制),既簡(jiǎn)單又安全的減少interpolation period。你可以用 net_graph 1檢查最終的perp。
Note: 如果你打開sv_showhitboxes(Source 2009不支持),你將會(huì)看到玩家hitboxes在server time中淹沒,也就是說它們通過lerp period早于了渲染玩家模型。這是非常普通的!
Input prediction
假設(shè)一個(gè)玩家的網(wǎng)絡(luò)延遲為150毫秒,現(xiàn)在他要前進(jìn)。按下+FORWARD鍵的信息被存為一個(gè)用戶指令并發(fā)送給server。用戶指令通過前進(jìn)代碼處理后,玩家角色在游戲中前進(jìn)。這個(gè)world state變化會(huì)在下一個(gè)快照更新時(shí)被傳遞給所有clients。所以,玩家會(huì)晚于操作150毫秒看到自己位置上的變化。這種delay會(huì)發(fā)生在玩家所有的動(dòng)作上,比如移動(dòng)和射擊,延遲越高玩家看到的滯后越嚴(yán)重。
玩家輸入與對(duì)應(yīng)視覺效果之間的delay,讓玩家產(chǎn)生不自然的感覺,移動(dòng)和瞄準(zhǔn)也都不是很精確。client-side的input prediction (cl_predict 1) 是一種去掉這種delay的方法,讓玩家的動(dòng)作感覺上更連貫。本地client自行預(yù)判用戶指令結(jié)果,而不用等待server更新自己的位置。client運(yùn)行的代碼和規(guī)則,與server用來處理用戶指令的那一套是一樣的。prediction完成后,本地玩家瞬間移動(dòng)到了新的位置,但在服務(wù)器看來他還在原位。
150毫秒之后,client將會(huì)收到來自server的快照,其中包括了剛才已經(jīng)預(yù)判到的變化。client接著會(huì)將server發(fā)來的位置與自身預(yù)判的位置進(jìn)行比較。如果不同,就是發(fā)生了一個(gè)預(yù)判錯(cuò)誤。也就是說,當(dāng)client進(jìn)行用戶指令處理時(shí),沒能完全掌握其它entities和environment的正確信息。那么client需要糾正自己的位置,因?yàn)閟erver擁有更高權(quán)限。如果cl_showerror 1是打開的,clients可以看到預(yù)判錯(cuò)誤的發(fā)生。預(yù)判錯(cuò)誤的糾正會(huì)十分明顯,可能導(dǎo)致client’s view失常地跳動(dòng)。在短時(shí)間內(nèi)逐漸修正這個(gè)錯(cuò)誤(cl_smoothtime),效果會(huì)更平緩。cl_smooth 0就會(huì)關(guān)掉prediction error smoothing。
prediction只可能發(fā)生在本地用戶身上,以及與其有關(guān)的實(shí)體上,因?yàn)閜rediction的工作原理是通過用戶的鍵盤輸入來做出一個(gè)用戶要去哪兒的“最好的假設(shè)”。預(yù)判其它玩家,可能會(huì)在沒有數(shù)據(jù)的情況下全方面預(yù)判未來,因?yàn)槟悴豢赡荞R上拿到其他人的鍵盤輸入。
Lag compensation
lag compensation和view interpolation的所有source code在Source SDK中都是可行的。安裝教程詳見Lag compensation.
假設(shè)一個(gè)玩家在client time 10.5時(shí),朝目標(biāo)開槍。射擊信息打包成用戶指令發(fā)給server。數(shù)據(jù)包在網(wǎng)絡(luò)上傳輸時(shí)server繼續(xù)simulate the world,那個(gè)目標(biāo)可能已經(jīng)移到了新的位置。用戶指令在server time 10.6時(shí)到達(dá),server將不會(huì)覺察到該打擊,即便玩家之前是非常準(zhǔn)確的擊中目標(biāo)。這個(gè)錯(cuò)誤會(huì)被server-side的lag compensation修正。
lag compensation系統(tǒng)會(huì)保留玩家一秒鐘內(nèi)的所有歷史位置。如果執(zhí)行了一個(gè)用戶指令,server會(huì)估計(jì)一下它大約是在什么時(shí)間開始的,如下:
Command Execution Time = Current Server Time - Packet Latency - Client View Interpolation
接著,server把其他所有玩家-只是玩家-移回指令執(zhí)行的時(shí)間點(diǎn)。用戶指令執(zhí)行并且打擊被正確探測(cè)到。處理完用戶指令,玩家回到原來的位置上。
Note: 因?yàn)閑ntity interpolation包含在了公式中,調(diào)用失敗會(huì)導(dǎo)致意外的結(jié)果。
在listen server上,你可以sv_showimpacts 1,然后看到不同的server和client hitboxes:

這張截圖取自listen server上,有一個(gè)200毫秒lag(用net_fakelag),就在server確認(rèn)了打擊之后。紅色hitbox顯示了100ms+interp period之前,目標(biāo)在client上的位置。那之后,目標(biāo)繼續(xù)向左移動(dòng),而用戶指令還在向server傳輸中。用戶指令到達(dá)之后,server根據(jù)預(yù)測(cè)的指令執(zhí)行時(shí)間,存儲(chǔ)目標(biāo)位置(blue hitbox)。server跟蹤射擊并且確認(rèn)打擊(client看到目標(biāo)掉血)。
client and server hitboxes 不會(huì)完全匹配,因?yàn)樵跁r(shí)間計(jì)算上會(huì)有小的誤差。及時(shí)一個(gè)小到幾毫秒的差別也讓快速移動(dòng)的物體發(fā)生好幾英寸的錯(cuò)誤。Multiplayer hit detection不會(huì)精確到以像素為單位,基于tickrate和物體移動(dòng)速度,也有精確度的上限。
問題來了,為什么hit detection在server上這么復(fù)雜?倒回去追蹤玩家位置,以及當(dāng)hit detection可以在client-side輕松完成的情況下處理精確度錯(cuò)誤和處理pixel precision。(原句太長(zhǎng)不知道理解的對(duì)不對(duì)。Doing the back tracking of player positions and dealing with precision errors while hit detection could be done client-side way easier and with pixel precision.)client只需要告訴server一個(gè)”hit”消息,player在哪兒打的什么。我們不可能這么簡(jiǎn)單的就通過了。因?yàn)樵谶@些重要決定上,game server不會(huì)相信clients。即便client是”clean”的,并且受到Valve Anti-Cheat保護(hù),數(shù)據(jù)包還是有可能在傳輸?shù)絞ame server的途中,被第三方機(jī)器修改。這些“cheat proxies”會(huì)把”hit”消息插入到網(wǎng)絡(luò)數(shù)據(jù)包中,并且不會(huì)被VAC(a "man-in-the-middle" attack)探知到。
network latencies和lag compensation可以創(chuàng)造出看起來接近現(xiàn)實(shí)世界的paradoxes。例如,你會(huì)被一個(gè)自己根本看不到的人襲擊,因?yàn)槟阋呀?jīng)took over了。如果server把你的player hitboxes移回到剛才,就是你暴露目標(biāo)的地方,會(huì)發(fā)生什么?這些前后不一致的問題不會(huì)被完全解決的,因?yàn)橄鄬?duì)較慢的packet speeds。在現(xiàn)實(shí)世界中,你不會(huì)注意到這些問題,因?yàn)楣猓ㄏ喈?dāng)于數(shù)據(jù)包)的傳播非??欤愫椭車娜丝吹降氖澜缡且粯拥摹?br>
Net graph
Source引擎提供了很多工具可供你檢查client的鏈接速度和質(zhì)量。最受歡迎的一個(gè)是net graph,它可以通過net_graph 2 (or +graph)啟動(dòng)。細(xì)小的線條從右向左移動(dòng)代表輸入數(shù)據(jù)包。每條線的高度反映出包的大小。當(dāng)線之間出現(xiàn)間隔時(shí),表示數(shù)據(jù)包有丟失或者達(dá)到順序出現(xiàn)了錯(cuò)亂。所包含的數(shù)據(jù)內(nèi)容不同,線的顏色不同(color-coded)。
在net graph下,第一條線顯示的是每秒鐘當(dāng)前渲染的幀,你的平均latency,和當(dāng)前cl_updaterate值。第二條線顯示的是最后一個(gè)輸入包(快照)的大小(in bytes),平均輸入帶寬,每秒鐘接收到的包。第三條線顯示的是輸出包(用戶指令)一樣的數(shù)據(jù)。

Opimizations
默認(rèn)網(wǎng)絡(luò)設(shè)置是為在Internet上的dedicated server(專用服務(wù)器)上玩游戲而配備的。這個(gè)設(shè)定平衡了大多數(shù)的client/server硬件和network configuration(網(wǎng)絡(luò)隊(duì)列),使得大家都能很好的工作。對(duì)于Internet游戲,唯一可以在client上進(jìn)行調(diào)整手柄變量(console variable)是”rate”,它是定義你網(wǎng)絡(luò)鏈接的可用 bytes/second的帶寬。好的”rate”數(shù)值是調(diào)制解調(diào)器4500,ISDN 6000, 10000 DSL或者更高。
在性能高的網(wǎng)絡(luò)環(huán)境中,也就是server和所有clients的硬件條件都很充裕,是有可能對(duì)帶寬和tickrate設(shè)置進(jìn)行調(diào)節(jié)以獲得更精確gameplay。增加server tickrate通常是改進(jìn)移動(dòng)和射擊精準(zhǔn)度,但也會(huì)大量消耗CPU。Source server運(yùn)行tickrate 100時(shí),產(chǎn)生的CPU load大約是tickrate 66時(shí)的1.5倍或以上。這樣會(huì)導(dǎo)致嚴(yán)重的calculation lags,尤其是在很多人同時(shí)射擊的情況下。所以,我們不建議讓game server運(yùn)行比66還高的tickrate,要為緊急情況儲(chǔ)備CPU資源。
Note:在CSS, DoD S TF2, L4D 和 L4D2上不能改變tickrate,因?yàn)檫@樣做會(huì)導(dǎo)致server timing出問題。tickrate在CSS,DoD S和TF2中設(shè)置為66, 在L4D和L4D2中是30。
如果game server運(yùn)行tickrate很高,clients就會(huì)增加快照更新頻率(cl_updaterate)和用戶指令頻率(cl_cmdrate),當(dāng)然是要在帶寬(頻率)允許的情況下。快照更新頻率會(huì)受到server tickrate限制,server每tick送出的更新不可能超過一個(gè)。tickrate 66的server,cl_updatera最高的teclient數(shù)值也就是66。如果你提高快照頻率后遇到了數(shù)據(jù)包丟失或者阻塞,那么你需要重新調(diào)低它。提高cl_updaterate同樣會(huì)讓你的view interpolation delay (cl_interp)變低。默認(rèn)interpolation delay是0.1秒,是由默認(rèn)cl_updaterate 20得來的。View interpolation delay會(huì)讓移動(dòng)中的玩家比固定住的玩家有一點(diǎn)小小的優(yōu)勢(shì),因?yàn)橐苿?dòng)中的玩家可以早一點(diǎn)點(diǎn)時(shí)間看見他的目標(biāo)。這種效果是難以避免的,但是可以通過降低view interpolation delay來減少。如果兩個(gè)玩家都在移動(dòng),那么view lag delay對(duì)他們的影響效果是一樣的,不會(huì)有人有優(yōu)勢(shì)。
下面是快照頻率和view interpolation delay之間的關(guān)系:
interpolation period = max( cl_interp, cl_interp_ratio / cl_updaterate )
“Max(x,y)”代表“其中哪一個(gè)高”。你可以把cl_interp設(shè)為0,也會(huì)有一個(gè)安全數(shù)量的interp。你可以通過增加cl_updaterate從而進(jìn)一步減短interp period,但是不要超過tickrate(66)或者讓你的網(wǎng)絡(luò)連接承載超出它處理能力的數(shù)據(jù)量。
Tips
- 不要改變console設(shè)定值,除非你100%清楚自己在做什么
如果server或者network負(fù)載能力有限,很多“高性能”設(shè)置反而會(huì)引發(fā)負(fù)面效應(yīng)。
- 不要關(guān)掉view interpolation 和/或者 lag compensation
這些功能可以增進(jìn)移動(dòng)或者射擊的精確度。
- 對(duì)某個(gè)client進(jìn)行的優(yōu)化設(shè)置不一定也適用于其他clients
不要把其他clients的設(shè)置照搬到自己身上,而不做任何修改。
- 如果你是以spectator的身份在“First-Person”視角的游戲或者Source TV中跟蹤別的玩家,你和他看到的不會(huì)完全一樣
spectator看到的游戲世界沒有l(wèi)ag compensation。