Send Priority
概要
Send Priorityは、Fusion 2.1以降で利用可能です。
Send Priorityによって、NetworkObjectを更新する頻度を制御できます。各オブジェクトには優先度の値が設定されていて、何ティックごとに送信するかが決定されます:
- 優先度
1の場合、オブジェクトは送信ティックごとに更新されます。 2の場合、オブジェクトは2送信ティックに1回更新されるため、送信レートはSendRate / 2になります。3の場合、オブジェクトは3送信ティックに1回更新されるため、送信レートはSendRate / 3になります。(以下同様です)
オブジェクトの優先度のデフォルトは1のため、異なる値が設定されない限り、すべてのNetworkObjectは送信ティックごとに更新されます。
優先度の値を上げて送信レートを下げることで、更新頻度は低下してしまいますが、そのかわりにオブジェクトの帯域幅を削減することができます。
Send Priorityはインタレストマネジメントを補完する機能です:インタレストマネジメントはプレイヤーがオブジェクトの更新を受け取るかどうかを決定し、Send Priorityはその更新の受信頻度を決定します。
Per Player Priority
オブジェクトの送信レートを、特定のプレイヤーに合わせて調整することも可能です。 例えば、プレイヤーとの距離に基づいてオブジェクトの優先度を調整することで、関心領域を使用してオブジェクトを非表示にする以外の、遠くのオブジェクトの帯域幅を削減する新たな方法となります。
プレイヤーごとの優先度(Per Player Priority)は、その特定プレイヤーに対して、オブジェクトのデフォルト優先度1を上書きします。
プレイヤーごとの優先度が設定されていないプレイヤーは、引き続きデフォルト優先度1が適用されます。
Per Player Priorityは、ホストモード・サーバーモード・共有モードで利用可能です。
特定プレイヤーに対して優先度を設定するには、以下を呼び出します:
C#
NetworkObject.SetPriority(PlayerRef player, int priority)
- ホスト/サーバーモードでは、ホスト/サーバーが優勢度を管理して、すべてのプレイヤーとオブジェクトのペアに対して優先度を設定する必要があります。
- 共有モードでは、各クライアントが受信するオブジェクトの送信レートを管理します。そのため、ローカルの
PlayerRef(NetworkRunner.LocalPlayer)に対してのみSetPriority呼び出しが有効です。
優先度をクリアする(オブジェクトをデフォルト優先度1にして、送信ティックごとに送信するように戻す)には、以下を使用します:
C#
NetworkObject.ClearPriority(PlayerRef player)
例:Distance-Based Priority
一般的なユースケースとして、遠くのオブジェクトを関心対象から外さずに表示を調整することが挙げられます。以下のコードでは、NetworkObjectのリストを反復処理し、各プレイヤーとオブジェクトとの距離に基づいて、プレイヤーごとの優先度を段階的に調整しています。
C#
public float NearDistance = 32f;
public float FarDistance = 128f;
public void UpdateDistanceBasedPriority(List<NetworkObject> objects)
{
if (!Runner.IsServer) return;
float nearSqrDistance = NearDistance * NearDistance;
float farSqrDistance = FarDistance * FarDistance;
foreach (var player in Runner.ActivePlayers)
{
if (!Runner.TryGetPlayerObject(player, out var playerObject)) continue;
Vector3 playerPosition = playerObject.transform.position;
foreach (var obj in objects)
{
float sqrDistance = (obj.transform.position - playerPosition).sqrMagnitude;
int priority;
if (sqrDistance < nearSqrDistance) priority = 1;
else if (sqrDistance < farSqrDistance) priority = 2;
else priority = 4;
obj.SetPriority(player, priority);
}
}
}
C#
public float NearDistance = 32f;
public float FarDistance = 128f;
public void UpdateDistanceBasedPriority(List<NetworkObject> objects)
{
if (!Runner.TryGetPlayerObject(Runner.LocalPlayer, out var localObject)) return;
Vector3 localPosition = localObject.transform.position;
float nearSqrDistance = NearDistance * NearDistance;
float farSqrDistance = FarDistance * FarDistance;
foreach (var obj in objects)
{
float sqrDistance = (obj.transform.position - localPosition).sqrMagnitude;
int priority;
if (sqrDistance < nearSqrDistance) priority = 1;
else if (sqrDistance < farSqrDistance) priority = 2;
else priority = 4;
obj.SetPriority(Runner.LocalPlayer, priority);
}
}
Send Priorityを上げると、オブジェクトの更新間隔が長くなります。その間隔がNetworkProjectConfig.Simulation.ObjectInactivityThreshold(秒単位、デフォルト0.251)を超えると、オブジェクトの補間処理が停止し、動きが明らかにぎこちなく見えるようになります。
ObjectInactivityThresholdは、2つの異なる更新方法を区別するために存在しています:
- 更新がなめらかなオブジェクト:更新間隔の間にオブジェクトが移動しているなら、2つのスナップショット間を補間することで、正しくスムーズな動きが得られます。
- 非アクティブなオブジェクトがアクティブになる際:変更が止まったオブジェクトは更新データが送信されないため、補間も事実上停止します。そこから新しい状態が到着した際、古いスナップショットから補間を再開すると、動きがジャンプしてしまいます。Fusionはそのかわりに、古いスナップショットの時間を新しいスナップショットに近づけるため、オブジェクトは自身の位置を保持したまま動き始められます。
低い更新レートを効果的に活用するには、ObjectInactivityThresholdを調整して、優先度が高いオブジェクトを上記の最初のカテゴリに分類されるようにする必要があります。オブジェクトの通常の更新間隔がこのしきい値を超えると、すべての更新が「非アクティブ状態からの復帰」に誤分類されてしまい、更新のたびにジャンプが発生してしまいます。
しきい値の選択
更新間隔はおよそpriority / send rateになりますが、送信スケジュールのエイリアシングやネットワークのジッターによって、実際の間隔はこれよりも長く(例:理論上250msでも実際には310ms以上に)なります。経験則として:
ObjectInactivityThreshold = 2 * priority / send rate
しきい値はグローバルに適用されるため、このpriorityは動くオブジェクトで使用される最も高い優先度になります。
例えば、優先度4で送信レート16Hzの場合、結果は0.5秒となります。結果がデフォルト値を下回る場合は、デフォルト値のままにしておいてください。不必要に高い値に設定すると、非アクティブ状態の検知が遅れ、実際に変更が止まったオブジェクトに対してのジャンプが頻発する原因となります。
この設定はインスペクター上には表示されず、NetworkRunner開始前にコードで調整します:
C#
NetworkProjectConfig.Global.Simulation.ObjectInactivityThreshold = 0.5f;
値を確認するには、NetworkProjectConfigのTrace ChannelsフィールドでSendPriorityのトレースチャンネルを有効にします。しきい値を超える更新間隔について、ObjectInactivityThreshold exceeded: 0.3125 >= 0.251というログが出力されます。継続的に変化するオブジェクトに対してこのメッセージが表示され続ける場合は、ログに記録された最大更新間隔よりしきい値を高く設定してください。