Frames
概述
Quantum的預測——復原結構允許降低延遲。Quantum總是復原及重新模擬幀。它針對確定性而言是必要的,並且它涉及伺服器對玩家輸入的驗證。當伺服器已經確認玩家輸入或已經覆寫/替換它(只在輸入並沒有準時到達伺服器的情況),針對一個給定幀的所有玩家的已驗證輸入將被發送到客戶端。當收到已驗證輸入,最後的已驗證幀將使用已確認的輸入來向前推進。
請注意: 一個玩家的自己的輸入如果沒有準時到達伺服器或無法被驗證,則將被復原。
幀類型
Quantum區分兩種幀類型:
- 已驗證;以及,
- 已預測。
已驗證
一個 已驗證 幀是一個 受信任的 模擬幀。一個已驗證幀確保是確定性的,並且在所有客戶端模擬上都完全相同。已驗證模擬只在收到由伺服器確認的輸入之後,模擬下一個已驗證幀;因此,它與來自伺服器的RTT/2等比例地向前移動。
如果以下兩個條件都為真,則一個幀是已驗證:
- 針對這個刷新,來自 所有 玩家的輸入由伺服器所確認;以及,
- 所有它所遵循的先前的刷新都被驗證。
來自玩家的 僅僅一個子集 的輸入已經被伺服器驗證的,一個部分刷新確認,將不會導致一個已驗證刷新/幀。
已預測
相對於 已驗證 幀,已預測 幀並不要求由伺服器確認的輸入。這意味著只要模擬已經在本機遊戲階段累積足夠的差量時間,已預測幀以預測來向前推進。
Unity側的API提供存取到各種已預測幀的版本,請參見以下的API說明。
Predicted
:模擬「前端」,基於已同步時鐘。PredictedPrevious
(已預測 - 1):用於主要時鐘鋸齒化內插補點(多數檢視將使用這個以保持平順,因為Unity的本機時鐘可能會與主要伺服器時鐘之間有輕微的漂移。Quantum從一個分離的時鐘來運行,其時鐘與伺服器時鐘同步——平順地被校正)。PreviousUpdatePredicted
:這正是上次調用Session.Update
(附有「已校正」的資料在其中)的「已預測/前端」的幀。用於錯誤校正內插補點(大多數時間下沒有錯誤)。
API
已驗證 及 已預測 幀的概念存在於模擬及檢視之中,雖然它們有一個稍微不同的API。
模擬
在模擬中,可以透過Frame
類別來存取目前的已模擬幀的狀態。
方法 | 傳回值 | 說明 |
---|---|---|
被驗證 | 布林值 | 如果幀在所有客戶端上是確定性的,並且使用由伺服器確認的輸入,則傳回真。 |
被預測 | 布林值 | 如果幀是一個本機已預測幀,則傳回真。R |
檢視
在檢視中,已驗證 及 已預測 幀透過QuantumRunner.Default.Game.Frames
來成為可用。
方法 | 說明 |
---|---|
已驗證 | 受信任的模擬幀,在所有客戶端上都相同。 |
已預測 | 本機模擬「前端」,基於已同步的Quantum時鐘。在客戶端之間可能不同。 |
已預測先前 | 已預測 - 1 用於主要時鐘鋸齒化內插補點,大多數檢視將使用這個以保持平順。因為Unity的本機時鐘與主要伺服器時鐘之間可能有輕微的漂移。Quantum從一個分離的時鐘來運行,其時鐘與伺服器時鐘同步——平順地被校正 |
先前更新已預測 | 幀的重新模擬版本,其在最後一次調用Session.Update時,已是「已預測/前端」的幀。在復原的情形,這是必要的,是為了「校正」由它所持有的資料。它被檢視使用,在內插補點中來進行錯誤校正——這是一種安全措施,且很少有必要這樣做。 |
使用Frame.User
您可以透過新增資料到Frame.User.cs
來擴展幀。然而,這樣做的話,您也必須要執行相對應的幀所使用的初始化、配置及序列化方法。
C#
partial void InitUser() // Initialize the Data
partial void SerializeUser(FrameSerializer serializer) // De/Serialize the Data
partial void CopyFromUser(Frame frame) // Copy to next Frame
partial void AllocUser() // Allocate space
partial void FreeUser() // Free allocated space
請注意:新增過量的資料到幀,將影響性能(序列化/取消序列化),也影響延遲加入。
實例
這是一個非常簡單的實例,其並不需要手動記憶體配置。
C#
using System;
namespace Quantum {
unsafe partial class Frame {
public byte[] Grid => _grid;
private byte[] _grid;
partial void InitUser() {
_grid = new byte[RuntimeConfig.GridSize];
}
partial void SerializeUser(FrameSerializer serializer)
{
serializer.Stream.SerializeArrayLength<Byte>(ref _grid);
for (int i = 0; i < Grid.Length; i++)
{
serializer.Stream.Serialize(ref Grid[i]);
}
}
partial void CopyFromUser(Frame frame)
{
Array.Copy(frame._grid, _grid, frame._grid.Length);
}
}
}
Back to top