LoginSignup
2

Photon Fusion for Unityでマッチングシステムを実装する~基礎編

Posted at

はじめに

オンラインマルチプレイゲームを作る場合、プレイヤー同士のマッチングシステムを用意したい場合がほとんどだと思います。

Photon Fusionには、そのためにMatchmakingAPIというものが用意されています。

本記事では過去記事で紹介した公式サンプルを元に、MatchmakingAPIの基礎的な内容を紹介します。

前提記事

Photon Fusionの基本的な解説は以下の記事で行っていますので、そちらを参照下さい。

動作確認環境

Windows 11 Home 22H2
Unity 2022.3.2f1
Fusion SDK 1.1.8 F Build 725

MatchmakingAPIとは

プレイヤー同士をマッチングさせるための情報のやりとりや、同一セッションに接続しゲームを開始するための機能で、以下の3つが主に使われます。

  • NetworkRunner.StartGame

    基本的なメソッド

    プレイヤー同士の情報が通信できるセッションを作成、接続する

  • NetworkRunner.JoinSessionLobby

    セッションを検索するためのロビーを作成、接続する

  • SessionInfo.UpdateCustomProperties

    参加セッションの情報を更新する

また重要な用語として以下が挙げられます。

  • Game Session

    ゲームを行うための部屋のようなもの

    複数作成可能

    Lobbyの中に存在し、同Lobby内のsessionの情報を取得可能

  • Lobby

    セッションに入る前の状態

    複数作成可能

    Lobby内に作られている全てのsessionの情報を取得可能

    他のプレイヤーの情報は取得できない

以下の画像は上記のメソッドや用語がどのような関係にあるかを表した図です。

MatchmakingAPI概略図

MatchmakingAPIの使い方

先の画像からもわかるように、処理の流れは以下のようになっています

ロビーに接続する

まずはNetworkRunner.JoinSessionLobbyメソッドを使いロビーに接続します。ランダムマッチやフレンドからの招待など、セッションを探す必要がなければこの工程は飛ばして構いません。

RoomDataManager.cs
//オリジナルのスクリプト
//INetworkRunnerCallbacksを継承

private void Awake()
{
    _runner = GetComponent<NetworkRunner>();
    Connect();
}

private async void Connect()
{
    _runner.AddCallbacks(this);
    var result = await _runner.JoinSessionLobby(SessionLobby.Shared,"MyCustomLobby");

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

ロビーに接続したあとは目的のセッションを探し、次の工程へ進みます。

セッション情報はロビーにいる限り、セッション情報が更新されるたびにOnSessionListUpdatedコールバックで取得する事ができます。実装については後述します。

セッションに接続

接続するセッションを特定できたらNetworkRunner.StartGameメソッドでセッションへ参加します。GameMode、セッション名の有無、セッションの有無、これらの組み合わせで接続の挙動が変わってくるため、詳細は公式マニュアルを参照ください。

StartGameの主な引数は以下のような意味を持ちます。

  • GameMode

    ネットワーク・トポロジーを選択する

  • SessionName

    セッションに名前を設定する

    セッション名はRegion内でユニークな値

  • SessionProperties

    セッションに関するメタデータ

    Session Lobbyから常に見れる

  • CustomLobbyName

    Lobbyに名前をつける

    他のLobbyの情報は見えない

セッション・ロビーから抜ける

ゲームを終えた場合や再マッチングを行う場合など、セッションやロビーから抜けたい場面では以下の方法が使えます。

  • 別のセッションへ行く

    NetworkRunner.StartGame

  • ロビーに戻る

    NetworkRunner.JoinSessionLobby

  • ローカルに戻る

    NetworkRunner.Shutdown

MatchmakingAPIの実装例

マッチングにまつわる機能には、セッション検索やランダムマッチ、プライベートマッチなど色々なものがあります。ここでは代表的な機能を紹介します。

なお、コードの一部は公式サンプルのSocialHubを利用しています。

なおSocialHubの活用&改造例は過去記事を参考にできます。

ランダムマッチ

公式サンプルではこの方式を多く用いています。

StartGameメソッドにおいて、セッション名を指定しなければ同名ロビー内のランダムなセッションに接続します。

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

セッション情報の追加

遊び方や目的、対戦レギュレーションなどが複数ある場合、適切にマッチングを行うためにその情報をセッションが保持している必要が出てきます。

SessionPropertyはそのためのオプションで、情報を設定するには2つ方法があります。

  • StartGame時にSessionPropertyに追加する
  • セッション内でSessionInfo.UpdateCustomPropertiesメソッドで変更する

この情報を使うことで、マッチング機能を拡張したり、ランダムマッチに応用することもできます。

サンプルでは、各情報は事前にConnectionDataを経由し他の場所で設定しています。

ConnectionManager.cs
//公式サンプルSocialHub

public async Task ConnectToRunner(ConnectionData connectionData, Action<NetworkRunner> onInitialized = default, Action<ShutdownReason> onFailed = default)
{

//略

var sessionProperties = new Dictionary<string, SessionProperty>()
	  { { "ID", (int)connectionData.ID },
	      {"Target", connectionData.Target.ToString() },
	       {"Rate", connectionData.Rate.ToString() },                
	  };

//略

var startResult = await connection.Runner.StartGame(new StartGameArgs()
	  {
	      GameMode = gameMode,
	      SessionProperties = sessionProperties,
	      DisableClientSessionCreation = false,
	      Scene = scene,
	      PlayerCount = connectionData.MaxClients,
	      Initialized = onInitialized,
	      SceneManager = sceneManager,
	      ObjectPool = connection.Runner.GetComponent<INetworkObjectPool>(),
	  });
}

セッション一覧の表示や検索

ロビーに参加することでOnSessionListUpdatedコールバックによりセッションリストを取得することができます。このコールバックは、同一ロビー内に存在するセッションが更新される度に受け取ります。

リストには設定したセッション情報のSessionNameやSessionPropertiesが含まれているため、セッションの一覧を表示したりフィルタしたり、セッションに参加することができます。

実装例

RoomDataManager.cs
//オリジナルのスクリプト
//INetworkRunnerCallbacksを継承

public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
    //前略
	//初期化やフィルタリングを行う
    
    //リストデータをUIオブジェクトに流す
    for (int i = 0; i < sessionList.Count; i++)
    {
        var session = sessionList[i];
        roomDataList[i].SetData(session.Properties["ID"].PropertyValue.ToString()
            ,session.PlayerCount + "/" + session.MaxPlayers
            ,session.Name);
        roomDataList[i].GetComponent<Button>().onClick.RemoveAllListeners();
        roomDataList[i].GetComponent<Button>().onClick.AddListener(()=>Launch(session.Name));
        roomDataList[i].SetActive(true);
    }
}

なおこの機能を実装するにあたって注意することが2つあります。

1つ目は、ロビーに入っている状態もCCU(concurrent user、同時接続数)にカウントされてしまうことです。セッションに参加した状態でNetworkRunnerを生成しロビーにも参加することができますが、この場合一人で2CCUとして計算されます。

無料プランでは20CCU、ひとつ上のプランでも100CCUの制限があるため、適宜接続を切る必要があります。

2つ目はセッション情報のトラフィックです。ロビーにいる間、セッション情報に変更があるたびにOnSessionListUpdatedが呼ばれるため、ユーザー(セッション数、ロビーの人数)が増えるとトラフィックが大きくなってしまいます。そのためSessionPropertyはなるべく小さくなるようにしたほうが良いでしょう。

また、そもそもマッチングすることのない母集団、例えばプライベートマッチをしたい人、ランクマッチをしたい人、特殊ルールでランダムマッチしたい人がいた場合、予めCustomLobbyNameによって分けておく方が良いかもしれません。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2