延遲補償在C/S架構遊戲協議設計和優化中的應用
這篇文章介紹了cs這樣的第一人稱射擊遊戲中如何實現延遲補償。非第一人稱設計遊戲設計也可以參考其中一些思想
原文地址:
Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization
C/S架構遊戲協議設計和優化中延遲補償的應用
Contents [hide]
1 Overview
1 綜述
2 Basic Architecture of a Client / Server Game
2 C/S遊戲的基本架構
3 Contents of the User Input messages
3 使用者訊息的內容
4 Client Side Prediction
4 客戶端預測
5 Client-Side Prediction of Weapon Firing
5 開火過程中的客戶端預測
6 Umm, This is a Lot of Work
6 一些工作
7 Display of Targets
7 目標的顯示
8 Lag Compensation
8 延遲補償
9 Game Design Implications of Lag Compensation
9 遊戲涉及中延遲補償的使用
10 Conclusion
10 總結
11 Footnotes
11 腳註
Overview
1.綜述
Designing first-person action games for Internet play is a challenging process. Having robust on-line gameplay in your action title, however, is becoming essential to the success and longevity of the title. In addition, the PC space is well known for requiring developers to support a wide variety of customer setups. Often, customers are running on less than state-of-the-art hardware. The same holds true for their network connections.
第一人稱角色網路遊戲的設計是一項很有挑戰性的工作。網路環境下的健壯性,是動作遊戲能否成功的一個重要因素。另外,PC上面的開發者需要考慮到玩家層次不齊的機器配置以及網路狀況,很多使用者的硬體配置跟網路跟當前最好的配置跟網路有一定差距。
While broadband has been held out as a panacea for all of the current woes of on-line gaming, broadband is not a simple solution allowing developers to ignore the implications of latency and other network factors in game designs. It will be some time before
broadband truly becomes adopted the United States, and much longer before it can be assumed to exist for your clients in the rest of the world. In addition, there are a lot of poor broadband solutions, where users may occasionally have high bandwidth, but
more often than not also have significant latency and packet loss in their connections.
寬頻網路的出現有利於線上遊戲開發,但是開發者還是需要考慮網路延遲和其它網路特性。而且寬頻網路在美國被廣泛採用還需要一段時間,在世界上其它國家可能需要更長的一段時間。另外,很多寬頻網路質量很差,使用者雖然偶爾能夠享受到高頻寬,但更多的時候他們不得不面對高延遲和高丟包率。
Your game must behave well in this world. This discussion will give you a sense of some of the tradeoffs required to deliver a cutting-edge action experience on the Internet. The discussion will provide some background on how client / server architectures work
in many on-line action games. In addition, the discussion will show how predictive modeling can be used to mask the effects of latency. Finally, the discussion will describe a specific mechanism, lag compensation, for allowing the game to compensate for connection
quality.
我們應該提供給玩家良好的遊戲。本篇文章討論瞭如何提供給玩家頂尖的操作體驗;介紹了很多線上動作遊戲中採用的C/S架構背景。此外,我們還討論瞭如何通過一個預測模型來掩飾延遲帶來的影響。文章的最後描述了一個叫做延遲補償的機制,彌補了因為網路質量不好帶來的負面影響
Basic Architecture of a Client / Server Game
2.C/S遊戲的基本架構
Most action games played on the net today are modified client / server games. Games such as Half-Life, including its mods such as Counter-Strike and Team Fortress Classic, operate on such a system, as do games based on the Quake3 engine and the Unreal Tournament
engine. In these games, there is a single, authoritative server that is responsible for running the main game logic. To this are connected one or more "dumb" clients. These clients, initially, were nothing more than a way for the user input to be sampled and
forwarded to the server for execution. The server would execute the input commands, move around other objects, and then send back to the client a list of objects to render. Of course, the real world system has more components to it, but the simplified breakdown
is useful for thinking about prediction and lag compensation.
網路上可玩的大部分動作遊戲都是C/S結構遊戲基礎上修改完成的,比如半條命以及其修改版反恐精英、軍團要塞,以及一些基於quake3引擎和虛幻引擎的遊戲。這類遊戲都有一個用來執行遊戲邏輯的伺服器以及連線到這個伺服器的多個客戶端。客戶端僅僅是用來接收玩家的操作併發給伺服器,伺服器對這些操作作出響應,移動玩家周圍物體,並將遊戲世界的資訊發給客戶端顯示出來。當然世界的遊戲系統有更多元件,我們這樣簡化有利於分析預測和延遲補償。
With this in mind, the typical client / server game engine architecture generally looks like this:
基於這種考慮,典型的C/S遊戲引擎通常看起來是這樣的
For this discussion, all of the messaging and coordination needed to start up the connection between client and server is omitted. The client's frame loop looks something like the following:
為了便於討論,我們假定客戶端跟伺服器之間已經建立連線;客戶端的每一幀迴圈如下:
1.Sample clock to find start time
1.獲取幀開始時間
2.Sample user input (mouse, keyboard, joystick)
2.採集使用者輸入
3.Package up and send movement command using simulation time
3.根據模擬時間將移動命令打包傳送給伺服器
4.Read any packets from the server from the network system
4.獲取處理伺服器傳過來的資料包
5.Use packets to determine visible objects and their state
5.根據伺服器資料包的內容決定可見物體及其狀態
6.Render Scene
6.渲染場景
7.Sample clock to find end time
7.獲取幀結束時間
8.End time minus start time is the simulation time for the next frame
8.結束時間減去開始時間就是下一幀的模擬時間
Each time the client makes a full pass through this loop, the "frametime" is used for determining how much simulation is needed on the next frame. If your framerate is totally constant then frametime will be a correct measure. Otherwise, the frametimes will
be incorrect, but there isn't really a solution to this (unless you could deterministically figure out exactly how long it was going to take to run the next frame loop iteration before running it...).
客戶端每完成一個幀迴圈,就用“frametime”來決定下一幀需要多少時間,如果幀率恆定,“frametime”就是準確的,否則就沒辦法獲得準確的“frametime”(因為在沒一幀開始你不可能知道這一幀需要多長時間)
The server has a somewhat similar loop:
伺服器的迴圈大同小異:
1.Sample clock to find start time
1.獲取幀開始時間
2.Read client user input messages from network
2.讀取客戶端發過來的操作資訊
3.Execute client user input messages
3.根據客戶端操作執行邏輯運算
4.Simulate server-controlled objects using simulation time from last full pass
4.採用上一個迴圈得到的模擬時間來模擬伺服器控制的物體移動狀態
5.For each connected client, package up visible objects/world state and send to client
5.對每一個連線的客戶端,傳送打包相應的物體/世界狀態
6.Sample clock to find end time
6.獲取幀結束時間
7.End time minus start time is the simulation time for the next frame
7.結束時間減去開始時間就是下一幀的模擬時間
In this model, non-player objects run purely on the server, while player objects drive their movements based on incoming packets. Of course, this is not the only possible way to accomplish this task, but it does make sense.
在這個模型中,非玩家物體完全由伺服器控制其狀態,每個玩家根據伺服器發過來的資料包控制自己的移動。這是一種很自然的方法,當然還有其它的方法也可以完成這個功能。
Contents of the User Input messages
3.使用者訊息的內容
In Half-Life engine games, the user input message format is quite simple and is encapsulated in a data structure containing just a few essential fields:基於half-life引擎的遊戲使用者訊息都很簡單,只需要封裝在一個包含幾個關鍵成員的結構中:
typedef struct usercmd_s
{
// Interpolation time on client
shortlerp_msec;
// Duration in ms of command
bytemsec;
// Command view angles.
vec3_tviewangles;
// intended velocities
// Forward velocity.
floatforwardmove;
// Sideways velocity.
floatsidemove;
// Upward velocity.
floatupmove;
// Attack buttons
unsigned short buttons;
//
// Additional fields omitted...
//
} usercmd_t;
The critical fields here are the msec, viewangles, forward, side, and upmove, and buttons fields. The msec field corresponds to the number of milliseconds of simulation that the command corresponds to (it's the frametime). The viewangles field is a vector representing
the direction the player was looking during the frame. The forward, side, and upmove fields are the impulses determined by examining the keyboard, mouse, and joystick to see if any movement keys were held down. Finally, the buttons field is just a bit field
with one or more bits set for each button that is being held down.
結構中最關鍵的變數時msec,viewangles,forward,side,upmove和buttons。msec表示這個命令執行對應的毫秒數(也就是上面提到的“frametime”)。viewangles是一個三維向量,表示玩家的朝向。forward,side和upmove表示玩家是否通過鍵盤、滑鼠或控制桿控制移動。最後,buttons這個欄位包含一個或多個位元,標誌玩家是否按著某些按鍵。
Using the above data structures and client / server architecture, the core of the simulation is as follows. First, the client creates and sends a user command to the server. The server then executes the user command and sends updated positions of everything back to client. Finally, the client renders the scene with all of these objects. This core, though quite simple, does not react well under real world situations, where users can experience significant amounts of latency in their Internet connections. The main problem is that the client truly is "dumb" and all it does is the simple task of sampling movement inputs and waiting for the server to tell it the results. If the client has 500 milliseconds of latency in its connection to the server, then it will take 500 milliseconds for any client actions to be acknowledged by the server and for the results to be perceptible on the client. While this round trip delay may be acceptable on a Local Area Network (LAN), it is not acceptable on the Internet.
基於C/S架構的遊戲採用以上資料結構執行如下:客戶端建立命令併發送到伺服器,伺服器響應這些命令並把更新了的世界和物體位置資訊發回客戶端,客戶端收到以後進行渲染。這種方式非常簡單,但是在實際應用中效果差強人意,使用者會感覺到網路連線帶來的明顯延遲。這主要是由於客戶端完全沒有邏輯操作,發出訊息以後就等待伺服器響應。如果客戶端跟伺服器有500ms的延遲,客戶端執行了操作到看到操作的結果就需要500ms,這種延遲在區域網通常可以接受(因為通常延遲比較小),但在因特網上是沒法接受的
Client Side Prediction
4.客戶端預測
One method for ameliorating this problem is to perform the client's movement locally and just assume, temporarily, that the server will accept and acknowledge the client commands directly. This method is labeled as client-side prediction.
有一種方法可以改善這種情況:客戶端本地即時執行移動操作,假定伺服器即時通知客戶端可以執行操作,這種方法可以稱為客戶端預測。
Client-side prediction of movements requires us to let go of the "dumb" or minimal client principle. That's not to say that the client is fully in control of its simulation, as in a peer-to-peer game with no central server. There still is an authoritative server running the simulation just as noted above. Having an authoritative server means that even if the client simulates different results than the server, the server's results will eventually correct the client's incorrect simulation. Because of the latency in the connection, the correction might not occur until a full round trip's worth of time has passed. The downside is that this can cause a very perceptible shift in the player's position due to the fixing up of the prediction error that occurred in the past.
採用客戶端運動預測以後,客戶端就不再是一個“小型客戶端”,不再單單響應伺服器命令;但也不是說客戶端可以像沒有中央伺服器的p2p遊戲完全自治。伺服器仍然在執行並保證在客戶端跟伺服器執行結果不一致的情況下糾正客戶端錯誤的模擬。由於網路延遲,修正在一個網路傳輸週期以後才會執行,這個時候糾正資訊通常已經過期,這樣會導致明顯的位置漂移,因為客戶端收到的修正資訊是過去某個時間的。
To implement client-side prediction of movement, the following general procedure is used. As before, client inputs are sampled and a user command is generated. Also as before, this user command is sent off to the server. However, each user command (and the
exact time it was generated) is stored on the client. The prediction algorithm uses these stored commands.
為了使客戶端運動預測有效,我們採用以下方法:還是客戶端取樣並生成命令傳送到伺服器,但是每個包含生成時間的命令在客戶端本地存起來並在預測演算法中使用。
For prediction, the last acknowledged movement from the server is used as a starting point. The acknowledgement indicates which user command was last acted upon by the server and also tells us the exact position (and other state data) of the player after that
movement command was simulated on the server. The last acknowledged command will be somewhere in the past if there is any lag in the connection. For instance, if the client is running at 50 frames per second (fps) and has 100 milliseconds of latency (roundtrip),
then the client will have stored up five user commands ahead of the last one acknowledged by the server. These five user commands are simulated on the client as a part of client-side prediction. Assuming full prediction[1], the client will want to start with
the latest data from the server, and then run the five user commands through "similar logic" to what the server uses for simulation of client movement. Running these commands should produce an accurate final state on the client (final player position is most
important) that can be used to determine from what position to render the scene during the current frame.
預測的過程中,我們把伺服器確認的移動資訊作為開始,這樣客戶端就可以確定伺服器執行上次命令以後遊戲中玩家的準確資訊(比如位置)。如果網路有延遲,這個確認命令也會有一定延遲。假設客戶端執行幀率為50fps,網路延時為100ms,這樣在客戶端收到伺服器的確認命令的時候,本地命令佇列中已經有5條資訊,這5條資訊被用來執行客戶端預測。假設執行完全預測【1】客戶端在收到來自伺服器的最新資訊後,就開始按照與伺服器相同的邏輯執行本地訊息佇列中的5個命令。這些命令執行以後得到當前狀態(最重要的是位置),然後根據玩家的狀態資訊渲染當前幀。
In Half-Life, minimizing discrepancies between client and server in the prediction logic is accomplished by sharing the identical movement code for players in both the server-side game code and the client-side game code. These are the routines in the pm_shared/
(which stands for "player movement shared") folder of the HL SDK. The input to the shared routines is encapsulated by the user command and a "from" player state. The output is the new player state after issuing the user command. The general algorithm on the
client is as follows:
在半條命這個遊戲中,客戶端跟伺服器採用相同的程式碼來計算移動,這樣可以減小客戶端預測跟伺服器之間的誤差。這些程式碼位於HLSDK中的pm_shared/(意思是“player movement shared”)。這段程式碼的輸入是玩家操作和客戶端的初始狀態,輸出是玩家操作以後的狀態。客戶端演算法大致如下:
"from state" <- state after last user command acknowledged by the server;
"command" <- first command after last user command acknowledged by server;
while (true)
{
run "command" on "from state" to generate "to state";
if (this was the most up to date "command")
break;
"from state" = "to state";
"command" = next "command";
};
“初始狀態” <-上個玩家命令執行以後玩家的狀態
"命令" <-尚未執行的下一條命令
while (true)
{
以"from state" 為基礎執行"command"得到 "to state";
if (沒有更多的 "command")
break;
"from state" = "to state";
"command" = next "command";
};
The origin and other state info in the final "to state" is the prediction result and is used for rendering the scene that frame. The portion where the command is run is simply the portion where all of the player state data is copied into the shared data structure, the user command is processed (by executing the common code in the pm_shared routines in Half-Life's case), and the resulting data is copied back out to the "to state".
玩家的初始狀態和預測結果用來渲染場景。命令的執行過程就是:將玩家狀態複製到共享資料結構中,執行玩家操作(執行hlsdk中pm_shared中的共用程式碼),然後將結果複製到目標狀態(to state)
There are a few important caveats to this system. First, you'll notice that, depending upon the client's latency and how fast the client is generating user commands (i.e., the client's framerate), the client will most often end up running the same commands over and over again until they are finally acknowledged by the server and dropped from the list (a sliding window in Half-Life's case) of commands yet to be acknowledged. The first consideration is how to handle any sound effects and visual effects that are created in the shared code. Because commands can be run over and over again, it's important not to create footstep sounds, etc. multiple times as the old commands are re-run to update the predicted position. In addition, it's important for the server not to send the client effects that are already being predicted on the client. However, the client still must re-run the old commands or else there will be no way for the server to correct any erroneous prediction by the client. The solution to this problem is easy: the client just marks those commands which have not been predicted yet on the client and only plays effects if the user command is being run for the first time on the client.
這個系統中有幾個需要注意的地方,首先,由於網路延遲,客戶端又在不停地以一定速度(客戶端幀率)生成命令,一個命令通常會被客戶端多次執行,知道得到伺服器的確定以後將其從命令列表中刪除(這就是半條命中的滑動視窗)。首先要考慮的是如何處理共享程式碼中生成的聲效和動畫效果。因為命令可能會被多次執行,預測位置的過程被多次執行的時候要注意避免重聲等不正確的效果。另外,伺服器也要避免客戶端意見預測的效果。然而,客戶端必須重新執行舊的命令,否則就沒法根據伺服器來糾正客戶端的預測錯誤。解決方法很簡單:客戶端將沒有執行的客戶端命令進行標記,如果這些命令在客戶端第一次執行,則播放相應的效果。
The other caveat is with respect to state data that exists solely on the client and is not part of the authoritative update data from the server. If you don't have any of this type of data, then you can simply use the last acknowledged state from the server
as a starting point, and run the prediction user commands "in-place" on that data to arrive at a final state (which includes your position for rendering). In this case, you don't need to keep all of the intermediate results along the route for predicting from
the last acknowledged state to the current time. However, if you are doing any logic totally client side (this logic could include functionality such as determining where the eye position is when you are in the process of crouching—and it's not really totally
client side since the server still simulates this data also) that affects fields that are not replicated from the server to the client by the networking layer handling the player's state info, then you will need to store the intermediate results of prediction.
This can be done with a sliding window, where the "from state" is at the start and then each time you run a user command through prediction, you fill in the next state in the window. When the server finally acknowledges receiving one or more commands that
had been predicted, it is a simple matter of looking up which state the server is acknowledging and copying over the data that is totally client side to the new starting or "from state".
另外需要注意的是伺服器不處理,只有客戶端才有的一些資料;如果沒有這種型別的資料,我們可以如上面所述,以伺服器第一條訊息作為起點進行預測得到下一幀狀態(包括用來渲染的位置資訊)。然而,如果有些邏輯是純客戶端的,伺服器不會處理(比如玩家蹲下來眼睛的位置-然而這也不是純客戶端資訊,因為伺服器也會處理這個資料),這種情況下我們需要將預測的中間結果存起來。可以用一個滑動視窗完成這項工作,其中“開始狀態”是開始,以後每次執行一個玩家命令預測完成後,填寫視窗中的下一個狀態;當伺服器通知某個命令被接受並執行以後,從視窗中查詢伺服器處理的是哪條命令並將相應的資料傳到下一個幀的“起始狀態”
So far, the above procedure describes how to accomplish client side prediction of movements. This system is similar to the system used in QuakeWorld2.
到此為止,我們描述了客戶端的運動預測。quakeworld2中採用了這種型別的預測
Client-Side Prediction of Weapon Firing
5.開火過程中的客戶端預測
Layering prediction of the firing effects of weapons onto the above system is straightforward. Additional state information is needed for the local player on the client, of course, including which weapons are being held, which one is active, and how much ammo each of these weapons has remaining. With this information, the firing logic can be layered on top of the movement logic because, once again, the state of the firing buttons is included in the user command data structure that is shared between the client and the server. Of course, this can get complicated if the actual weapon logic is different between client and server. In Half-Life, we chose to avoid this complication by moving the implementation of a weapon's firing logic into "shared code" just like the player movement code. All of the variables that contribute to determining weapon state (e.g., ammo, when the next firing of the weapon can occur, what weapon animation is playing, etc.), are then part of the authoritative server state and are replicated to the client so that they can be used on the client for prediction of weapon state there.
上面描述的系統可以很自然地用於武器開火效果預測。客戶端玩家需要記錄一些狀態,比如身上有哪些武器,正在使用的是哪一個,每把武器都還剩多少彈藥。有了這些資訊,開火邏輯可以建立在運動邏輯上面,只需要在客戶端和伺服器使用的命令裡面加上玩家開火的按鍵資訊。在半條命中,為了簡單,武器開火邏輯程式碼也跟運動程式碼一樣也作為“共享程式碼”。所有會影響到武器狀態的變數,比如彈藥、下次可開火時間、正在播放那個武器動畫,都作為伺服器的狀態,這些狀態會通知給客戶端用來預測武器狀態。
Predicting weapon firing on the client will likely lead to the decision also to predict weapon switching, deployment, and holstering. In this fashion, the user feels that the game is 100% responsive to his or her movement and weapon activation activities. This
goes a long way toward reducing the feeling of latency that many players have come to endure with today's Internet-enabled action experiences.
客戶端武器開火預測包括預測武器切換、部署、手槍皮套。這樣,玩家會感覺遊戲中的移動和武器狀態100%受他控制。這在減小網路延遲給玩家帶來的不爽上面邁出了一大步。
Umm, This is a Lot of Work
6.一些工作
Replicating the necessary fields to the client and handling all of the intermediate state is a fair amount of work. At this point, you may be asking, why not eliminate all of the server stuff and just have the client report where s/he is after each movement? In other words, why not ditch the server stuff and just run the movement and weapons purely on the client-side? Then, the client would just send results to the server along the lines of, "I'm now at position x and, by the way, I just shot player 2 in the head." This is fine if you can trust the client. This is how a lot of the military simulation systems work (i.e., they are a closed system and they trust all of the clients). This is how peer-to-peer games generally work. For Half-Life, this mechanism is unworkable because of realistic concerns about cheating. If we encapsulated absolute state data in this fashion, we'd raise the motivation to hack the client even higher than it already is3. For our games, this risk is too high and we fall back to requiring an authoritative server.
伺服器需要將必要的欄位發給客戶端,並且處理很多中間狀態,有人可能有這樣的疑問,為什麼不把伺服器邏輯取消,讓客戶端廣播自己的位置,也就是將所有的移動、開火邏輯放在客戶端。這樣,客戶端就會給伺服器傳送類似這樣的結果報告:“我在X位置,我爆了玩家2的腦袋”。如果客戶端可信的話,這樣做是可以的,很多軍方模擬系統就是這樣做的(他們是一個封閉系統,所有客戶端都可信)。點對點的遊戲也是這麼做的。對於半條命來說不可以這樣做,因為客戶端可能“欺騙”伺服器。如果我們以這種方法封裝狀態資料,就會誘導玩家破解客戶端【3】。對於我們的遊戲來說這樣做奉獻太大,我們還是選擇採用伺服器模式來做校驗。
A system where movements and weapon effects are predicted client-side is a very workable system. For instance, this is the system that the Quake3 engine supports. One of the problems with this system is that you still have to have a feel for your latency to
determine how to lead your targets (for instant hit weapons). In other words, although you get to hear the weapons firing immediately, and your position is totally up-to-date, the results of your shots are still subject to latency. For example, if you are
aiming at a player running perpendicular to your view and you have 100 milliseconds of latency and the player is running at 500 units per second, then you'll need to aim 50 units in front of the target to hit the target with an instant hit weapon. The greater
the latency, the greater the lead targeting needed. Getting a "feel" for your latency is difficult. Quake3 attempted to mitigate this by playing a brief tone whenever you received confirmation of your hits. That way, you could figure out how far to lead by
firing your weapons in rapid succession and adjusting your leading amount until you started to hear a steady stream of tones. Obviously, with sufficient latency and an opponent who is actively dodging, it is quite difficult to get enough feedback to focus
in on the opponent in a consistent fashion. If your latency is fluctuating, it can be even harder.
客戶端進行運動和武器效果預測是非常可行的。例如quake3就支援這樣的預測。這個系統需要注意一點,在判斷目標的時候需要考慮到延遲(比如即時射擊武器)。換句話說,雖然你看到自己用即時武器進行了射擊,你自己的位置也是最新的,射擊結果仍然跟延遲有關。例如,如果你射擊一個玩家,這個玩家沿與你實現垂直的方向奔跑,假設你客戶端延遲為100ms,玩家奔跑速度是500單位每秒,這樣你需要瞄準玩家前方50單位才能準確擊中。延遲越大,就需要更大的提前量。靠感覺彌補延遲太困難了。為了減輕這種效果,quake3對你的射擊播放一個短音來進行確定。這樣,玩家可以算出快速發射武器的時候需要多大的提前量,同時調整提前量直到聽到穩定的音調串。如果延遲比較大,而你的對手又在不斷躲避,就很難獲得足夠的反饋判斷。如果延遲也不斷變化,就更難了。
Display of Targets
7.目標的顯示
Another important aspect influencing how a user perceives the responsiveness of the world is the mechanism for determining, on the client, where to render the other players. The two most basic mechanisms for determining where to display objects are extrapolation and interpolation[4].
影響玩家遊戲體驗的另一個重要方面是客戶端如何渲染其它玩家。兩種基本的判斷機制是:外推法和內插法【4】
For extrapolation, the other player/object is simulated forward in time from the last known spot, direction, and velocity in more or less a ballistic manner. Thus, if you are 100 milliseconds lagged, and the last update you received was that (as above) the
other player was running 500 units per second perpendicular to your view, then the client could assume that in "real time" the player has moved 50 units straight ahead from that last known position. The client could then just draw the player at that extrapolated
position and the local player could still more or less aim right at the other player.
外推法把其它玩家/物體看作一個點,這個點開始的位置、方向、速度已知,沿著自己的彈道向前移動。因此,假設延時是100ms,最新的協議通知客戶端這個玩家奔跑速度是500單位每秒,方向垂直於玩家視線,客戶端就可以假設事實上這個玩家當前實際的位置已經向前移動了50個單位。客戶端可以在這個外推的位置渲染這個玩家,這樣本地玩家就差不多可以正確瞄準。
The biggest drawback of using extrapolation is that player's movements are not very ballistic, but instead are very non-deterministic and subject to high jerk[5]. Layer on top of this the unrealistic player physics models that most FPS games use, where player's
can turn instantaneously and apply unrealistic forces to create huge accelerations at arbitrary angles and you'll see that the extrapolation is quite often incorrect. The developer can mitigate the error by limiting the extrapolation time to a reasonable value
(QuakeWorld, for instance, limited extrapolation to 100 milliseconds). This limitation helps because, once the true player position is finally received, there will be a limited amount of corrective warping. In a world where most players still have greater
than 150 milliseconds of latency, the player must still lead other players in order to hit them. If those players are "warping" to new spots because of extrapolation errors, then the gameplay suffers nonetheless.
外推法的最大缺點是玩家的移動並不是完全彈道的,而是不確定的並且高"jerk"【5】。大部分FPS遊戲採用非現實的玩家系統,玩家可以隨時轉彎,可以在任意角度作用不現實的加速度,因此外推法得到的結果經常是錯誤地。開發者可以通過限制外推時間來減輕外推誤差(比如quake限制不能超過100ms)。這種限制使得在客戶端收到玩家正確位置以後,糾錯不至於太大。當前大部分玩家的網路延遲高於150ms,玩家必須對遊戲中的其他玩家進行外推以便正確擊中。如果別的玩家因為外推錯誤,被伺服器拉回,遊戲體驗將非常差。
The other method for determining where to display objects and players is interpolation. Interpolation can be viewed as always moving objects somewhat in the past with respect to the last valid position received for the object. For instance, if the server is sending 10 updates per second (exactly) of the world state, then we might impose 100 milliseconds of interpolation delay in our rendering. Then, as we render frames, we interpolate the position of the object between the last updated position and the position one update before that (alternatively, the last render position) over that 100 milliseconds. As the object just gets to the last updated position, we receive a new update from the server (since 10 updates per second means that the updates come in every 100 milliseconds) we can start moving toward this new position over the next 100 milliseconds.
另一種方法叫插值法。插值法可以這樣理解:客戶端物體實際移動位置總是滯後一段時間。舉個例子,如果伺服器每秒同步10次世界資訊,客戶端渲染的時候會有100ms滯後。這樣,每一幀渲染的時候,我們通過最新收到的位置資訊和前100ms的位置資訊(或者上一幀渲染位置)進行差值得到結果。我們每收到一個物體位置的更新資訊,(每秒10個更新意味著每100ms收到一個更新)接下來的100ms我們就可以朝這個新的位置移動。
If one of the update packets fails to arrive, then there are two choices: We can start extrapolating the player position as noted above (with the large potential errors noted) or we can simply have the player rest at the position in the last update until a
new update arrives (causing the player's movement to stutter).
如果一個更新包沒有收到,有2種處理方法:第一、用上面介紹的外推法(有可能產生較大誤差);第二、保持玩家位於當前位置直到收到下一個更新包(會導致玩家移動頓挫)
The general algorithm for this type of interpolation is as follows:
內插法的大致過程如下:
1.Each update contains the server time stamp for when it was generated[6]
1.每個更新包包含生成的伺服器時間戳【6】
2.From the current client time, the client computes a target time by subtracting the interpolation time delta (100 ms)
2.根據客戶端當前時間,客戶端通過減去時間差(100ms)計算 一個目標時間
3.If the target time is in between the timestamp of the last update and the one before that, then those timestamps determine what fraction of the time gap has passed.
3.如果計算得到的目標時間在上一個更新時間和上上個更新時間之間,這些時間戳可以決定目標時間在過去的時間間隙中的情況
4.This fraction is used to interpolate any values (e.g., position and angles).
4.目標時間情況用來通過插值計算結果(如位置、角度)
In essence, you can think of interpolation, in the above example, as buffering an additional 100 milliseconds of data on the client. The other players, therefore, are drawn where they were at a point in the past that is equal to your exact latency plus the
amount of time over which you are interpolating. To deal with the occasional dropped packet, we could set the interpolation time as 200 milliseconds instead of 100 milliseconds. This would (again assuming 10 updates per second from the server) allow us to
entirely miss one update and still have the player interpolating toward a valid position, often moving through this interpolation without a hitch. Of course, interpolating for more time is a tradeoff, because it is trading additional latency (making the interpolated
player harder to hit) for visual smoothness.
上面提到的插值法,本質上是客戶端快取了接下來100ms的資料。對於每一個周圍的玩家,他們都位於過去某個時間的位置,根據每一個具體的時間點進行插值。如果偶爾發生丟包,我們就將插值時間延長到200ms。這樣我們就可以忽略一次更新(假設同步頻率還是10次每秒),玩家還可以移動到合理的目標位置,這樣進行插值通常不會有什麼問題。當然,插值多少時間需要權衡,因為這種方法是用延時(玩家更難擊中)來換取平滑。
In addition, the above type of interpolation (where the client tracks only the last two updates and is always moving directly toward the most recent update) requires a fixed time interval between server updates. The method also suffers from visual quality issues
that are difficult to resolve. The visual quality issue is as follows. Imagine that the object being interpolated is a bouncing ball (which actually accurately describes some of our players). At the extremes, the ball is either high in the air or hitting the
pavement. However, on average, the ball is somewhere in between. If we only interpolate to the last position, it is very likely that this position is not on the ground or at the high point. The bounciness of the ball is "flattened" out and it never seems to
hit the ground. This is a classical sampling problem and can be alleviated by sampling the world state more frequently. However, we are still quite likely never actually to have an interpolation target state be at the ground or at the high point and this will
still flatten out the positions.
另外,上述插值方法(客戶端通過2個更新資訊插值並且朝最新更新位置移動)需要伺服器更新資訊間隔固定。對於所謂的“視覺效果因素”,這種方式很難處理,“視覺效果因素”是這樣的:假設我們插值的物體是彈球(這種模型可以準確描述某些玩家)。極端情況下,球或者在空中,或者正在碰地板。然而,通常情況下球在這兩種狀態之間。如果我們只插值上一個位置,這個位置可能既不在地面上,也不是最高點,這樣,彈球彈的效果就被平滑掉了,好像永遠沒有彈到地面一樣。這是一個經典問題,增加取樣率可以減輕這種影響,但是仍然有可能我們取樣不到球在地面的點跟最高點,這些點會給平滑掉。
In addition, because different users have different connections, forcing updates to occur at a lockstep like 10 updates per second is forcing a lowest common denominator on users unnecessarily. In Half-Life, we allow the user to ask for as many updates per
second as he or she wants (within limit). Thus, a user with a fast connection could receive 50 updates per second if the user wanted. By default, Half-Life sends 20 updates per second to each player the Half-Life client interpolates players (and many other
objects) over a period of 100 milliseconds.[7]
另外,不同使用者網路狀況不同,強迫每個使用者都以固定速度更新(比如每秒10次)效果不是很好,在半條命中,使用者每秒可以請求任意數量的更新包(沒有限制)。這樣,高速網路使用者可以每秒更新50次,只要使用者願意。半條命的預設設定是每秒每個使用者(以及遊戲中其它物體)傳送20次更新,以100ms為時間視窗進行插值。【7】
To avoid the flattening of the bouncing ball problem, we employ a different algorithm for interpolation. In this method, we keep a more complete "position history" for each object that might be interpolated.
為了避免“反彈球"平滑問題,我們在插值的過程中採用了一個不同的演算法,這種演算法中我們對每一個可能插值的物體記錄了一個完整的“歷史位置”資訊。
The position history is the timestamp and origin and angles (and could include any other data we want to interpolate) for the object. Each update we receive from the server creates a new position history entry, including timestamp and origin/angles for that
timestamp. To interpolate, we compute the target time as above, but then we search backward through the history of positions looking for a pair of updates that straddle the target time. We then use these to interpolate and compute the final position for that
frame. This allows us to smoothly follow the curve that completely includes all of our sample points. If we are running at a higher framerate than the incoming update rate, we are almost assured of smoothly moving through the sample points, thereby minimizing
(but not eliminating, of course, since the pure sampling rate of the world updates is the limiting factor) the flattening problem described above.
歷史位置資訊記錄了物體的時間戳、遠點、角度(以及其它我們需要插值計算的資料)。我們每收到一個伺服器的更新,我們就建立一條包含時間戳的記錄,其中包含原始位置、角度資訊。在插值過程中,我們用上面的方法計算目標時間,然後搜尋位置歷史資訊,找到包含目標時間的記錄區間。然後用找到的資訊插值計算當前幀的位置。這樣我們就可以平滑跟蹤到包含所有采樣點的曲線。如果客戶端幀率比伺服器更新頻率大,我們就可以將取樣點平滑處理,減小上面提到的平滑處理帶來的問題(當然沒法避免,因為採用頻率限制,而世界本身是連續的)。
The only consideration we have to layer on top of either interpolation scheme is some way to determine that an object has been forcibly teleported, rather than just moving really quickly. Otherwise we might "smoothly" move the object over great distances, causing
the object to look like it's traveling way too fast. We can either set a flag in the update that says, "don't interpolate" or "clear out the position history," or we can determine if the distance between the origin and one update and another is too big, and
thereby presumed to be a teleportation/warp. In that case, the solution is probably to just move the object to the latest know position and start interpolating from there.
需要注意的是,上面提到的插值方法使用的時候,物體有時候會被伺服器拉回,而不是快速移動。當然我們也可以平滑地將物體移動一段較長的距離,這樣看起來物體移動很快。更新的過程中我們可以設一個標誌表示不插值或清除歷史記錄,或者如果起始點與目標點距離過長,我們就認為資料不正常。這種情況我們就將物體直接拉過去。並以這個位置為起始點進行插值。
Lag Compensation
8.延遲補償
Understanding interpolation is important in designing for lag compensation because interpolation is another type of latency in a user's experience. To the extent that a player is looking at other objects that have been interpolated, then the amount of interpolation must be taken into consideration in computing, on the server, whether the player's aim was true.插值也會帶來延遲,所以考慮延遲補償的過程中需要理解插值過程。玩家看到的別的物體是經過插值計算出來的,所以插值過程中需要考慮在伺服器上玩家的目標是否正確。
Lag compensation is a method of normalizing server-side the state of the world for each player as that player's user commands are executed. You can think of lag compensation as taking a step back in time, on the server, and looking at the state of the world
at the exact instant that the user performed some action. The algorithm works as follows:
延遲補償是伺服器執行的一種策略,當伺服器收到客戶端命令並執行的過程中,根據客戶端的具體情況進行歸一。延遲補償可以看做伺服器處理使用者命令的時候回退一段時間,退到客戶端傳送命令時候的準確時間。演算法流程如下:
1.Before executing a player's current user command, the server:
1.伺服器執行客戶端命令之前執行以下操作:
1.Computes a fairly accurate latency for the player
1.計算玩家正確的延遲
2.Searches the server history (for the current player) for the world update that was sent to the player and received by the player just before the player would have issued the movement command
2.對每個玩家,從伺服器歷史資訊中找到傳送給玩家資訊和收到玩家響應的資訊。
3.From that update (and the one following it based on the exact target time being used), for each player in the update, move the other players backwards in time to exactly where they were when the current player's
user command was created. This moving backwards must account for both connection latency andthe interpolation amount[8] the client was using that frame.
3.對於每一個玩家,將其拉回到這個更新時間(插值得到的精確時間)中執行使用者命令。這個回退時間需要考慮到命令執行的時候的網路延時和插值量【8】
2.Allow the user command to execute (including any weapon firing commands, etc., that will run ray casts against all of the other players in their "old" positions).
2.執行玩家命令(包括武器開火等。)
3.Move all of the moved/time-warped players back to their correct/current positions
3.將所有移動的、錯位的玩家移動到他們當前正確位置。
Note that in the step where we move the player backwards in time, this might actually require forcing additional state info backwards, too (for instance, whether the player was alive or dead or whether the player was ducking). The end result of lag compensation
is that each local client is able to directly aim at other players without having to worry about leading his or her target in order to score a hit. Of course, this behavior is a game design tradeoff.
注意:我們把時間往後推算的時候,需要考慮那個時候玩家的狀態,比如玩家是或者還是已經已經死掉,玩家是否處於躲避狀態。執行運動補償以後,玩家就可以直接瞄準目標進行設計,而不需要計算一個提前量。當然,這種方案是遊戲中的權衡設計。
Game Design Implications of Lag Compensation
9.遊戲涉及中延遲補償的使用
The introduction of lag compensation allows for each player to run on his or her own clock with no apparent latency. In this respect, it is important to understand that certain paradoxes or inconsistencies can occur. Of course, the old system with the authoritative server and "dumb" or simple clients had it's own paradoxes. In the end, making this tradeoff is a game design decision. For Half-Life, we believe deciding in favor of lag compensation was a justified game design decision.採用延遲補償以後,每個玩家遊戲的過程中感覺不到明顯延遲。在這裡需要理解可能會產生一些矛盾和不一致。當然,驗證伺服器和無邏輯的客戶端老系統也會有自相矛盾的情況。最後,這個這種事遊戲設計決定的。對於半條命,我們相信採用延遲補償是正確的遊戲決定。
The first problem of the old system was that you had to lead your target by some amount that was related to your latency to the server. Aiming directly at another player and pressing the fire button was almost assured to miss that player. The inconsistency
here is that aiming is just not realistic and that the player controls have non-predictable responsiveness.
老系統的一個問題是,由於網路延遲,目標需要有一個提前量。瞄準敵人進行射擊幾乎總是不能擊中。這種不一致導致射擊很不真實,響應也不可控制。
With lag compensation, the inconsistencies are different. For most players, all they have to do is acquire some aiming skill and they can become proficient (you still have to be able to aim). Lag compensation allows the player to aim directly at his
or her target and press the fire button (for instant hit weapons[9]). The inconsistencies that sometimes occur, however, are from the points of view of the players being fired upon.
採用延遲補償以後帶來的是另一種形式的不一致。對於大部分玩家,他們只需要專注於得到更多的射擊技能來武裝他們(當然他們也是需要瞄準的)。延時補償使得玩家只需要直接瞄準他的目標並按下開火按鈕即可(對於即時擊中武器【9】)。不一致也時有發生,但是是在擊中以後。
For instance, if a highly lagged player shoots at a less lagged player and scores a hit, it can appear to the less lagged player that the lagged player has somehow "shot around a corner"10. In this case, the lower lag player may have darted around a corner.
But the lagged player is seeing everything in the past. To the lagged player, s/he has a direct line of sight to the other player. The player lines up the crosshairs and presses the fire button. In the meantime, the low lag player has run around a corner and
maybe even crouched behind a crate. If the high lag player is sufficiently lagged, say 500 milliseconds or so, this scenario is quite possible. Then, when the lagged player's user command arrives at the server, the hiding player is transported backward in
time and is hit. This is the extreme case, and in this case, the low ping player says that s/he was shot from around the corner. However, from the lagged player's point of view, they lined up their crosshairs on the other player and fired a direct hit. From
a game design point of view, the decision for us was easy: let each individual player have completely responsive interaction with the world and his or her weapons.
例如,如果一個延時比較大的玩家擊中一個延時比較小的玩家並且得到一分,低延時的玩家會感覺高延時玩家“在角落裡被擊中”【10】。這種情況下,低延遲玩家可能已經從角落裡衝出,而高延時玩家看到的是過去的資訊。每一個有延遲的玩家都有一個朝向別的玩家的直的視線,直的視線指向一個瞄準點然後開火。這個時候,低延時的玩家可能已經跑到角落裡並且蹲在一個箱子後面,如果高延遲玩家延遲比較大,比如500ms,這是經常發生的;這樣當高延時玩家的命令傳到伺服器的時候,已經隱藏起來的玩家需要取一個歷史位置並計算是否擊中,在這種極端情況下,低延時玩家會覺得他再角落裡被擊中了。然而,對於高延時玩家來說,他是正對著別的玩家開火的。從遊戲設計的角度來講,我們需要這樣決定:讓每個玩家即時與世界互動並開火。
In addition, the inconsistency described above is much less pronounced in normal combat situations. For first-person shooters, there are two more typical cases. First, consider two players running straight at each other pressing the fire button. In this case,
it's quite likely that lag compensation will just move the other player backwards along the same line as his or her movement. The person being shot will be looking straight at his attacker and no "bullets bending around corners" feeling will be present.
此外,在正常戰鬥中,上面提到的不一致並不明顯。對於第一人稱射擊遊戲,有兩種典型情況。第一、考慮兩個玩家直線跑向對方並且開火;這種情況下,延時補償只會把玩家在移動直線上往後拉。被擊中的玩家看他的射擊者在前方,這樣就不會有“子彈拐到角落裡”的情況發生。
The next example is two players, one aiming at the other while the other dashes in front perpendicular to the first player. In this case, the paradox is minimized for a wholly different reason. The player who is dashing across the line of sight of the shooter
probably has (in first-person shooters at least) a field of view of 90 degrees or less. In essence, the runner can't see where the other player is aiming. Therefore, getting shot isn't going to be surprising or feel wrong (you get what you deserve for running
around in the open like a maniac). Of course, if you have a tank game, or a game where the player can run one direction, and look another, then this scenario is less clear-cut, since you might see the other player aiming in a slightly incorrect direction.
第二種情況是兩個玩家中的一個射擊,另外一個玩家在垂直於第一個玩家視線的方向衝鋒。這種情況下的解決問題的原理與剛才不同。剛才提到的衝鋒的玩家視野差不多是90°(至少第一人稱射擊遊戲是這樣),因此,這個玩家看不到正在射擊他的那個人。因此他被擊中也不會感覺奇怪或者錯誤(誰讓你在空曠區域狂奔呢,活該)。當然,如果你開發的是一個坦克遊戲,或者在你的遊戲中玩家朝一個方向跑的時候可以看到別的方向,錯誤可能就會比較明顯,你可能發現玩家設計方向不對。
Conclusion
10.總結
Lag compensation is a tool to ameliorate the effects of latency on today's action games. The decision of whether to implement such a system rests with the game designer since the decision directly changes the feel of the game. For Half-Life, Team Fortress and Counter Strike, the benefits of lag compensation easily outweighed the inconsistencies noted above.
延遲補償是當前動作遊戲改善延遲影響的一種方法。是否採用這種方法取決於遊戲設計者,因為如何設計直接影響到遊戲的體驗。對於把那條命、軍團要塞、cs這樣的遊戲,延遲補償所帶來的效果提升顯著大於其帶來的錯誤。
Footnotes
腳註
[1]In the Half-Life engine, it is possible to ask the client-side prediction algorithm to account for some, but not all, of the latency in performing prediction. The user could control the amount of prediction by changing the value of the "pushlatency" console variable to the engine. This variable is a negative number indicating the maximum number of milliseconds of prediction to perform. If the number is greater (in the negative) than the user's current latency, then full prediction up to the current time occurs. In this case, the user feels zero latency in his or her movements. Based upon some erroneous superstition in the community, many users insisted that setting pushlatency to minus one-half of the current average latency was the proper setting. Of course, this would still leave the player's movements lagged (often described as if you are moving around on ice skates) by half of the user's latency. All of this confusion has brought us to the conclusion that full prediction should occur all of the time and that the pushlatency variable should be removed from the Half-Life engine.
【1】在半條命引擎中,預測的過程中允許一定的延遲,但不能容忍實際網路延遲這麼大的延遲。通過調整引數,我們可以控制預測過程中的延遲,這個引數pushlatency是一個負數,以毫秒為單位表示預測過程中的延遲。如果這個值大於(絕對值)實際網路延遲,這時預測就是完全的預測(譯註:客戶端伺服器完全同步)。這種情況下玩家感覺不到任何延遲。實際應用中,一些人錯誤地認為引數pushlatency應該設為實際網路延遲的一半,這種情況下玩家移動仍然有網路延遲一半的延遲(感覺類似於冰面移動)。基於這個原因,實際應用總應該總是採用完全預測,pushlatency這個變數應該從半條命引擎中移除
[2]http://www.quakeforge.net/files/q1source.zip (Return)
[3]A discussion of cheating and what developers can do to deter it is beyond the scope of this paper. (Return)
【3】關於作弊和反作弊的問題超出了本篇文章討論的範圍
[4]Though hybrids and corrective methods are also possible. (Return)
【4】雖然混合糾正方法也可以使用
[5]"Jerk" is a measure of how fast accelerative forces are changing. (Return)
【5】“jerk”用來度量使玩家改變加速度的作用的快慢
[6]It is assumed in this paper that the client clock is directly synchronized to the server clock modulo the latency of the connection. In other words, the server sends the client, in each update, the value of the server's clock and the client adopts that value
as its clock. Thus, the server and client clocks will always be matched, with the client running the same timing somewhat in the past (the amount in the past is equal to the client's current latency). Smoothing out discrepancies in the client clock can be
solved in various ways. (Return)
【6】本文假設計算連線延時的時候客戶端與伺服器完全同步,也就是說,每次更新的時候客戶端收到伺服器發過來的時間被直接當做客戶端的時間使用。這樣,客戶端跟伺服器完全匹配,只是客戶端稍微晚一點(晚多少取決於延時多少)。平滑客戶端時鐘差值可以有很多方法。
[7]The time spacing of these updates is not necessarily fixed. The reason why is that during high activity periods of the game (especially for users with lower bandwidth connections), it's quite possible that the game will want to send you more data than your
connection can accommodate. If we were on a fixed update interval, then you might have to wait an entire additional interval before the next packet would be sent to the client. However, this doesn't match available bandwidth effectively. Instead, the server,
after sending every packet to a player, determines when the next packet can be sent. This is a function of the user's bandwidth or "rate" setting and the number of updates requested per second. If the user asks for 20 updates per second, then it will be at
least 50 milliseconds before the next update packet can be sent. If the bandwidth choke is active (and the server is sufficiently high framerate), it could be 61, etc., milliseconds before the next packet gets sent. Thus, Half-Life packets can be somewhat
arbitrarily spaced. The simple move to latest goal interpolation schemes don't behave as well (think of the old anchor point for movement as being variable) under these conditions as the position history interpolation method (described below). (Return)
【7】更新時間間隔沒必要是固定的。因為對於劇烈運動的遊戲,如果頻寬不夠,很有可能客戶端發過來的資料超過了處理能力。如果採用固定更新間隔,在發完一個更新包以後就需要等待一個固定更新週期時間以後再發下一個包。這種邏輯不能很好地使用頻寬。因此,伺服器發給每個客戶端資料包以後,應該自己決定下一個包什麼時候發,決定的依據是使用者的頻寬、使用者設定的每秒更新頻率。如果使用者要求更新20次每秒,那麼需要等待50ms以後下個更新包才能傳送。如果激活了頻寬限制(而伺服器幀率又足夠高),我們可能就需要等待比如61ms(或其他值)以後傳送下一個更新包。因此,半條命遊戲資料包傳送間隔是隨機的。基於伺服器的這種情況,將啟動點作為一個變數,移動到最新目標點進行插值這種方法效果欠佳。
[8]Which Half-Life encodes in the lerp_msec field of the usercmd_t structure described previously. (Return)
【8】半條命程式碼中usercmd_t結構中變數lerp_msec前面描述過。
[9]For weapons that fire projectiles, lag compensation is more problematic. For instance, if the projectile lives autonomously on the server, then what time space should the projectile live in? Does every other player need to be "moved backward" every time
the projectile is ready to be simulated and moved by the server? If so, how far backward in time should the other players be moved? These are interesting questions to consider. In Half-Life, we avoided them; we simply don't lag compensate projectile objects
(that's not to say that we don't predict the sound of you firing the projectile on the client, just th