レプリケーション

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

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

FusionReplicator

FusionReplicatorは、ネットワークオブジェクトのプロパティの同期を処理します。ネットワークシーンのルートノードの子ノードとして追加してください。権限を持つクライアントが書き込んだ値はサーバーに送信され、リモートクライアントはその値を受け取り、必要に応じて値をスムージングします。

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_smoothingtrue(デフォルト)の場合、リモートの値はスナップせずにスムーズに補正されます。物理オブジェクトでは速度ベースの予測が使用され、それ以外ではspring-damper補間が使用されます。値を直接適用したい場合は、root_smoothingfalseに設定してください。

カスタムプロパティ

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

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

プロパティパス構文

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

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

GDScript

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

文字列と配列

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

Array(配列:PackedFloat32ArrayPackedInt32ArrayPackedVector2Arrayなど)は、生成時に固定max_capacityを設定する必要があります。容量を超えた値は切り捨てられます。

ノードの参照

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

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

GDScript

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

# リモートでは解決されたノード(またはnull)を自動的に受信する
print(partner.name) # "Player2"

解決ルール

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

ワード数の参照

複製される各プロパティは、同期バッファ内で固定数の32ビットワードを占有します。ワード数を把握することで、オブジェクトごとの帯域を見積もることができます。

Godotの型 ワード数
bool1
int2
float1
String2
Node (Object)2
Vector22
Vector33
Vector4 / Quaternion / Color4
Transform2D6
Transform3D12

配列は1 + (max_capacity × element_words)。実行時に$FusionReplicator.compute_word_count()で確認可能。

権限と所有権

権限(Authority)によって、ネットワークオブジェクトのプロパティを書き込みできるクライアントが決定されます。サーバーは権限を持つクライアントの値のみを受け付けて、その他のクライアントは読み取り専用のコピーが送信されます。Fusionでは、FusionReplicatorノードから設定可能な権限モードがいくつか(TransactionPlayer AttachedDynamicMaster Client)あります。

APIクイックリファレンス

GDScript

$FusionReplicator.has_authority()         # 自身が現在の所有者か?
$FusionReplicator.get_owner_id()          # どのプレイヤーがこのオブジェクトを所有しているか?
$FusionReplicator.want_authority(true)    # 所有権を要求する
$FusionReplicator.want_authority(false)   # 所有権を手放す

want_authorityメソッドは、オプションで第二引数(update_interval)を受け取り、クライアントがプロパティ更新を送信する頻度(ティック単位)を制御できます。これは主にDynamicモードに関連します。

Transaction (default)

所有権の移譲は、サーバーによって確認されます。現在の所有者は明示的にwant_authority(false)で解放するまで権限を保持し、解放されるとサーバーはオブジェクトを孤立(所有者無し)にマークします。その後、他のクライアントはwant_authority(true)によってそのオブジェクトの所有権を要求できます。

所有者が切断されると、オブジェクトは孤立状態で存続し、他のクライアントがオブジェクトの所有権を要求できます。

GDScript

# 所持アイテムや乗り物で使用できるパターン
func _on_interact(player):
    $FusionReplicator.want_authority(true)   # 接触時に要求

func _on_exit(player):
    $FusionReplicator.want_authority(false)  # 手放したら解放

Player Attached

要求/解放の仕組みはTransactionと同じですが、1つ重要な違いがあり、所有しているプレイヤーが切断されると、オブジェクトはサーバーによって自動的にデスポーンされるという点です。

プレイヤーのアバター・プレイヤー所有のアイテムなど、所有者より長く存在してはいけないオブジェクトにはこれを使用してください。

GDScript

# プレイヤーアバターをPLAYER_ATTACHEDモードで作成する
# スポーンしたプレイヤーが権限者に割り当てられる
func _on_player_joined(player_id: int):
    var avatar = preload("res://player.tscn").instantiate()
    $FusionClient.spawn(avatar)  # FusionReplicatorノードでowner_modeが設定される

Dynamic

権限の予測は、サーバー確認を待つと遅く感じられるような、高速な権限移譲を想定して設計されています。

want_authority(true)は、クライアントはローカルで即座に所有権を割り当て、完全な送信レートでデータ送信を開始します。サーバーが受け入れた場合、特別な処理は行われず、クライアントはそのまま権限者になります。サーバーが要求を拒否した場合(別のクライアントのデータが先に到達した場合など)は、権限は元に戻されます。

want_authority(false)は、クライアントは送信レートを下げて所有権を放棄する意思を示します。これによって、サーバーは別のクライアントの予測的な要求を受け入れることができるようになります。他のクライアントに権限が引き継がれたことがサーバーに確認された後、クライアントは自動的に権限を再要求することはありません。

自動的な送信レート管理

メソッドupdate_interval
want_authority(true)1(毎ティック)
want_authority(false)16(低レート)

権限者の送信レートを下げることで、サーバーが動的なリクエストを受け入れやすくなります(新しいクライアントが新しいデータを送信するための十分な余裕を持たせられるためです)。第二引数を指定する(例:want_authority(false, 12))ことで、デフォルトの許容間隔を上書きすることができます。

GDScript

# 物理ボール - センターラインで所有権が移譲される(ポンゲームのパターン)
func _physics_process(delta):
    if not $FusionReplicator.has_authority():
        if ball_crossed_my_half():
            $FusionReplicator.want_authority(true)   # 予測的な要求
    else:
        if ball_left_my_half():
            $FusionReplicator.want_authority(false)  # 所有権の譲渡
        else:
            apply_physics(delta)

Master Client

現在のマスタークライアントによって常に所有されます。want_authority()呼び出しは無視されます。

マスタークライアントが切断された場合、Photonは新しいマスタークライアントを割り当て、所有権が自動的に移譲されます。そのため、アプリケーション側の処理は不要です。

シングルトンで共有される状態(スコアボード・試合のタイマー・GameManagerオブジェクトなど)に使用してください。

authority_changed シグナル

authority_changedシグナルは、モードに関係なく所有権が確定するたびにFusionReplicatorノードから発火します。権限移譲に反応したい場合は、このシグナルに接続してください。

GDScript

func _ready():
    $FusionReplicator.authority_changed.connect(_on_authority_changed)

func _on_authority_changed():
    if $FusionReplicator.has_authority():
        print("I now own this object")

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

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

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

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

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

GDScript

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

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

主要プロパティ

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

  • root_path (NodePath, "..") - プロパティが同期されるノード
  • root_replication_mode - NONE or AUTO
  • root_smoothing (bool, true)
  • update_interval (int, 1) - サーバーへ送信する間隔(ティック単位)
  • root_teleport_threshold (float, 100.0) - 位置ズレがこの値を超えたらスナップする
  • root_spring_stiffness (float, 50.0) / root_spring_damping (float, 10.0)
FusionReplicator インスペクター
FusionReplicator インスペクター

シグナル

FusionReplicatorは、権限が変更された時や同期サイクルが完了した時にシグナルを発火します。

  • authority_changed(has_authority: bool) - 権限が変更された
  • synchronized - 内部の同期サイクルが完了した
Back to top