入力
はじめに
入力は、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));
}
それから、コールバック内で入力ソースから読み出し、構造を投入します。
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);
}
注意:ここでの固定点コンバージョンへの浮動点は、シミュレーションに共有される前に実行されるため決定性となります。
最適化
一般的に、ゲームで対応できるプレイヤー数を最大にするには、Input
定義が消費する帯域幅ができる限り小さくなるようにすることがベストプラクティスです。Below are a few ways to optimize it.
ボタン
キー入力の表現にブーリアンや同様のデータタイプを使用する代わりに、Input DSL定義内でButton
タイプを使用しています。これはインスタンスごとに1ビットしか使用していないからで、可能である箇所に使用するのが望ましいです。ネットワーク上で1ビットしか使用しないのですが、ローカルではもう少し多くのゲームステートを含みます。これは単一のビットが表現するのが、現在のフレームでボタンが押されたか押されてないかのみで残りの情報はローカルでコンピューティングしているからです。
ボタンの定義は以下の通りです。
C#
input
{
button Jump;
}
コード化した方向
一般的な設定では、移動は方向ベクトルを使用して表現する場合があり、以下のようにDSL
ファイル内で定義します。:
C#
input
{
FPVector2 Direction;
}
ただし、FPVector2
は2つの'FP'で構成されています。これは16バイトのデータを取り扱い、特に同一のルーム内に多くのクライアントがいる場合、大量のデータを送信します。この最適化の1つは、毎回ベクトル全体を送信するのではなく、Input
構造を拡張し方向のベクトルをByte
にコード化するという方法です。この実装の一つの例が以下です。
まず、入力を通常通りに定義します。ただし、方向にFPVector2
を含める代わりにByte
で代用し、ここにコード化したバージョンを保管します。
C#
input
{
Byte EncodedDirection;
}
次に、コンポーネントの拡張と同じ方法で入力構造を拡張します(参照:Adding Functionality))。
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バイトの使用で済みます。これはDirection
プロパティを使用して実現しており、EncodedDirection
からの値を自動的にコード化したりデコードしたりしています。