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

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

Fusionは接続中のゲームセッションから、「名前」や「リージョン」などの様々な情報を取得できます。
これらのデータは、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.AuthenticationValues | AuthenticationValueの参照で、Fusion開始時のローカルピアの認証に使用されます。 |
| NetworkRunner.CurrentConnectionType | ピアの現在の接続タイプで、リモートサーバーへの接続がDirectRelayedかを示します。共有モードでは、クライアントは常にRelayedになります。 |
| NetworkRunner.NATType | NATパンチスルーシステムが有効な場合、Fusionは、ローカルピアの現在のネットワークのNATタイプを判定します。NATタイプは、InvalidUdpBlockedOpenInternetFullConeSymmetricのいずれかです。 |
| NetworkRunner.IsSharedModeMasterClient | ローカルピアが、共有モードのセッションのマスタークライアントであるかどうかのフラグです。この値は共有モードでのみ有効で、マスタークライアントとその他のクライアントとで差別化したい特定のアクションを、どのピアで実行するかを決定するために使用できます。 |

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

| Property | Description |
|---|---|
| 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, // または、GameMode.Shared
  });

  if (result.Ok) {
    // 問題なし
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

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

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

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

C#

// ゲームセッションプロパティに、定義済みの型の値を使用する
public enum GameType : int {
  FreeForAll,
  Team,
  Timed
}

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

// 定義されたGameMapと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) {
    // 問題なし
  } 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) {
    // 問題なし
  } 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#

// ClientServerロビーに参加する便利メソッド
public async Task JoinLobby(NetworkRunner runner) {

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

  if (result.Ok) {
    // 問題なし
  } 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) {
    // 問題なし
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

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

C#

// カスタムロビーに参加する便利メソッド
public async Task JoinLobby(NetworkRunner runner) {

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

  if (result.Ok) {
    // 問題なし
  } 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 {

  // その他のコールバック...

  // 現在のロビーからセッションのリストを取得する
  public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) {

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

    // 例
    // リストの最初のセッションに参加する

    // 参加できるセッションが存在するかをチェックする
    if (sessionList.Count > 0) {

      // リストから最初のセッションを取得する
      var session = sessionList[0];

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

      // 参加する
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // GameModeはClient、Sharedも可能
        SessionName = session.Name, // 参加するセッション
        // ...
      });
    }

    // または

    // 例
    // 指定プロパティのセッションをリストから探す

    // 対象セッションの保存枠
    SessionInfo session = null;

    foreach (var sessionItem in sessionList) {

      // 指定のカスタムプロパティをチェックする
      if (sessionItem.Properties.TryGetValue("type", out var propertyType) && propertyType.IsInt) {

        var gameType = (int)propertyType.PropertyValue;

        // 希望するゲームタイプかどうかをチェックする
        if (gameType == 1) {

          // セッション情報を保存する
          session = sessionItem;
          break;
        }
      }
    }

    // 該当セッションが存在するかチェックする
    if (session != null) {
      Debug.Log($"Joining {session.Name}");

      // 参加する
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // GameModeはClient、Sharedも可能
        SessionName = session.Name, // 参加するセッション
        // ...
      });
    }
  }
}

専用サーバーゲームモードのセットアップ

Photon Fusionをサーバーモードで実行するのは比較的簡単ですが、サーバーを実行しているホスト機のネットワーク特性によっては、異なるセットアップが必要になる場合があります。
そのためこのドキュメントでは、FusionをGameMode.Serverで開始する3つの方法を紹介します。

  1. 完全自動:ピアは、OSがソケットレイヤーに割り当てる利用可能なポートのいずれかに接続しバインドします。これは、事前にポートを定義する必要がない多くのシナリオで機能します。「STUNプロトコル」は、ピアの「パブリックエンドポイント(IP:Port)」を解決するために使用されます。例えば、家庭内のインターネット接続など、デフォルトでポートをブロックしない一般的なネットワークに適しています。

C#

NetworkRunner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Server
    // その他の引数
});
  1. 事前定義されたローカルエンドポイントと自動的なパブリックエンドポイント:ケース1の場合と似ていますが、ここでは特定の「ローカルエンドポイント」がサーバーに使用されるように設定します。指定のポートに他のサービスがバインドされている場合、ピアは開始に失敗するため、ポートが利用可能であることを確認してください。コード例では、サーバーはエンドポイント192.168.0.10:27015にバインドを試みます。これは、ホスト機のネットワークカードの1つに割り当てられたIPアドレス、かつポートが利用可能である必要があります。ローカルIPの指定は不要でポートのみを指定する場合は、NetAddress.Any(27015)(つまり0.0.0.0:27015)を使用してください。開放ポートのリストが制限されているホストのファイアウォール経由で通信(特定のポートの1つを使用)する場合に適しています。

C#

NetworkRunner.StartGame(new StartGameArgs() {
  GameMode = GameMode.Server,
  Address = NetAddress.CreateFromIpPort("192.168.0.10", 27015) // または NetAddress.Any(27015)
  // その他の引数
});
  1. 事前定義されたエンドポイント:ケース2を拡張して、ピアの「ローカルエンドポイント」と「パブリックエンドポイント」の両方を設定します。Fusionはパブリックエンドポイントを検出するSTUNプロトコルの使用を完全に無視し、渡された引数をそのまま転送します。例では、ケース2と同じローカルエンドポイントをバインドしますが、特定のパブリックエンドポイント10.0.0.1:27030も使用します。CustomPublicAddress引数は、ホスティングサービスが既にローカルエンドポイントとパブリックエンドポイント間のマッピングを提供している場合を想定しています。

C#

NetworkRunner.StartGame(new StartGameArgs() {
  GameMode = GameMode.Server,
  Address = NetAddress.CreateFromIpPort("192.168.0.10", 27015),
  CustomPublicAddress = NetAddress.CreateFromIpPort("10.0.0.1", 27030)
  // その他の引数
});
Back to top