This document is about: FUSION 2
SWITCH TO

インタレストマネジメント

概要

インタレストマネジメントはデータカリングの機能で、サーバーから特定のクライアントへのデータ(ネットワークオブジェクトやNetworkBehaviour)複製を制限します。これは、ネットワークトラフィックの削減や、プレイヤーのデータアクセス制限(同じチームのみに送る情報など)に役立ちます。

主要なデータカリングメカニズムは、以下の2つです。

  • Object Interest サーバーから特定のPlayerRefへの、NetworkObjectの更新を制限する
  • Behaviour Interest サーバーから特定のPlayerRefへの、NetworkBehaviourの更新を制限する

Object Interest

Object Interestはネットワークオブジェクトごとの設定で、オブジェクトに対して、プレイヤーがどのように関心を持つのかを定めます。ネットワークオブジェクトに関心を持たないプレイヤーは、サーバーからネットワークオブジェクトの更新(ネットワークプロパティやRPC)を受け取ることは一切ありません。

NetworkObject.ObjectInterestには、以下の3つのオプションがあります。

  • Area Of Interest:プレイヤーは、自身の関心領域と位置(2D/3D)が重なるネットワークオブジェクトに対して関心を持ちます
  • Global:ネットワークオブジェクトに対して、すべてのプレイヤーが関心を持ちます
  • Explicit:ネットワークオブジェクトに対して、明示的にSetPlayerAlwaysInterested()を使用したプレイヤーのみが関心を持ちます
Object Interestオプション
Object Interestオプション

IInterestEnter / IInterestExit コールバック

ネットワークオブジェクトに登録されているコンポーネントにIInterestEnter/IInterestExitインターフェースを実装すると、ローカルプレイヤーがオブジェクトに対する関心を持った/失った時にInterestEnter(PlayerRef player)/InterestExit(PlayerRef player)が呼ばれます。これは、プレイヤーの関心領域にオブジェクトが出入りしたり、サーバーによって明示的にSetPlayerAlwaysInterested()が設定されたりした時に発生します。

これらのコールバックは、ホストモードのサーバー(専用サーバー/ホスト)でもトリガーされます。すべてのプレイヤーの関心から外れているネットワークオブジェクトの動作を、選択的に無効にするのに便利です(例えば、関心を持つプレイヤーがいないNPCのアニメーションを無効にしたい場合など)。

ローカルプレイヤーが関心を持つオブジェクトでのみ、クライアントでコールバックがトリガーされます。ホスト以外のプレイヤーは、他のプレイヤーがどのオブジェクトに関心を持っているかを知ることはできません。

NetworkBehaviourIInterestEnterIInterestExitの実装は、ネットワークオブジェクトによって自動的に検出されますが、NetworkBehaiourを継承しないコンポーネントでインターフェースを実装した場合は、NetworkRunnerに手動で登録する必要があります。

重要: プレイヤーの関心はSpawned()時に適用されるため、クライアントでは、オブジェクトがスポーンする時にIInterestEnterは呼び出されません。必要に応じて、Spawned()InterestEnter()固有の処理を実行してください。

重要: クライアントでは、オブジェクトがデスポーンする時にIInterestExitは呼び出されません。必要に応じて、Despawned()InterestExit()固有の処理を実行してください。

Area Of Interest

NetworkObjectでこのモードが選択されると、サーバーピア(共有モードを使用している場合は、共有サーバー)は、各プレイヤーが定義した関心領域(AOI)を使用して、プレイヤーがそのネットワークオブジェクトに関心があるかどうかを判定します。プレイヤーの関心領域を指定するには、Runner.AddPlayerAreaOfInterest()メソッドを呼び出します。

重要: 関心領域処理を有効にするには、Network Project ConfigReplication FeaturesScheduling and Interest Managementに設定する必要があります。

重要: Area Of Interest の処理を有効にするには、Network Project Config (NPC) の Replication Features フィールドを Scheduling and Interest Management に設定する必要があります。

NetworkProjectConfigからインタレストマネジメントを有効にする
NetworkProjectConfigからインタレストマネジメントを有効にする

共有モードを使用している場合を除き、クライアントは複数の領域を定義することができます(例えば、プレイヤーがリモートカメラを操作している場合など)。プレイヤーは、関心領域に1つ以上重なるネットワークオブジェクトに関心を持ち、サーバーからネットワークオブジェクトの更新を受け取ります。

重要: 共有モードでは、AddPlayerAreaOfInterest()でプレイヤーの新しい関心領域を追加すると、過去のアクティブな領域は削除されます。共有モードは、プレイヤーごとに関心領域を1つのみ設定できます。

NetworkTRSP要件

関心領域システムは、NetworkTRSPクラスの位置の値から、オブジェクトの位置を決定します。関心領域処理に含めたいネットワークオブジェクトは、NetworkObjectコンポーネントと同じゲームオブジェクトにNetworkTRSP派生クラスを追加する必要があります。

NetworkTRSPの詳細は、こちらをご覧ください。

以下のFusionのコンポーネントは、NetworkTRSPを継承していて、関心領域処理に準拠します。

NetworkTransformの入れ子

重要NetworkTransformはローカル座標を同期しますが、これがインタレストマネジメントに問題を引き起こす可能性があります。他のネットワークオブジェクトの子要素になる可能性のあるオブジェクトは、AutoAOIOverrideを使用することを推奨します。NetworkObject/NetworkTransformコンポーネントを持たない通常のゲームオブジェクトを親に持ち、その親オブジェクトの位置が(0,0,0)でない場合、子ネットワークオブジェクトのNetworkTransformのローカル座標が、インタレストマネジメントのグローバル座標として使用されるため、インタレストマネジメントが正しく機能しません。

AddPlayerAreaOfInterestの使用方法

C#

public class SetAreaOfInterest : NetworkBehaviour
{
  public float Extents = 32f;

  public override void FixedUpdateNetwork()
  {
    if (Runner.IsServer)
    {
      var controller = Object.InputAuthority;
      // Set the controlling players area of interest region around this object
      if (!controller.IsNone)
      {
        Runner.AddPlayerAreaOfInterest(controller, transform.position, Extents);
      }
    }
  }
}

備考: Object.SetPlayerAlwaysInterested()を関心領域と合わせて使用することで、関心領域外のオブジェクトに対して強制的に関心を持たせることができます。

AreaOfInterestOverride

NetworkTransformのAreaOfInterestOverrideをご覧ください。

メインのNetworkTRSPAreaOfInterestOverrideを設定すると、サーバーは、別のネットワークオブジェクトの位置データを使用して、ネットワークオブジェクトに対するプレイヤーの関心を判定します。

C#

public void ChangeAOIOverride(NetworkObject proxyObject)
{
  // Any NetworkTRSP derived component (such as NetworkTransform)
  // on the root of an NetworkObject is referred to as the Main TRSP,
  // This main NetworkTRSP component is used to determine AOI for the NetworkObject.
  var mainTRSP = Object.GetComponent<NetworkTRSP>();

  // Setting AreaOfInterestOverride on the main NetworkTRSP tells Fusion to use the
  // Area Of Interest results of different NetworkObject to determine this Object's Interest.
  // Setting this to 'null' disables the override.
  Object.GetComponent<NetworkTRSP>().SetAreaOfInterestOverride(proxyObject);
}

Global

すべてのプレイヤーが、このネットワークオブジェクトに対して関心を持ちます。ネットワークオブジェクトに対するカリングを効率的に無効化でき、すべてのプレイヤーが、ネットワークオブジェクトの更新をサーバーから受け取ります。

Explicit

Runner.SetPlayerAlwaysInterested()で明示的に関心を持ったプレイヤーのみが、このネットワークオブジェクトに対して関心を持ちます。明示的に関心を持ったプレイヤーのみが、ネットワークオブジェクトの更新をサーバーから受け取ります。

C#

public class MakeAlwaysInterested : NetworkBehaviour, IPlayerJoined
{
  void IPlayerJoined.PlayerJoined(PlayerRef player)
  {
    if (Runner.IsServer)
    {
      Object.SetPlayerAlwaysInterested(player, true);
      // or
      // Runner.SetPlayerAlwaysInterested(player, Object, true);
    }
  }
}

RPCとObject Interest

Object Interestは、静的ではないRPC(インスタンスRPC・ターゲットRPC)の配信にも影響します。サーバーは、ネットワークオブジェクトに関心を持たないプレイヤーへのRPC送信の失敗を、RpcInvokeInfoの戻り値から検知して処理できます。

C#

[Rpc]
public RpcInvokeInfo RpcFoo()
{
  return default;
}

public override void FixedUpdateNetwork()
{
  if (Object.HasStateAuthority)
  {
    var info = RpcFoo();
    int culledCount = info.SendResult.CulledReceivers.Length;
    if (culledCount > 0)
    {
      //Handling for a target peer being culled from send
      Debug.LogWarning($"{culledCount} receivers culled, possibly due to no Object Interest.");
    }
  }
}

Behaviour Interest

ホストモード(専用サーバー/ホスト/シングルプレイヤー)のサーバーピアは、NetworkBehaviourのすべてのネットワークプロパティ(状態)の複製を、特定のプレイヤーのみに制限することができます。

ReplicateTo(PlayerRef player)メソッドを独自実装でオーバーライドすると、状態を複製するかどうかをプレイヤーごとに選択できます。

備考: これはホストモードでのみ適用可能(共有モードでは不可能)です。また、ReplicateTo()は、ホストモードのサーバー(状態権限を持つピア)でのみ有効です。

備考: Behaviour Interestより先にObject Interestが決定されます。ネットワークオブジェクトに関心を持たないプレイヤーは、そのオブジェクトのNetworkBehaviourも複製されません。

備考: Behaviour Interestは、ネットワークプロパティ([Networked]属性が付いたプロパティ)にのみ影響します。リモートプロシージャコール(RPC)には影響しません。

使用方法

C#

using Fusion;

public class InterestManagementSampleCode : NetworkBehaviour
{
  // Because this NetworkBehaviour is restricted to even players by the ReplicateTo() override
  // Only clients with an even LocalPlayer value will get updates for this value.
  // For odd-numvered players this value will remain zero.
  [Networked]
  int secretNumberOnlyEvenPlayersShouldKnow { get; set;}

  // NOTE: This method is only ever called on the Server in Server Mode
  // and is not applicable to Shared Mode
  protected override bool ReplicateTo(PlayerRef player)
  {
      // Only replicate this behaviours state to Players with even numbered PlayerIDs
      return player.PlayerId % 2 == 0;
  }
}

Send Priority

Fusion 2.1 Only

Send Priorityは、Fusion 2.1以降で利用可能です。

Send Priorityによって、NetworkObjectを更新する頻度を制御できます。各オブジェクトには優先度の値が設定されていて、何ティックごとに送信するかが決定されます:

  • 優先度1の場合、オブジェクトは毎ティック更新されます。
  • 2の場合、オブジェクトは2ティックに1回更新されるため、送信レートはtickRate / 2になります。
  • 3の場合、オブジェクトは3ティックに1回更新されるため、送信レートはtickRate / 3になります。(以下同様です)

オブジェクトの優先度のデフォルトは1のため、異なる値が設定されない限り、すべてのNetworkObjectは毎ティック更新されます。

優先度の値を上げて送信レートを下げることで、更新頻度は低下してしまいますが、そのかわりにオブジェクトの帯域幅を削減することができます。

Per Player Priority

オブジェクトの送信レートを、特定のプレイヤーに合わせて調整することも可能です。
例えば、プレイヤーとの距離に基づいてオブジェクトの優先度を調整することで、関心領域を使用してオブジェクトを非表示にする以外の、遠くのオブジェクトの帯域幅を削減する新たな方法となります。

プレイヤーごとの優先度(Per Player Priority)は、その特定プレイヤーに対して、オブジェクトのグローバル優先度を上書きします。プレイヤーごとの優先度が設定されていないプレイヤーに対しては、引き続きオブジェクトのグローバル優先度が適用されます。

Per Player Priorityは、ホストモード・サーバーモード・共有モードで利用可能です。

特定プレイヤーに対して優先度を設定するには、以下を呼び出します:

C#

NetworkObject.SetPriority(PlayerRef player, int priority)
  • ホスト/サーバーモードでは、ホスト/サーバーが優勢度を管理して、すべてのプレイヤーとオブジェクトのペアに対して優先度を設定する必要があります。
  • 共有モードでは、各クライアントが受信するオブジェクトの送信レートを管理します。そのため、ローカルのPlayerRefNetworkRunner.LocalPlayer)に対してのみSetPriority呼び出しが有効です。

優先度をクリアする(グローバル優先度を再使用する)には、以下を使用します:

C#

NetworkObject.ClearPriority(PlayerRef player)

ホスト/サーバーモードでは、NetworkObjectにコールバックが用意されていて、オブジェクトが初めてプレイヤーへの送信対象になった際に呼び出されます。これを実装することで、オブジェクトに対してプレイヤーごとの優先度が常に設定されるようにすることができます:

C#

public void Awake()
{
    Object.SendPriorityCallback += PriorityCallback;
}

public void OnDestroy()
{
    Object.SendPriorityCallback -= PriorityCallback;
}

public int PriorityCallback(NetworkObject obj, PlayerRef player) 
{
    int priority = 1; // ここに実装を追加する
    return priority;
}

例: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);
  }
}
Back to top