This document is about: V3 CLIENT SERVER
SWITCH TO

予測と入力

概要

クライアントサーバー型では、シミュレーションサーバー(ホストまたは専用サーバー)がゲームロジックを実行し、すべての状態変化を検証します。クライアントは直接プロパティを書き込みません。そのかわりに、入力をバッファに格納し、シミュレーションサーバーに送信し、結果をローカルで予測します。シミュレーションサーバーは処理を実行し、正しい状態を書き込みます。状態の配信・関心領域フィルタリング・途中参加者用スナップショットは引き続きPhoton Cloudが担当し、シミュレーションサーバーはゲームプレイ処理に専念します。クライアントの予測と正しい状態にズレが生じた場合には補正が適用されます。

メンタルモデル

共有権限型の考え方は、各クライアントが「自分自身が所有しているオブジェクトの状態を直接書き込む」というものです。その一方でクライアントサーバー型の考え方は、各クライアントが「入力を提案して、サーバーが結果を決定し、その応答があるまで結果をローカルで予測する」というものです。サーバーの結果が常に絶対の真実であり、クライアントの状態はいつでも修正される可能性がある予測にすぎません。

FusionServerReplicator

FusionServerReplicatorは、ネットワークオブジェクトのプロパティ同期とクライアントサーバー型の入力ループ(カスタムプロパティ・スムージング・関心領域・ルートの複製モード・入力キューイングと実行・入力権限の割り当て・予測シグナルなど)を管理します。クライアントサーバー側の予測フローに関連するあらゆるオブジェクトで使用できます。

PLAYER_PREDICTED 所有権モード

インスペクター上で、複製ノードのowner_modePLAYER_PREDICTEDに設定します。このモードでは、入力権限をset_input_authority()で割り当て、サーバーが正しい状態を決定する間、入力権限を持つクライアントがローカルで予測を行うフローが有効になります。spawner.spawn()pre_spawn_functionコールバックを渡して、スポーンしたシーンの_ready()が実行される前に権限が割り当てることで、_ready()内でその割り当てを確認することができます。

入力ループ

入力権限API

入力権限によって、オブジェクトへの入力を送信するクライアントが決定されます。これは状態権限(常にサーバーに属する)とは別です。オブジェクトのスポーン後、マスタークライアントはリクエストを送信したプレイヤーに入力権限を割り当てます。

  • has_input_authority() - このオブジェクトの入力を送信するクライアントならtrue
  • has_authority() - サーバー/ホスト(状態権限者)ならtrue
  • get_input_authority() - 入力権限を持つプレイヤーIDを返す
  • set_input_authority(player_id) - 入力権限を割り当てる(マスタークライアントのみ)

ホスト(クライアント兼サーバーとして振る舞うプレイヤー)では、すべてオブジェクトのhas_authority()trueとなり、has_input_authority()もホスト自身のキャラクターに対してtrueとなります。これらフラグによる分岐コードを記述する際には、役割を二重に持つケースを適切に処理できるロジックを組んでください。

入力ペイロードの格納

フレームごとに、入力権限を持つクライアントは、プレイヤーの入力を読み取ってPackedByteArrayにエンコードします。このバッファはqueue_input(delta, buffer)に渡され、そこからサーバーへ送信されます。バッファのフォーマットは自由なので、ゲームに必要な情報(方向・アクション・ティックカウンターなど)をエンコードしてください。

GDScript

func _create_input() -> PackedByteArray:
    var dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    var buf = PackedByteArray()
    buf.resize(12)
    buf.encode_float(0, dir.x)
    buf.encode_float(4, dir.y)
    buf.encode_u32(8, _tick)
    _tick += 1
    return buf

キューと実行フロー

フレーム(または物理フレーム)ごとに、すべてのピア上でprocess_input_queue(delta)を呼び出します。入力権限を持つクライアントでは、この関数によってローカルにキューイングされた入力(予測)を含むon_process_inputを発火します。サーバー側では、入力権限を持つクライアントから受信した入力(正しい値)を含むon_process_inputが発火します。リモートプロキシ(サーバーでも入力権限者でもないクライアント)側では、process_input_queue()では何もせず、レプリケーションによってオブジェクトの状態を受信します。つまり、移動ロジックをon_process_inputコールバック内に一度記述するだけで、クライアント(予測)とサーバーの両方でロジックが実行されます。

GDScript

func _physics_process(delta):
    if replicator.has_input_authority():
        replicator.queue_input(delta, _create_input())
    replicator.process_input_queue(delta)

func process_input(tick: int, delta_time: float, payload: PackedByteArray, is_new: bool):
    var dir_x = payload.decode_float(0)
    var dir_z = payload.decode_float(4)
    velocity = Vector3(dir_x, 0.0, dir_z) * SPEED
    velocity.y += GRAVITY * delta_time
    move_and_slide()

シグナル

  • on_process_input(tick: int, delta_time: float, payload: PackedByteArray, is_new: bool) - process_input_queue()によって発火し、キュー内の次の入力が渡されます。is_newは、その入力が初めて実行される際に(クライアントとサーバーの両方で)trueになり、再シミュレーション中はfalseになります。再シミュレーションは、予測リセット後にクライアントがバッファされた入力を再実行し、予測状態を再構築する際に発生します。単発の効果(サウンド・パーティクル・UI更新)では、重複しないようにis_newの条件付きで実行するようにしてください。
  • state_reset(info: FusionStateResetInfo) - ネットワークによって複製ノードの状態が上書きされるたびに発火します。info.reason == FusionStateResetInfo.REASON_PREDICTION_RESETの場合は、サーバーの正しい状態とクライアントの予測が異なっていて、修正が適用されたことを意味します。

予測リセット

サーバーが入力を処理し、クライアントの予測とは異なる状態になった場合、info.reason == REASON_PREDICTION_RESETstate_resetシグナルが発火し、クライアントの複製されたプロパティはサーバーの状態と一致するように修正されます。修正によって生じる視覚的/音響的アーティファクトを処理する必要がある(例:パーティクルエフェクトを修正後に位置に移動させる)場合は、このシグナルで(reasonで条件分岐して)処理を行ってください。

予測リセット時に修正されるのは、複製されたプロパティのみです。ローカル変数(アニメーション進行・ジャンプカウンター・クールダウンタイマーなどの複製されていない状態)はロールバックされません。複製された状態と一貫性を保つべきローカル変数に依存したゲームプレイロジックがある場合は、info.reason == REASON_PREDICTION_RESET時のstate_resetで手動リセットを行う必要があります。

キューの監視

サーバーサイドの入力キューを監視することで、スタベーション(キューが空になり処理が停滞する状態)や肥大化(キューが増えすぎてレイテンシが増大する状態)を検知できます。

  • get_input_queue_count() - サーバーに現在キューイングされている入力数
  • get_input_queue_delta_time() - 入力が最後にキューに登録されてからの経過時間

これらは開発中の診断ラベルとして役立ちます。デモプロジェクトでは、0.5秒間隔のキューの最小値/現在値/最大値を表示します。

デバッグのヒント

  • 権限をカラーコードで表示:オブジェクトの色をhas_input_authority()(緑)、has_authority()(水色)、どちらでもない(白)に設定して、誰が何を所有しているかを一目で確認できるようにしましょう。
  • キュー情報の表示get_input_queue_count()get_input_queue_delta_time()をラベルで表示し、キューのスタベーションや肥大化を早期に検知しましょう。
  • ティック値の監視:入力バッファにティックカウンターを追加して、ティックがスキップされた(つまり入力抜けが発生した)際にログを残しましょう。
  • Network Inspector:組み込みのFusion Networkデバッガー(デバッガー > Fusion Network)を使用して、オブジェクトごとの帯域幅・RTT・権限・ワード数などをリアルタイムで確認しましょう。
Back to top