【翻譯搬運】起源引擎 C/S延遲補償方法在遊戲協議中的設計與優化【一】

寫在前面
繼翻譯搬運起源引擎(Source)的客戶端服務器同步之後,搬運了這篇
Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization
原文地址是:https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization
基本講分割成兩篇翻譯。

渣翻和錯別字也請各位諒解。

【2018-03-12補充】 對格式進行了整理,逐句的方式太影響閱讀體驗了,內容和原來還是相同的。


正文


索引

1 概述 Overview(本篇)
2 客戶端/服務器遊戲的基本體系結構 Basic Architecture of a Client / Server Game (本篇)
3 用戶輸入消息內容 Contents of the User Input messages(本篇)
4 客戶端一側預測 Client Side Prediction (本篇)
5 Client-Side Prediction of Weapon Firing
6 Umm, This is a Lot of Work
7 Display of Targets
8 Lag Compensation
9 Game Design Implications of Lag Compensation
10 Conclusion
11 附註 Footnotes(分散於各篇最後)


概述 Overview

Paragraph 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遊戲要求開發者支持非常多種的用戶設置。而且普遍的情況是,玩家的運行環境往往低於現階段最先進的計算機硬件。他們的網絡連接情況也是同樣。

Paragraph 2

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 in 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.

雖然寬帶可以作爲目前的網絡遊戲問題解決的靈丹妙藥,但是寬帶這種解決方案並不容許開發者忽略遊戲設計過程中的網絡延遲和其他網絡問題。 寬帶在美國被真正普及還需要一段時間,那麼在其他的客戶端存在的地方,就要花更長時間。 【額外說明】文章比較久遠,所以這段描述可以忽略。 此外,還有很多糟糕的寬帶情況,比如用戶可能有時候擁有高帶寬,而更經常出現的情況是在連接中出現嚴重延遲和丟包的情況。

Paragraph 3

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

Paragraph 1

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.

目前玩到的大多數動作網絡遊戲都是客戶端/服務器遊戲。例如半條命,以及CS、軍團要塞這些Mod,都是運行在這樣的體系中,不管他們是基於Quake3引擎還是虛擬競技場引擎。在這些遊戲中,都有一個核心服務器負責遊戲邏輯的運行。他連接着一個或者很多個“沉默的”客戶端。最初,這些客戶端只是將用戶的輸入進行採樣並且轉發給服務器進行演算。服務器執行輸入指令,移動對象,並且向客戶端返回一個用來渲染的對象的列表。當然,真實的世界系統擁有更多的組件,但是這樣簡化,有利於我們理解預測和滯後補償。

Paragraph 2

With this in mind, the typical client / server game engine architecture generally looks like this:
這裏寫圖片描述

在這種想法之下,典型的客戶端/服務器引擎架構看起來如下圖所示:
這裏寫圖片描述

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
2. Sample user input (mouse, keyboard, joystick)
3. Package up and send movement command using simulation time
4. Read any packets from the server from the network system
5. Use packets to determine visible objects and their state
6. Render Scene
7. Sample clock to find end time
8. End time minus start time is the simulation time for the next frame

在本篇中,有關服務器和客戶端建立連接的通訊和同步我們都忽略處理,那麼客戶端的幀循環如下所示:
1. 採樣時鐘尋找開始時間點
2. 對玩家的輸入信息採樣(鼠標、鍵盤、搖桿)
3. 對模擬時間段採集的信息進行進行打包,發送活動命令
4. 從網絡系統模塊中讀取服務器發送來的數據包
5. 使用數據包中的數據決定對象的狀態
6. 渲染場景
7. 採樣時鐘尋找結束時間點
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…).
每次客戶端做完這個完整的循環,“幀時長”用作確認下一幀需要模擬多少內容,如果你的幀率固定不變,那麼幀時長會相對固定。另外,就算幀時長不固定,仍舊不會影響這個循環(至少你能在下一幀循環前確切的知道他需要的循環時長是多久)

The server has a somewhat similar loop:
1. Sample clock to find start time
2. Read client user input messages from network
3. Execute client user input messages
4. Simulate server-controlled objects using simulation time from last full pass
5. For each connected client, package up visible objects/world state and send to client
6. Sample clock to find end time
7. End time minus start time is the simulation time for the next frame

服務器的循環類似於下文:
1. 採樣時鐘尋找起始時間點
2. 從網絡中讀取客戶端發來的輸入信息
3. 執行客戶端輸入的信息
4. 使用上一次循環的時間,模擬服務器控制下的對象狀態。
5. 對象狀態、世界狀態打包,發送給每一個連接中的客戶端
6. 採樣時鐘尋找結束時間點
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

Paragraph 1

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:

在遊戲《半條命》中,玩家的輸入消息格式非常簡單,他被封裝在一個只有幾個基本字段的數據結構中。

typedef struct usercmd_s
{
    // Interpolation time on client  客戶端插值時間(關於插值時間,請參照多人網絡同步模型的相關解釋)
    short       lerp_msec;   
    // Duration in ms of command  命令時間
    byte        msec;      
    // Command view angles.  命令時刻視角角度
    vec3_t  viewangles;   
    // intended velocities
    // Forward velocity.  前向速度
    float       forwardmove;  
    // Sideways velocity.  橫向速度
    float       sidemove;    
    // Upward velocity.  向上的速度
    float       upmove;   
    // 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,buttonmsec 的值等於命令產生的毫秒數(時間是指代幀時間)。viewangles 的值等於在這幀中玩家的視角方向。forward、side、upmove 的值是通過檢測鍵盤、鼠標和搖桿是否有移動或按下得到的脈衝值。最後,button 的值是一個位字段,使用一個或者多個位來表示每一個按鍵是否被按下。

Paragraph 2

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,客戶端對行爲結果產生認知同樣需要500ms。在本地網絡之中(LAN)往返的延遲是可以接受的,但是放到互聯網之中,就完全不可接受了。


客戶端一側預測 Client Side Prediction

Paragraph 1

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.

一個解決問題的方法,是在客戶端本地展現移動,這樣,服務器接收時將直接承認客戶端命令。這種方法被稱作客戶端預測。

Paragraph 2

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.

在客戶端進行移動的預測,要求我們放棄客戶端“最小”和“沉默”爲條件。這並不意味着客戶端要像是沒有中央服務器的點對點遊戲一樣,有着對模擬的完全控制權。這裏依然有一個服務器如前文所述模擬遊戲的運行。擁有這樣一臺服務器意味着,當客戶端和服務器模擬出不同的結果時,會以服務器的結果來糾正客戶端的運算和模擬。因爲在連接時存在的延遲,直到一個完整的RTT時間走完纔會發生修正。這樣的缺點在於,糾正客戶端“過去”的預測錯誤時會引起一個玩家位置的明顯變化。

Paragraph 3

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.

實現客戶端的運動預測,程序中需要實現下面所述的步驟。如同前文所述,客戶端採集用戶指令,並且發送給服務器。而現在每一條用戶指令(以及生成的具體時間)都被存儲在客戶端。預測運算的時候將會使用這些用戶指令。

Paragraph 4

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.

“預測”將會把從服務器獲取的最後一次動作作爲起始點。這將會告訴我們在服務器端哪個用戶指令已經被執行,也會告訴我們這次指令執行過後,玩家當前的確切位置(和其他狀態數據)。連接過程中存在延遲,上一個發來的命令將會是在過去的某個時間點。舉例來說,如果客戶端每秒運行50幀(FPS),並且延遲爲100ms(往返),客戶端從服務器得到的最後命令,會存儲5個用戶命令。這五個用戶命令將會參與客戶端的預測。假設要做一個完整的“預測(注1)”【額外說明】見本篇最後的“腳註”部分,客戶端將會把上一次從服務器接收的數據作爲開始,用與服務器“相似的邏輯”運算5個用戶命令。運算這些用戶命令將會在客戶端產生一個最終的狀態(其中最主要的是玩家的最終位置),這個狀態可以用來確定當前幀的玩家在場景的哪個位置渲染。

Paragraph 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:

在遊戲《半條命》中,爲了最小化客戶端和服務器在預測這部分產生的不同,兩端共享了玩家移動這一部分的遊戲代碼。這些在HL_SDK中的 pm_shared(即“玩家共享移動”)/文件夾下。共享代碼部分的輸入是用戶指令和用戶狀態的封裝,輸出結果是經過用戶指令影響的新用戶狀態。客戶端上的大致運算如下所示

"from state" <- state after last user command acknowledged by the server;
"from state" <- 從服務器接收的“最後一條”用戶指令得到的狀態;

"command" <- first command after last user command acknowledged by server;
"command" <- 服務器的“最後一條”用戶指令後產生的第一條用戶指令;

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";
};

Paragraph 6

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”.

在預測結果“to state”中的位置和其他狀態將用作場景得渲染根據。指令的運行是將所有的玩家狀態數據拷貝到共享數據結構中、指令執行(《半條命》中執行的代碼在pm_shared)、運算出的結果拷貝到“to state”中。

Paragraph 7

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.

這裏有些針對這個系統的重要附加說明。首先,我們會注意到,由於客戶端的延遲,不論客戶端生成用戶指令的速度多塊(比如:幀率),客戶端將會在收到服務器確認執行某條指令執行之前,在未被確認執行的命令隊列(《半條命》的滑動窗口)中重複運行。首先要考慮的是渲染代碼部分中聲音和特效,因爲指令要反覆的被執行,不要重複創建類似腳步聲這樣的東西,舊的命令重複運行只用來更新預測位置。 另外,服務器也不要發送客戶端已經進行預測的特效。 然而這種情況下,客戶端仍舊需要重複運算舊的命令,否則服務器無法糾正客戶端出現的預測錯誤。對於這個問題的解決方法,客戶端對未進行預測的命令進行標記, 在這個命令第一次被執行的時候播放特效。

Paragraph 8

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”.

另一個需要注意的地方是,客戶端存儲的狀態數據,並不單純是從服務器下發數據的一個子集。如果你沒有服務器下發數據以外的數據,那麼你可以直接使用上一次服務器通知的起始點的狀態數據,用戶指令的預測在這種情況下計算出最終狀態(包括即將渲染的角色位置)這樣,就不需要保留從服務器通知最後的狀態到當前時間之間的中間計算結果。然而,如果你正在做一些純客戶端的邏輯(比如在蹲的過程中,視線的位置——這其實並不是一個純客戶端邏輯,服務器也將進行模擬),那麼從服務器取回的玩家狀態數據不會影響這一部分的表現,直接使用預測結果即可。這可以通過一個滑動窗口來完成,從“from state”開始,每次對一條用戶指令進行預測計算,在窗口中可以用他對下一個狀態進行填充。當服務器的消息執行了一條或者數條已經進行過預測的用戶命令發回消息。可以明顯的看出哪些數據是從服務器的下發狀態拷貝的,哪些數據是從純客戶端演算或者從“from state”中獲得的。

Paragraph 8

So far, the above procedure describes how to accomplish client side prediction of movements. This system is similar to the system used in QuakeWorld.

到目前爲止,上述過程描述瞭如何完成運動的客戶端預測。這套系統與在QuakeWorld中所使用的相似。


附註 Footnotes

prediction : 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.

預測:在《半條命》的引擎中,可以要求客戶端的方面在預測的時候只承擔一部分延遲上的計算,而不是完成所有。用戶可以在控制檯改變“pushlatency”的值來控制預測的量。這個變量的值是一個負數,用來表示要執行預測的最大毫秒數。如果這個值大於(取反後)當前的延遲,預測將會調整在當前時間。如果是這樣,那麼用戶將會在移動過程中覺得自己是0延遲的。某些羣體中有一種錯誤的迷信,堅定的認爲將pushlatency的值設置爲實際平均延遲的一半最爲恰當。當然,這種設置,會讓這個遊戲世界中玩家的移動依舊有延遲(經常被描述成滑冰一樣的移動)。這些困惑是得我們得出了這樣的結論,完全的預測應該貫穿遊戲全程,pushlatency這個變量也應該從《半條命》的引擎中移除


寫在後面

後續一篇將不定期更新

歡迎糾錯

轉載請註明,出自喵喵丸的博客 http://blog.csdn.net/u011643833/article/details/77989770

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章