This document is about: FUSION 2
SWITCH TO

マッチメイキングAPI

はじめに

マルチプレイヤーゲームの重要な要件の1つは、スキルやレベルが似ているプレイヤー同士や、同じゲームタイプやマップでプレイしたいプレイヤー同士を簡単にマッチングできるようにして、ゲーム体験全般をできるだけ楽しいものにすることです。
Photon Fusionが提供しているAPIを使用することで、プレイヤーに最適な体験をもたらす理想的なマッチングを作成することができます。

Photon FusionはPhoton Cloudと透過的に連動していて、Photonのバックエンドサービスとのやり取りは、ほとんどが内部で自動的に行われます。
このページでは、FusionのマッチメイキングAPIについて説明します。APIはゲームセッションを作成するために使用され、カスタムプロパティのオプションによって、プレイヤーが希望するゲーム体験にあわせた最適なセッションのフィルタリング/参加が可能です。

用語集

マッチメイキングAPIを完全に理解して正しく使用するために、関連する用語を以下に記載します。

  • ゲームセッション:単にセッションとも呼ばれます。プレイヤーがゲームをプレイしたり、コミュニケーションを行うために集まる場所です。これはPhoton Cloudで公開されるため、クライアントは特定のゲームの検索・フィルタリング・参加が可能です。プレイヤー同士はセッション外では通信不可能で、プレイヤーがアクティブに参加できるセッションは1つのみです。ゲームセッションはカスタムプロパティによって、最大プレイヤー数・(ロビーでの)表示/非表示・(参加の)許可/不許可などを設定できます。PUNやPhoton Realtimeでは、ルームと呼ばれていました。
  • ロビー:セッションのリスト(仮想コンテナ)です。例えば、異なるゲームタイプのセッションを複数のロビーに分けることが可能で、これは任意のセッションリストを作成する一般的な方法になります。クライアント同士はロビーでは通信不可能なため、他のクライアントがロビーにいるかを確認することはできません。クライアントは「ロビーに参加している」「ゲームセッションに参加している」「どちらにも参加していない」のいずれかになります。

ゲームセッションの作成/参加

ゲームセッションの作成と参加の2つは、同じ手続きで行われ、そのルールはシンプルです。

  1. 指定したSessionNameのセッションが存在しない場合、SessionNameの新しいセッションが作成されます(後述しますが、必ず作成されるわけではありません)
  2. 指定したSessionNameのセッションが既に存在する場合、ピアはそのセッションに参加します

マッチメイキングAPIは、Fusionのシミュレーションが開始される際に、すべて自動で行われます。
新しいセッションを作成したり、参加するセッションをフィルタリングして検索したりするために使用される、主な引数のリストは以下の通りです。

C#

NetworkRunner.StartGame(new StartGameArgs {
  // ...
  // Arguments related to the Matchmaking between peers
  SessionName = [string],
  SessionProperties = [Dictionary<string, SessionProperty>],
  CustomLobbyName = [string],
  EnableClientSessionCreation = [bool],
  PlayerCount = [int],
  IsOpen = [bool],
  IsVisible = [bool],
  MatchmakingMode = [MatchmakingMode],
});

マッチメイキング関連の引数は全てオプションで、以下のようなものがあります。

  • SessionName:ゲームセッションの「名前」または「ID」で、Photon Cloudでセッションを識別するために使用されます。これは、各リージョンで、一意である必要があります。名前を指定しない場合、Fusionは自動的にランダムなGUIDを生成し、セッションが識別されます。
  • SessionProperties:セッションのカスタムプロパティは、ゲームセッションのメタデータ(ゲームモード/ゲームタイプや、選択中のマップなど)を渡す方法になります。セッションを作成すると、すべてのプロパティはロビーで公開され、プロパティの値をランダムマッチング(後述)のフィルタリングに使用できます。トラフィックを最小化するため、プロパティのキーはできるだけ短くすることを推奨します。デフォルトでは、この値は空で、追加情報は一切含まれません。
  • CustomLobbyName:セッションを関連付けるカスタムの「ロビー名」に使用されます。Fusionはデフォルトで、GameModeに基づいてセッションを分けています(HostServerClientで開始した場合はClientServer LobbySharedで開始した場合はShared Lobby)。
  • EnableClientSessionCreationClientピアが、新しいセッションを作成できるか(または参加のみが可能なのか)を決めるフラグです。詳細は後述します。
  • PlayerCount:セッションに参加できる最大プレイヤー数を定義します。このパラメーターは、新しいセッションを作成する時のみ使用され、デフォルトではNetworkProjectConfig/SimulationDefault Playersの値を使用します。
  • IsOpen:セッションが作成された場合、そのセッションへ他のプレイヤーの参加を許可するかどうかを定義します。詳細は、セッション情報の取得/更新をご覧ください。
  • IsVisible:セッションが作成された場合、そのセッションが他のプレイヤーに表示されるかどうかを定義します。詳細は、セッション情報の取得/更新をご覧ください。
  • MatchmakingMode:ランダムなセッションへの参加を試みる際に使用するモードを定義します。
    • FillRoom:可能な限り早くプレイヤーを集めるために、(古いものから順に)ルームを埋めていきます。これがデフォルトのモードになります。
    • SerialMatching:フィルタリングを考慮しながら、プレイヤーを利用可能なルームに順番に分配していきます。フィルタリングを行わない場合、プレイヤーはルームに均等に分配されます。
    • RandomMatching:(完全に)ランダムにルームへ参加します。フィルタリングは考慮されますが、すべての利用可能なルームが選択候補になります。

ゲームセッションの作成と参加は、ランダムな名前でも、特定のSessionName(ゲームの招待に便利)でも可能です。また、カスタムプロパティを使用して、セッションをフィルタリングし、特定の設定のゲームにのみ参加することも可能です。
これにより、セッションは非常に柔軟に管理することができるようになっています。

ゲームセッションの作成と参加を、Fusionがどのように処理するかを、以下の表にまとめます。この処理は、SessionNameGameModeEnableClientSessionCreationの設定によって決まります。

GameMode Session Name
Valid Empty/Null
Server/Host Create or Join specific Session Create or Join Session with Random ID
EnableClientSessionCreation
Null (default) True False Null (default) True False
Client Join Session Create or Join Session Join Session Join Random Session Join Random or Create Join Random Session
Shared Create or Join Session Create or Join Session Join Session Join Random or Create Join Random or Create Join Random Session
AutoHostOrClient Create or Join Session Create or Join Session Join Session Join Random or Create Join Random or Create Join Random Session

セッション情報の取得/更新

接続中のゲームセッションからは、「名前」や「リージョン」などの様々な情報を取得できます。
これらは、NetworkRunnerSessionInfoプロパティから直接使用できます。
SessionInfo型で使用できるすべてのフィールドを、以下のリストで示します。

  • IsValid [bool{get}]SessionInfoが読み書き可能な状態かどうか。
  • Name [string{get}]:セッション名です。
  • Region [string{get}]:現在接続中のリージョンです。
  • Properties [Dictionary<string, SessionProperty>{get}]:現在のセッションのカスタムプロパティを持つ、読み取り専用のDictionaryです。プロパティの更新は、SessionInfo.UpdateCustomProperties(Dictionary<string, SessionProperty>)メソッドで新しいプロパティを渡してください。
  • IsVisible [bool{get,set}]:セッションがロビーで表示されるかどうか。セッションを非表示にする場合は、このプロパティを変更してください。
  • IsOpen [bool{get,set}]:セッションへの参加が許可されているかどうか。参加の許可/不許可を切り替える場合は、このプロパティを変更してください。
  • PlayerCount [int{get}]:セッションの現在プレイヤー数です。ロビーでのみ利用可能です
  • MaxPlayers [int{get}]:セッションに参加可能な最大プレイヤー数で、値にはサーバー/ホストのピアも含まれます。ロビーでのみ利用可能です

セッション情報やカスタムプロパティは、マッチメイキング用途でのみ使用し、クライアント間のゲームの状態の同期などには使用しないことを強く推奨します
ゲームプレイに関連するセッションの情報を通信する必要がある場合は、グローバルなネットワークオブジェクトを用意したり、単発のRPCを使用したりしてください。

NetworkRunnerが提供している、セッションやPhoton Cloudに関連するゲーム中に使用可能なプロパティは、以下の通りです。

  • NetworkRunner.IsCloudReady:ローカルピアがPhoton Cloudに接続していて、ルームの作成/参加や、ロビーへの参加が可能かどうか。
  • NetworkRunner.UserId:認証後のローカルピアのUserIdです。この情報は、アプリケーションの認証サービスから渡されます。
  • NetworkRunner.AuthenticationValuesAuthenticationValueの参照で、Fusion開始時のローカルピアの認証に使用されます。
  • NetworkRunner.CurrentConnectionType:ピアの現在の接続タイプで、リモートサーバーへの接続がDirectRelayedかを示します。共有モードでは、クライアントは常にRelayedになります。
  • NetworkRunner.NATType:NATパンチスルーシステムが有効な場合、Fusionは、ローカルピアの現在のネットワークのNATタイプを判定します。NATタイプは、InvalidUdpBlockedOpenInternetFullConeSymmetricのいずれかです。
  • NetworkRunner.IsSharedModeMasterClient:ローカルピアが、共有モードのセッションのマスタークライアントであるかどうかのフラグです。この値は共有モードでのみ有効で、マスタークライアントとその他のクライアントとで差別化したい特定のアクションを、どのピアで実行するかを決定するために使用できます。

他にもNetworkRunner.LobbyInfoでは、ピアが接続している現在のロビーの情報が取得できます。LobbyInfoの主要なプロパティのリストは、以下の通りです。

  • IsValid [bool{get}]LobbyInfoが読み書き可能な状態かどうか
  • Name [string{get}]:現在のロビー名です

ゲームセッションブラウザ

Fusionでゲームセッションブラウザを作成することは可能ですが、極力避けることを強く推奨します。

ゲームセッションブラウザは、1990年代~2000年代初頭まで非常に人気がありました。基本的な設計は今も有効ですが、近年ではその主要な目的(できるだけ早く適切なゲームを見つけて参加する)は、マッチメイキングに置き換えられています。

ゲームセッションブラウザが推奨されないことには、様々な理由があります。

  • セキュリティ上の問題があります
  • アクティブなゲームセッションのリスト取得は、サーバーに非常に高い負荷がかかります
  • ゲームセッションのリストは、プレイヤーが接続しているリージョンに常に限定されます
  • 最も重要な点として、これは古いデザインパターンであり、現在はより優れたUXのオプションが利用可能です

FusionのマッチメイキングAPIが提供する、最新の代替方法は以下の通りです。

  1. ゲームセッションを速やかに埋める/参加するなら:ランダムな公開ルームに参加します。
  2. 特定のタイプのセッションに参加するなら:ゲームセッションプロパティを使用してルームをフィルタリングした後に、ランダムな公開ルームに参加します。
  3. 特定のプレイヤーと一緒にプレイするなら:招待コードを作成して、セッション名指定でゲームセッションに参加します。

ゲームのコミュニティでカスタムサーバー/MODサーバーがよほど豊富に稼働しているのでもない限り、ルームの完全なリストをリクエストして、ユーザーがゲームセッションブラウザで選択できるようにする理由はほぼありません。

APIの使用例

ランダムなセッションへの参加

任意のセッションに参加する(できるだけ早くプレイヤーをゲームに参加させる)には、単にNetworkRunnerを追加パラメーター無しで開始して、利用可能なゲームセッションを検索します。

C#

public async Task StartPlayer(NetworkRunner runner) {

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.AutoHostOrClient, // or GameMode.Shared
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

この方法では、ローカルピアはランダムなゲームセッションに参加します。もしセッションが見つからない場合は、ランダムなセッション名の新しいセッションを作成します(これはGameMode.AutoHostOrClientを使用しているためです)。共有モードのセッション(NetworkRunnerGameMode.Sharedで開始した場合)でも有効です。

カスタムプロパティを持つゲームセッションの開始

この例では、ホストはいくつかのカスタムプロパティを持ったゲームセッションを作成し、その後、クライアントはプロパティを使用してセッションをフィルタリングできます。

C#

// Some predefined types used as values for the Game Session Properties
public enum GameType : int {
  FreeForAll,
  Team,
  Timed
}

public enum GameMap : int {
  Forest,
  City,
  Desert
}

// Utility method to start a Host using a defined GameMap and GameType
public async Task StartHost(NetworkRunner runner, GameMap gameMap, GameType gameType) {

  var customProps = new Dictionary<string, SessionProperty>();

  customProps["map"] = (int)gameMap;
  customProps["type"] = (int)gameType;

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Host,
    SessionProperties = customProps,
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

サンプルコードでは、ゲームセッションのカスタムプロパティの値にenumを使用していますが、これはあくまで一例です。
ホスト(GameMode = GameMode.Host)としてrunner.StartGameを呼び出すと、ランダムな名前(SessionName引数を渡していないため)の新しいセッションを開始します。セッションには、SessionProperties引数のプロパティが含まれます。

ランダムなセッションのフィルタリング

ここでは上記のコード例に合わせて、特定のGameTypeのゲームセッションに参加するクライアントを開始する方法を説明します。
開始コードは基本的に同じですが、GameModeGameMode.Clientに設定され、customPropsにはtypeキーと希望するgameType値のみが含まれます。

C#

public async Task StartClient(NetworkRunner runner, GameType gameType) {

  var customProps = new Dictionary<string, SessionProperty>() {
    { "type", (int)gameType }
  };

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Client,
    SessionProperties = customProps,
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

これだけで、クライアントは特定のGameTypeのランダムなセッションに参加できます。

ロビーからセッションに参加

最適なゲームセッションを見つける方法として、プレイヤーが選択できるセッションのリストを提供する方法もあります。
この場合はロビーに参加することになりますが、本当に必要でなければこの方法は極力避けることを強く推奨します。
ほとんどの場合は、プロパティのフィルタリングに基づいたセッションの参加が最適な方法です。しかし、Fusionはセッションのリストも非常に簡単に取得できるようになっています。

セッションのリストの取得は、前述したFusionを開始する通常のフローとは若干異なります。

  1. ロビーへの参加:ピアは、NetworkRunner.JoinSessionLobby([SessionLobby], [string])を呼び出して、Photon Cloudに接続し、特定のロビーに参加します。このメソッドは、2つの引数を取ります。
    • SessionLobby:以下の値のいずれかになります。
      1. ClientServer:デフォルトのClientServer Lobbyに参加します
      2. Shared:デフォルトのShared Lobbyに参加します
      3. Custom:カスタムのLobbyNameと組み合わせて使用します
    • LobbyName:作成済みのゲームセッションのCustomLobbyNameを指定します

C#

// Utility method to Join the ClientServer Lobby
public async Task JoinLobby(NetworkRunner runner) {

  var result = await runner.JoinSessionLobby(SessionLobby.ClientServer);

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

上記で説明した通り、クライアントはこの方法で、ClientServer/Shared/Customのロビーに参加できます。
以下の例は、サーバー/ホストでカスタムロビーのセッションを作成する方法を示しています。

C#

public async Task StartHost(NetworkRunner runner) {

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Host,
    CustomLobbyName = "MyCustomLobby"
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

クライアントがそのカスタムロビーに参加する方法は、以下の通りです。

C#

// Utility method to Join a Custom Lobby
public async Task JoinLobby(NetworkRunner runner) {

  var result = await runner.JoinSessionLobby(SessionLobby.Custom, "MyCustomLobby");

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}
  1. ゲームセッションのリストを取得:Fusionの主要なAPIのエントリーポイントの1つはINetworkRunnerCallbacksで、Fusionの様々なイベント(ロビーからのセッションのリストを含む)を受け取るための特別なインターフェースです。OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)コールバックは、セッションの作成/削除や、セッションのプロパティが更新されるたびに呼び出され、セッションの完全なリストが取得できます。SessionInfo型は前述したものと同じです。リストは表示・フィルタリング・並び替えなどが可能です。

  2. セッションへの参加:参加するセッションを選択して、通常のNetworkRunner.StartGame()でFusionを開始します。クライアントは、存在するセッションのSessionNameを使用しなければ、特定のゲームセッションに参加することはできません。

    • 通常通りに適切なGameModeを選択します。セッションに参加するピアは、GameMode.ClientまたはGameMode.Sharedのいずれかである必要があります。
    • ゲームセッションを識別するため、SessionNameSessionInfo.Nameを設定する必要があります。
    • その他のパラメーターはオプションですが、適切に初期化する必要があります。

C#

public class MyBehaviour : Fusion.Behaviour, INetworkRunnerCallbacks {

  // other callbacks...

  // Receive the List of Sessions from the current Lobby
  public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) {

    Debug.Log($"Session List Updated with {sessionList.Count} session(s)");

    // Example
    // Join first session from the list

    // Check if there are any Sessions to join
    if (sessionList.Count > 0) {

      // Get first Session from the list
      var session = sessionList[0];

      Debug.Log($"Joining {session.Name}");

      // Join
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // Client GameMode, could be Shared as well
        SessionName = session.Name, // Session to Join
        // ...
      });
    }

    // OR

    // Example
    // Search the list for a Session with a specific Property

    // Store the target session
    SessionInfo session = null;

    foreach (var sessionItem in sessionList) {

      // Check for a specific Custom Property
      if (sessionItem.Properties.TryGetValue("type", out var propertyType) && propertyType.IsInt) {

        var gameType = (int)propertyType.PropertyValue;

        // Check for the desired Game Type
        if (gameType == 1) {

          // Store the session info
          session = sessionItem;
          break;
        }
      }
    }

    // Check if there is any valid session
    if (session != null) {
      Debug.Log($"Joining {session.Name}");

      // Join
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // Client GameMode, could be Shared as well
        SessionName = session.Name, // Session to Join
        // ...
      });
    }
  }
}
Back to top