This document is about: V3 CLIENT SERVER
SWITCH TO

物理演算のレプリケーション

物理オブジェクトは、力・衝突・重力の影響下で継続して動き続けるため、複製するのが困難です。リモートオブジェクトを受信した位置に単純にスナップさせても、特に一般的なネットワーク更新レート(10~30回毎秒)では、同期ズレが目立ってしまいます。

Fusionは速度ベースの予測によってこの問題を解決します。リモートクライアントは、最新の既知の速度を使用して物体の位置を予測し、次のサーバー更新が到着した際に、spring-damper系によってズレをスムーズに補正します。これによって、ある程度のパケットロスや遅延が発生している状況でも、リモートクライアント上でスムーズな動きが実現できます。

概要

RigidBody2D/3Dをルートに持つFusionSharedReplicatorroot_replication_modeAutoに設定します。FusionSharedReplicatorは物理オブジェクトを自動的に検知し、速度ベースの予測スムージングを使用します。位置・回転・速度・角速度は自動的に同期されるため、手動でプロパティを設定する必要はありません。

Forecastの動作について

各ネットワーク更新において、以下の式から位置が予測されます:

predicted_pos = received_pos + velocity × time + 0.5 × gravity × time²

spring-damper系によってローカル状態から予測位置に向かってスムーズに補正することで、高速で動作するオブジェクトの知覚的な遅延を軽減します。

主要プロパティ

FusionSharedReplicatorのプロパティによって、リモートクライアント上で物理補正をどのように適用するかを制御できます。

  • root_teleport_threshold (float, 100.0) - 位置ズレがこの値を超えると、即座にスナップが行われます
  • root_rotation_teleport_threshold (float, 15.0) - 回転ズレがこの値を超えると、即座にスナップが行われます(度単位、0で無効)
  • root_spring_stiffness (float, 50.0) - 値が大きいほど補正挙動が硬くなる
  • root_spring_damping (float, 10.0) - 値が大きいほど跳ね返りが少なくなる
  • root_max_forecast_time (float, 0.25) - 予測する最大秒数
  • root_correction_mode - VELOCITY(軽いオブジェクト用), FORCE(重いオブジェクト用、デフォルト), BLEND(非物理的スナップ)
  • root_blend_factor (float, 0.1) - BLENDモードの重み付け
  • root_forecast_gravity (bool, true) - 予測に重力を適用するか
  • sync_sleeping (bool, true) - 物理のスリープ状態を複製するか

調整のコツ

以下のガイドラインを参考にして、物理演算のレプリケーションでよくみられる不具合を診断・修正してください。

  • 頻繁なテレポートroot_teleport_thresholdを増やす
  • オブジェクトの動作が鈍いroot_spring_stiffnessを増やし、root_spring_dampingを減らす
  • 振動/跳ね返りroot_spring_dampingを増やす(damping >= 2 × sqrt(stiffness) になるように調整する)
  • 予測のオーバーシュートroot_max_forecast_timeを減らすか、重力のないオブジェクトはroot_forecast_gravityを無効にする

実装例(2D)

Rigidbody2Dに権限を持つクライアントのみが入力を適用し、リモートクライアントはFusionSharedReplicatorによって物理状態を自動的に受信します。

GDScript

extends RigidBody2D

const MOVE_FORCE := 600.0
@onready var sync: FusionSharedReplicator = $FusionSharedReplicator

func _physics_process(delta):
    if sync.has_authority():
        var dir = Input.get_axis("ui_left", "ui_right")
        if dir != 0:
            apply_central_force(Vector2(dir * MOVE_FORCE, 0))
        if Input.is_action_just_pressed("ui_accept"):
            apply_central_impulse(Vector2(0, -500))

シーン構造:

物理的プレイヤーシーン構造
物理的プレイヤーシーン構造

テレポート

新しい位置に(スムージングを省略して)即座にスナップさせます:

GDScript

sync.teleport_2d(Vector2(400, 300), 0.0)                     # 2D (position, rotation)
sync.teleport_3d(Vector3(10, 0, 5), Vector3.ZERO)            # 3D (position, rotation)
sync.teleport_2d(Vector2(400, 300), deg_to_rad(90))           # 2Dでrotationを指定
Back to top