This document is about: QUANTUM 2
SWITCH TO

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