入力
はじめに
入力は、Quantumのコアアーキテクチャの重要なコンポーネントです。決定論的ネットワーキングライブラリでは、システムの出力は特定の入力に対して固定され、あらかじめ決まっています。つまり、ネットワーク内のすべてのクライアントで入力が同じであれば、出力も同じになります。
DSLでの定義
入力は、任意のDSLファイルに定義できます。例えば、移動方向と単一のジャンプボタンを持つ入力構造体は次のようになります:
C#
input
{
button Jump;
FPVector3 Direction;
}
サーバーは、すべてのティックセット(すべてのプレイヤーの入力)に対する入力確認をバッチ処理し、送信する責任があります。このため、この構造体はできるだけ最小限のサイズに保つべきです。
コマンド
決定論的コマンドはQuantumの別の入力経路であり、任意のデータとサイズを持つことができるため、「このアイテムを購入する」、「どこかにテレポートする」などの特別なタイプの入力に理想的です。
Unityでのポーリング
Quantumシミュレーションに入力を送信するには、Unity内でポーリングします。これを行うには、ゲームプレイシーンのMonoBehaviour内でPollInput
コールバックにサブスクライブします。
C#
private void OnEnable()
{
QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback));
}
次に、コールバック内で入力ソースから読み取り、入力構造体を populated します。
C#
public void PollInput(CallbackPollInput callback)
{
Quantum.Input i = new Quantum.Input();
var direction = new Vector3();
direction.x = UnityEngine.Input.GetAxisRaw("Horizontal");
direction.y = UnityEngine.Input.GetAxisRaw("Vertical");
i.Jump = UnityEngine.Input.GetKey(KeyCode.Space);
// convert to fixed point.
i.Direction = direction.ToFPVector3();
callback.SetInput(i, DeterministicInputFlags.Repeatable);
}
注意: ここでの浮動小数点から固定小数点への変換は、シミュレーションと共有される前に行われるため、決定論的です。
最適化
Quantumは3つのデルタ圧縮を行うものの、最適な帯域幅のために生のInput
データをできるだけコンパクトに保つことは一般的に良いプラクティスです。以下は、最適化するためのいくつかの方法です。
ボタン
キー入力を表現するためにブーリアンや類似のデータ型を使用するのではなく、Input DSL定義内でButton
型が使用されます。これは、インスタンスごとに1ビットしか使用しないため、可能な限り使用するのが好ましいからです。ネットワーク上では1ビットしか使用しませんが、ローカルではもう少し多くのゲーム状態を保持します。これは、1ビットが現在のフレーム中にボタンが押されたかどうかを表すものであり、残りの情報はローカルで計算されるためです。
ボタンは以下のように定義されます。
C#
input
{
button Jump;
}
Unityスクリプトからボタンの値をポーリングする際の重要な点は、現在のボタンの状態、つまり現在のフレームで押されているかどうかをポーリングすることです。これにより、Quantumは内部プロパティを自動的に設定し、ユーザーはシミュレーションコード内でWasPressed
、IsDown
、WasReleased
などの特定の状態をポーリングできるようになります。
つまり、UnityではGetKeyUp()
やGetKeyDown()
などの特定の状態を設定する必要はありません。これらを使用すると、UnityがQuantumと同じレートで実行されないため、いくつかの状態が失われ、入力がレスポンシブでなくなるため、問題を引き起こす可能性があります。
したがって、入力構造体のbutton
の値を設定する際は、以下のように常に現在のボタンの状態をポーリングすることを忘れないでください。
C#
// In Unity, when polling a player's input
input.Jump = UnityEngine.Input.GetKey(KeyCode.Space);
ボタンの状態はQuantumシミュレーションコードでも更新でき、ユーザーがInput構造体とbutton
型を使用してボットエンティティを更新することを選択した場合、ボットなどの非プレイヤーエンティティのボタンの変化をシミュレートするのに特に便利です。これを実現するには、以下のようにシミュレーションコードでボタンの状態を毎フレーム設定する必要があります:
C#
// In Quantum code
input.button.Update(frame, value);
このようにして、特定の状態(Pressed、Down、Released)も内部で生成されます。ボタンの状態を毎フレーム更新しないと、これらの状態が誤って設定されることになります。
エンコードされた方向
通常の設定では、移動は方向ベクトルで表されることが多く、通常は次のようにDSL
ファイルで定義されます。
C#
input
{
FPVector2 Direction;
}
しかし、FPVector2
は2つの'FP'から構成されており、16バイトのデータを占有します。これは、多くのクライアントが同じルームにいる場合、送信されるデータ量がかなり大きくなる可能性があります。このデータ量を最適化する方法の1つは、Input
構造体を拡張し、方向ベクトルを毎回フルベクトルとして送信するのではなく、Byte
にエンコードすることです。一例として、以下の実装を示します。
まず、通常通りに入力を定義しますが、方向のためにFPVector2
を含めるのではなく、エンコードされたバージョンを保存するためにByte
に置き換えます。
C#
input
{
Byte EncodedDirection;
}
次に、コンポーネントが拡張されるのと同じ方法で入力構造体を拡張します(参照: 機能の追加):
C#
namespace Quantum
{
partial struct Input
{
public FPVector2 Direction
{
get
{
if (EncodedDirection == default)
return default;
Int32 angle = ((Int32)EncodedDirection - 1) * 2;
return FPVector2.Rotate(FPVector2.Up, angle * FP.Deg2Rad);
}
set
{
if (value == default)
{
EncodedDirection = default;
return;
}
var angle = FPVector2.RadiansSigned(FPVector2.Up, value) * FP.Rad2Deg;
angle = (((angle + 360) % 360) / 2) + 1;
EncodedDirection = (Byte) (angle.AsInt);
}
}
}
}
この実装では、以前と同様の使用が可能ですが、16バイトではなく1バイトのみを占有します。これは、EncodedDirection
から値を自動的にエンコードおよびデコードするDirection
プロパティを利用することによって実現されています。