コマンド

Contents

コマンドの説明

コマンドは、サーバー権限でクライアントの予測をサポートすることを目的としたBolt内の完全に任意の構成要素です。 対応したくない場合は、ゲームでコマンドを使用する必要はありません。 実際には、Boltの最も簡単な実装 - 完全にクライアントの権限 - では、コマンドを使用する必要がないので、コマンドを使用する必要はありません。 また、完全にサーバー駆動のNPCのようなものは、クライアントが予測していないので、コマンドを使用する必要はありません。 Boltの状態を介して単純な変換同期を使用する必要があります。

コマンドを使用することは、ゲームオーバーフローをより複雑にしますので、広範囲に使用する前に完全に理解するべきです。 Advanced Tutorialでは、コマンドを使った簡単なキャラクターモーターを実装しています。 コマンドを理解せずに独自のモーターを使うことはお勧めできません。

Back To Top

予測

ローカルのエンティティ(通常はプレイヤー)は常に予測されています。 ローカルマシン上で瞬時に移動するのに、コマンドシステムを用いています。プレイヤーにとって即座にレスポンスがあることはとても大切なことです。 プレイヤーの入力は、コマンドの一部としてサーバーに送信されます。 サーバは同じ入力を再生しますが、その結果、ほとんどの場合、クライアントが予測した通りのシミュレーションが行われます。 サーバはその結果(最終的な位置や速度など)を特定のフレームでプレイヤーに返し、プレイヤーは基本的に位置やその他の状態をそのフレームのサーバに時間内に戻してリセットします(コマンド結果の「補正」)。次に、その時点から現在までの入力を再生して、予測した場所に戻るようにします。

Back To Top

オーソリティビティ

クライアントの予測された挙動にしたがって、サーバは状態権限というものを実行します。 サーバのシミュレーションが異なる場合、プレイヤーは異なる位置に行き着くことになります。 シミュレーションはサーバ上で権限を持って実行されており、プレイヤーは単にそのシミュレーションがどのようなものになるかを予測しようとします。 プレイヤーが自分の速度を非常に高速に設定しても、クライアントのシミュレーションはサーバとは全く関係がありません。

Tプレイヤーのプロキシ、つまり他のプレイヤーのボックス上のプレイヤーの表現は、通常、同期されたトランスフォームを使用して動作するためにBoltステートシステムを使用します。 制御しているプレイヤーは自分の動きを予測しているので、プレイヤーの Transform プロパティの Replication ModeEveryone but Controller に設定するとよいでしょう。自身のPlayerオブジェクトを変換してサーバーから同期しないようにするためです。 これが Everyone に設定されている場合、プレイヤーは自分の動きを予測しますが、予測されていない自分のプレイヤーの状態の同期もサーバから戻ってきます。これは本質的に予測値と衝突し、明らかなアーチファクトが表示されます。

クライアントが予測した、ネットワーク上のサーバー権限の動きモーターをテストする場合は、レイテンシーシミュレーションを有効にして、Boltが付属しているデフォルトなど、合理的な設定にすることをお勧めします。 これは、遅延が大きいほど、実装でより顕著な(そして明らかな)エラーが発生するためです。 シミュレーションを行わず、サーバをローカルにしている場合、正常に動作しているようでも、レイテンシーシミュレーションを有効にしたとたん、問題が発覚することがあります。

コマンドは、基本的には、コントローラからサーバへの送信速度で前後に送信されるネットワークストリームです(入力はサーバに、結果はサーバに返されます)。 これは、クライアントの予測にコマンドを使用していない場合でも有用な構成になります。

Back To Top

コマンドの定義

コマンドを作成したり変更したりするには、Bolt Assets ウィンドウで右クリックして New Command を作成するか、リストから選択して定義を編集します。 Bolt Editor ウィンドウでは、InputsResults を含めることができます。これらのフィールドは、クライアントから入力コマンドを送信したり、サーバから結果を返したりするのに使われます。

Command Definition
コマンドの定義

コマンドには、その動作を変更するためのいくつかの設定もあります。

  • Is Instant - このコマンドのキューに入っているすべての入力は、サーバに到達した後、次のフレームで即座に実行されます。
  • Enable Frame Limit - SimulateController() につき、キューに入れる入力は 1 つだけに制限されます。これにより、スピードハックを防ぐことができます。
  • 修正補間 - サーバから受信した結果を補間します。
  • Enable Delta Compression - ネットワークトラフィックと割り当てを削減しますが、その代わりに処理時間が若干増加します。常に結果を測定してください! 入力/結果は決してデルタ圧縮されないので、boolsのみで使用しないでください。

Back To Top

コマンドを使う

コマンドを使用するためのBolt APIは比較的小さいですが、コマンドを利用するためには、すべての可動部分を理解する必要があります。 新しい Bolt Entity を作成し、その状態を定義し、Bolt.EntityEventListener<[あなたの状態はこち]]> を継承するコンポーネントクラスを関連付けると、3つの主要なメソッドにアクセスすることができます。

  • public override void SimulateController(): はゲームから入力を集めてコマンドに変換するために利用します。SimulateController はフレームごとに1回ずつ実行されます。
  • public override void ExecuteCommand():エンティティの所有者とコントローラの両方で実行されるため、サーバーがスポーンし、それをクライアントに制御するプレイヤーキャラクターがある場合、 ExecuteCommandはサーバーと制御クライアントの両方で実行されます。 他のクライアントでは実行されません。
  • entity.QueueInput(cmd): 関数 SimulateController() の内部からこのメソッドを呼び出すと、引数として渡されたコマンドをクライアント上でローカルに実行したり、リモートで実行するためにサーバに送信したりすることができます。これにより、Bolt はクライアント側の予測を行うことができます: コマンドはサーバとクライアントの両方で実行されます。

サーバはコマンドを実行すると、そのコマンドの Result をそのコマンドを作成したクライアントに送り返し、クライアント上の特定のコマンドの状態をそれ自身の正しい状態で上書きします。

パラメータ resetState は、resetStateがtrueの場合に渡されたコマンドの Result にキャラクターモータの状態をリセットすることを要求します。 これはリモートコントロールクライアントでのみ発生し、サーバでは発生しません。 この処理は各フレームの最初に一度だけ行われ、渡されたコマンドはサーバから正しい Result を受け取ったコマンドになります。

resetStateを持つコマンドが実行された後、Boltは現在の状態に "追いつく "ために、リセットされたフレームから現在のフレームまで、クライアント上で他のすべてのコマンドを再び実行します。 これはフレームごとに行われます(これがシミュレーションレートです)。

よくある質問:クライアント上でプレイヤーのリセット状態のロジックをコメントアウトすると、なぜプレイヤーは高速に移動するのでしょう? その理由は、1つ1つのティックごとに、ボルトは、特定のフレーム上にあった場所に巻き戻します(リセット状態で)。次に、現在のフレームに、そのフレームからキューに入れられたコマンドをすべてリプレイします。 これは、ほとんどのシナリオでは、ティックの前と同じ位置に戻って配置する必要がありますが、新しいティックからの追加入力を使用します。 再生される入力は、同じネットワーク上のサーバーでプレイしている場合でも、一般的には少なくとも10以上のコマンドが入力されます。 リセット状態のロジックをコメントアウトすると、Boltは、最初に時間を遡って位置をリセットすることなく、10以上のコマンドを(前方を押していた場合は前方入力で)実行することになります。 つまり、1ティックあたり10個以上の「前方への移動」を実行することになります。そのため、動きが速くなります。これは完全にクライアント側の問題であり、サーバーはこの高速な動きを反映しません。 サーバーを完全に無視しているということになります。

Back To Top

コマンドで入力をキューに入れる

Boltのユーザーからよくある質問は、入力を正しくキューに入れる方法です。 多くのユーザーは、ワンショット入力が連続して何度も処理されていたり、場合によっては逆に入力を見逃してしまうことがあります。この理由は非常に単純ですが、UnityのUpdate / FixedUpdateがどのように動作するかを知る必要があります。 UnityはUpdateの最初に入力を収集し、Boltの入力キューイングはFixedUpdateで行われます。

Updateは1フレームに1回発生します。FixedUpdateは一定の間隔で起動します。 フレームレートが高い場合は、各FixedUpdateの間に複数のUpdatesを実行します。 フレームレートが低い場合、Unityは物理の目盛りを同期させるために、1つのフレーム内に複数の FixedUpdates を実行します。

次のシナリオを想像してみてください。何もないテストシーンをエディタで実行しています。このときのフレームレートは非常に高いです。 この場合、シミュレーションレートが 60(1 秒間に 60 回の物理ティック)で、フレームレートが 180 であったとします。これは、各ティックの間に 3 回の更新が発生することを意味します。

つまり、このシナリオでは、1回の固定更新で3回の更新が行われることになります。

Update      - collect input for jump == false
Update      - collect input for jump == true (you pressed the jump button)
Update      - collect input for jump == false
FixedUpdate - queue input (false)

この例では、入力を追跡するデータ構造体を持っているので、SimulateControllerでキューに入れることができます。 2回目の更新でジャンプボタンをクリックします。データ構造体はキューに入れる前の 3 回目の更新時にジャンプを false にリセットしているので、実際にゲーム上でジャンプはしません。 解決策は簡単で、ジャンプフラグをtrueに設定するだけです。入力をポーリングしている間は決してfalseにリセットしないでください。 代わりに、入力のキューイングが終了したときに、ワンショット入力をすべてリセットします。

もちろん、低フレームレートの状況では逆のことが起こる可能性があります。

Update        - input polled
FixedUpdate   - queue input
FixedUpdate   - queue same input (again)
FixedUpdate   - queue same input (again)

この場合、Boltの SimulateController の後にワンショット入力をクリアしないと、同じオンショット入力を3回連続でキューに入れてしまいます。

ドキュメントのトップへ戻る