This document is about: V3 CLIENT SERVER
SWITCH TO

プロパティの同期

マルチプレイヤーゲームでは、各クライアントがそれぞれシミュレーションをコピーして実行します。レプリケーションとは、それらコピーの一貫性を保つ仕組みです。権限者(共有権限型ならクライアント、クライアントサーバー型ならシミュレーションサーバー)がオブジェクトのプロパティの正しい値を書き込むと、Photon Cloudはそれを他すべてのクライアントへ配信します。レプリケーションが行われなければ、プレイヤーごとに異なるゲーム画面が表示されてしまうでしょう。

Fusionはプロパティ単位でレプリケーションを行います。個別の値(位置・体力・スコアなど)は独立して追跡されます。サーバーは、前回更新以降に変更されたプロパティのみを送信するため、帯域は最小限に抑えられます。

複製ノード

FusionServerReplicatorは、ネットワークオブジェクトのプロパティ同期に加えて、クライアントサーバー型の入力ループのための入力キューイングと予測を処理します。これをネットワークシーンのルートノードの子に追加してください。シミュレーションサーバーは正しい値をサーバーに書き込み、クライアントはローカルで予測を行いながら受信した値を補正します。このページで説明されているすべてのプロパティ同期機能が適用されます。

Root Replication Mode

Replication Modeによって、ルートノード型に基づいて、どのプロパティを自動的に同期するかが制御されます。

  • REPLICATION_NONE - 自動同期無効:FusionReplicatorの下部パネルから手動でカスタムプロパティを追加してください
  • REPLICATION_AUTO - ルートノード型を自動的に検知して、以下のプロパティを同期します:
  • RigidBody2D/3D → position, rotation, linear_velocity, angular_velocity (forecast smoothing)
  • CharacterBody2D/3D → position, rotation, velocity (interpolation smoothing)
  • Other nodes → position, rotation (interpolation smoothing)

Smoothing

root_interpolation_modeによって、ルートノードにリモートの値がどのように適用されるかが制御されます。
エディター上では、型に応じたペアが表示されます。物理オブジェクトルート(Rigidbody2D/3D)の場合はNone/Forecast、それ以外のルートの場合はNone/Exponentialです。
Exponentialは、指数関数的な減衰によって受信した値をローカルノードにブレンドします。Forecastは、最新の速度から予測を行って受信した値を補正します。Noneは、受信した値を直接書き込みます。
object_interpolation_time(デフォルト0.150秒)は減衰定数で、Exponentialルートモードや、プロパティごとの補間で使用されます。

カスタムプロパティ

カスタムプロパティは、エディター上で複製ノードの下部パネルから直接追加できます。ここには、Replication Modeで自動同期されないゲームデータ(体力・スコア・名前など)を追加してください。Transformや物理プロパティは、REPLICATION_AUTOによって処理されるため、ここには追加しないでください

サポートされる型: bool, int, float, String, Vector2, Vector3, Vector4, Quaternion, Color, Node references, PackedByteArray, PackedFloat32Array, PackedInt32Array, PackedVector2Array.

未サポート(オブジェクトと一般的な配列の複製はサポート予定):

  • カスタムクラスインスタンス(例:ノードのプロパティとして保持される内部クラス)
  • 一般的な型指定なしのArray[]):現時点ではPacked*Array型のみが複製されます
  • Dictionary:かわりに個別のプロパティまたはPacked*Arrayを使用してください

デフォルトでは、カスタムプロパティは受信した値にスナップします。
各レプリケーション設定項目のInterpolate列から、プロパティごとの補間を個別に有効化できます。詳細はプロパティごとの補間をご覧ください。

カスタムプロパティパネル
カスタムプロパティパネル

プロパティパス構文

プロパティのパスは、複製ノードのルートノードを基準としたnode:property構文を使用します。

  • :property - ルートノードのプロパティ
  • child:property - 子ノードのプロパティ
  • child/grandchild:property - 孫ノードのプロパティ

GDScript

":health"             # ルートノード
"Sprite2D:modulate"   # 子ノードのSprite2D

文字列と配列

String(文字列)は2ワード(StringHeap handle + generation)を使用します。値が変更されると、handleは自動的に解放されます。

Array(配列:PackedFloat32ArrayPackedInt32ArrayPackedVector2Arrayなど)は、生成時に固定max_capacityを設定する必要があります。

実行時に配列長がmax_capacityを超えた場合、余分な要素はレプリケーション時に自動的に削除されます。リモートクライアントには、切り詰められた配列がエラーなしで送信されます。そのため、配列サイズmax_capacityはゲームで想定される最大ケースに合わせて設定してください。

ノードの参照

NodeObject)型のプロパティによって、他のネットワークオブジェクトのルートノードを参照できます。通信上、各参照はFusionのObjectId(2ワード:Origin + Counter)として保存されます。

参照するノードは、FusionReplicatorの親ノードであるネットワークオブジェクトのルートである必要があります。これには、スポーンされたオブジェクトのルート・サブオブジェクトのルート・シーンオブジェクトのルートが含まれます。ネットワークオブジェクトの子ノードは参照できません。ネットワークオブジェクトでないノードやnullは、どちらも(0, 0)としてシリアライズされ、リモートではnullとして解決されます。

GDScript

# 権限者が別のネットワークオブジェクトノードの参照を設定する
partner = get_node("/root/Main/Player2")

# リモートはnullチェック必須 - 対象が関心領域外または破棄されていた場合、参照はnullで解決される
if partner:
    print(partner.name)  # "Player2"

解決ルール

  • 参照ノードが、リモートクライアントの関心領域内にある場合、ローカルノードとして解決されます。
  • 参照ノードが、関心領域外にある・破棄されている・まだスポーンしていない場合、nullとして解決されます。
  • 権限者上で、プロパティにnullまたはネットワークオブジェクトでないノードを設定すると、すべてのリモートにnullが送信されます。
  • Fusion.load_scene()経由でロードされたシーンオブジェクトも同様に動作します。リモートクライアント上でシーンの読み込みが完了すると、参照が解決されます。

プロパティごとの補間

FusionReplicationConfigの各要素には、3つの値を含むInterpolate列があります。
Interpolate/Interpolate Angleに設定されている場合、受信した値はリモートクライアント上で車道ターゲットとして保持され、即座に書き込まれるのではなくフレームごとに適用されます。

動作
None デフォルト値。受信した値はノードに直接書き込まれます。
Interpolate 数値型(FLOAT, VECTOR2, VECTOR3, QUATERNION)の場合、ローカル値はobject_interpolation_timeの間に指数関数的な減衰を使用してフレームごとに目標値へブレンドされます。その他サポートされている型(BOOL, INT, STRING, packed arrays など)の場合、一時待機処理が行われます。ネットワーク時間がobject_interpolation_time / 2経過するまで受信した値が保持され、その後に適用されます。
Interpolate Angle Interpolateと動作は同じですが、FLOAT型はMath::lerp_angleを使用し、VECTOR3型は軸ごとに(ベクトルをラジアン単位のオイラー角として扱って)lerp_angleを適用します。その他の型はInterpolateと同じ動作です。

プロパティごとの補間は、ルートのスムージングとは分けられています。
ルートがネットワーク同期しているか、root_interpolation_modeNone/Exponential/Forecastのいずれかであるかにかかわらず補間が実行されます。
権限者は遅延なしでローカルに書き込み、それ以外がシャドウターゲットへの補間を行います。
スポーン時・関心領域への再進入時・テレポート時は、すべてのシャドウターゲットをノードに直接スナップした後、補間が再開されます。

指数関数的減衰関数は、Fusionシングルトンから手動で利用できるようになっています。プロパティのInterpolateNoneに設定したまま、ローカルコピーを独自にスムージングする(例:複製されたステートからUI側に表示する値を求める)場合に便利です。

GDScript

var smoothed_health: float = 0.0
var smoothed_yaw: float = 0.0

func _process(delta: float) -> void:
    smoothed_health = Fusion.interpolate_exponential(smoothed_health, health, 0.15, delta)
    smoothed_yaw    = Fusion.interpolate_exponential_angle(smoothed_yaw, target_yaw, 0.15, delta)

どちらのヘルパー関数も、FLOAT・VECTOR2・VECTOR3・QUATERNIONを受け付けます。それ以外の型を指定した場合は、targetが返されて警告が出力されます。_angleバージョンは、FLOATに対しては最短弧のlerp_angle、VECTOR3に対しては軸ごと(ベクトルをラジアン単位のオイラー角として扱って)のlerp_angleを使用します。VECTOR2/QUATERNIONに対しては通常の関数と同じ動作です。

実行時の上書き

set_property_interpolationは、FusionReplicationConfigを変更することなく、実行時に個別のプロパティのモード切り替え/カスタムCallable設定を行うことができます:

GDScript

replicator.set_property_interpolation(
    property: NodePath,
    mode: int,                       # FusionReplicationConfig.INTERPOLATE_NONE / LERP / ANGLE
    callable: Callable = Callable()  # 空のCallable = カスタム補間なし
) -> void

Callableは、デフォルトの線形補間/一時待機ステップを置き換えるものです。各フレームごとに、ノードの現在値・最新の受信値・object_interpolation_time設定・フレームのdeltaと共に呼び出されます:

GDScript

# 型付きシグネチャ(推奨):プロパティの実行時のVariant型が一致している必要がある
func smooth_health(current: float, target: float, decay_time: float, delta: float) -> float:
    return lerp(current, target, 1.0 - exp(-delta / decay_time))

func _ready() -> void:
    replicator.set_property_interpolation(
        ^":health",
        FusionReplicationConfig.INTERPOLATE_LERP,
        smooth_health)

    # Callableなしでモードを切り替え
    replicator.set_property_interpolation(^":yaw", FusionReplicationConfig.INTERPOLATE_ANGLE)

    # どちらも無効にする
    replicator.set_property_interpolation(^":health", FusionReplicationConfig.INTERPOLATE_NONE)

登録されたCallableは、modeNoneの場合でも、受信値のシャドウ作成が強制されます。デフォルトの空のCallable()を渡してset_property_interpolationを呼び出すと、以前に渡されていたCallableはクリアされます。この上書きはキャッシュ上に存在し、実行時のみ有効です。保存されたレプリケーション設定には一切影響しません。

インタレストマネジメント(AOI)

インタレストマネジメントによって、各クライアントがどのオブジェクトの更新を受信するかを制御し、広大なワールドにおける帯域を削減できます。オブジェクトは複製ノードのinterest_modeからinterest_keyを発行し、クライアントはFusionを介してキーを購読します。

  • INTEREST_GLOBAL(デフォルト) - すべてのクライアント上で表示される
  • INTEREST_AREA - 位置からグリッド空間のキーが自動的に計算される(セルサイズはプロジェクト設定のfusion/interest_management/area_of_interest_cell_size
  • INTEREST_USER - カスタムグループキーをinterest_keyに設定する

1つ以上のFusionInterestAreaノードを購読する(グリッドの購読は自動で、通常はカメラ/プレイヤーノードに設定)か、手動で指定します:

GDScript

Fusion.set_area_keys([[cell_key, 0]])   # エリアキー(フレーム毎にリフレッシュされる)
Fusion.add_user_key(42)                  # ユーザーキー(永続する)

Enter/Exitシグナル: Fusion.interest_enter / interest_exit

FusionInterestArea - インスペクター上の設定
FusionInterestArea - インスペクター上の設定

主要プロパティ

インスペクター上のプロパティから、各複製ノードにおけるレプリケーションの動作を制御できます。

  • root_path (NodePath, "..") - プロパティが同期されるノード
  • root_replication_mode - None または Auto
  • root_interpolation_mode - None / Exponential (非物理) または None / Forecast (物理);ルートノード型によってデフォルトが決まる
  • object_interpolation_time (float, 0.150 s) - ExponentialのスムージングとプロパティごとのInterpolateで共有される減衰定数
  • update_interval (int, 1) - サーバーへ送信する間隔(ティック単位)
  • root_teleport_threshold (float, 100.0) - 位置ズレがこの値を超えたらスナップする
  • root_spring_stiffness (float, 50.0) / root_spring_damping (float, 10.0) - 物理補正の調整値(ブレンド補正モードではない物理複製ノードでのみ表示される)
複製ノードのインスペクター
複製ノードのインスペクター

シグナル

複製ノードは、権限が変更された場合やネットワークステートが更新された場合にシグナルを発火します。

  • authority_changed(has_authority: bool) - 所有権が変更された場合
  • state_reset(info: FusionStateResetInfo) - ネットワークによって複製ノードの状態がリセットされた場合。info.reasonから原因を判別可能:REASON_REMOTE_RECEIVED(一般的な受信値の同期)・REASON_PREDICTION_RESET(クライアントサーバー型での予測のロールバック)・REASON_STATE_OVERRIDE(権限の変更)。権限者ローカル上の送信ティック時には発火しません。

入力ループと予測フローについては、予測と入力をご覧ください。

上級トピック:reset_state()

reset_state()は、スムージングを省略して、ノードの状態を現在のFusionネットワークバッファにリセットします。その際にstate_reset(REASON_REMOTE_RECEIVED)が発火します。

これによって複製されるノードのカスタム予測が可能になります。スクリプトからノードの状態を手動で進め、通常通り呼び出されるリセットまでの間に、サーバーのバッファと再同期するための手段が必要な場合に使用できます。

GDScript

@onready var replicator: FusionServerReplicator = $FusionServerReplicator

func _physics_process(delta: float) -> void:
    position += velocity * delta   # カスタム予測

# ゲームの好きなタイミングでトリガーできる:
replicator.reset_state()

完全に制御するためには、root_interpolation_mode = Noneを組み合わせてください。reset_state()自体はスムージングをスキップしますが、モードがExponential/Forecatのままでは、ルートのスムージング処理によって、フレーム間の最新の受信値に対してスムージングが適用され続けるため、カスタム予測処理と競合することになります。
プロパティごとのInterpolateはルートモードと独立しています。カスタムプロパティがInterpolate/Interpolate Angleに設定されていて、直接書き込みが必要な場合は、補間を無効にしてください。

変更データに対して(カスタム予測を行わない)スムージングなしの自動レプリケーションを行いたいのであれば、root_interpolation_mode = Noneに設定するだけで十分です。これですべての受信値が直接スナップされます。

Back to top