Unity2017でMatchmakerを使ってマッチング機能を実装した

[サークルで制作中のゲーム](https://github.com/denx-jp/SSP)にマッチング機能を実装しようとしたとき、UnityのMatchmakerの具体的な手順をかいた記事があまり見つからなかったので誰かの助けになればと思ったのでかきます。

## Unity MatchMakerとは
[tanaka's Programming Memo](http://am1tanaka.hatenablog.com/entry/2015/08/16/121943)によると
> (Unityの)多人数プレイネットワークには、パブリックIPアドレスを知らなくてもインターネット上でプレイヤー同士が集える機能が用意されています。ユーザーはゲームを作成することができ、有効なゲームのリストを取得し、それに参加したり離れたりできます。インターネット上で遊ぶ時、ネット通信はクライアント間で直接やりとりされるのではなく、Unityが提供するクラウドのサーバーで中継されます。これによりファイヤーウォールやNATなどによる問題を回避できます。

だそうです。
個人的にはunetで他のプレイヤーと接続したいときにいちいちホストのIPアドレスを調べるのは面倒なので、そこらへんをよしなにやってくれるUnityが提供するクラウドサービスぐらいの認識です。

## 事前準備
1. UnityのServicesウィンドウ → MULTI PLAYER → Go to dashboardの手順でunity developer cloudを開く。

2. こんな感じの画面がブラウザで表示されるので、1マッチの最大プレイヤー数を入力してSave。これで事前準備は完了。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125635/3cca682c-14d3-26d8-e156-fc9111930294.png)

## マッチング実装手順
1. マッチング用シーンと通信するシーンを適当に作って、Build SettingsからScenes In Buildに設定しておく。画像のやつはタイトルシーンも含めちゃってます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125635/6d8e7f9c-a4ea-d286-eed2-511eb71f96e1.png)

1. Matchシーンで空のオブジェクトを作ってNetworkManagerをアタッチして、NetworkManagerのOffline SceneにMatching、Online SceneにNetworkを設定。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125635/c77a65cd-40f1-b0b0-2400-f3a7105d351a.png)

1. 適当にプレイヤー用プレハブを作ってNetworkIdentityをアタッチする。プレイヤー周りの実装はこの記事ではかきません。[ここらへん](http://hiyotama.hatenablog.com/entry/2015/07/06/153000)を参考に作ってみてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125635/6f29c89b-381a-aae6-d7bb-c151599ff350.png)

1. そのプレハブをNetworkManagerのPlayer Prefabに設定する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125635/2de14a99-6eff-90d4-678f-3c798fec5b6e.png)

1. MatchMakerみたいな名前の適当なスクリプトを作り、先ほどNetworkManagerをアタッチしたオブジェクトにアタッチして公式リファレンスを参考にMatchmaker.csに処理をかく。ここで注意したいのは、[5.4系のドキュメント](https://docs.unity3d.com/jp/540/ScriptReference/Networking.Match.NetworkMatch.html)にかいてあるコードはunity2017だと動かないこと。なので、[Unity2017系のドキュメント](https://docs.unity3d.com/ja/current/ScriptReference/Networking.Match.NetworkMatch.html)を参考に処理を記述。下記のスクリプトは公式ドキュメントを少し改造してMatchシーンが始まったと同時にマッチを検索し、マッチがないor人数がいっぱいのマッチしかない場合は新しくマッチを作るようになっています。

  ```Matchmaker.cs
  using System.Collections;
  using System.Collections.Generic;
  using UnityEngine;
  using UnityEngine.UI;
  using UnityEngine.Networking;
  using UnityEngine.Networking.Match;
  using UnityEngine.SceneManagement;

  public class MatchMaker : MonoBehaviour
  {
      [SerializeField] string titleSceneName;
      [SerializeField] Text text;

      void Start()
      {
          NetworkManager.singleton.StartMatchMaker();
          FindInternetMatch("");
      }

      #region マッチ作成
      public void CreateInternetMatch()
      {
          text.text = "Create Match...";

          var matchName = "";
          uint matchSize = 6;                //マッチのプレイヤーの最大人数
          var matchAdvertise = true;         //NetworkMatch.ListMatchesで帰ってくるList<MatchInfoSnapshot>に、このマッチを含めるかどうか
          var matchPassword = "";            //マッチのパスワード
          var publicClientAddress = "";      //クライアントがインターネット経由で直接接続するためのネットワークアドレス
          var privateClientAddress = "";     //クライアントが LAN 経由で直接接続するためのネットワークアドレス
          var eloScoreForMatch = 0;          //いわゆるスキルレート。全クライアントが0だとランダムになる
          var requestDomain = 0;             //クライアントバージョンを区別するための番号

          NetworkManager.singleton.matchMaker.CreateMatch(matchName, matchSize, matchAdvertise, matchPassword, publicClientAddress, privateClientAddress, eloScoreForMatch, requestDomain, OnInternetMatchCreate);
      }

      private void OnInternetMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
      {
          if (success)
          {
              MatchInfo hostInfo = matchInfo;
              NetworkServer.Listen(hostInfo, 9000);

              NetworkManager.singleton.StartHost(hostInfo);
          }
          else
          {
              text.text = "Create match failed";
              SceneManager.LoadScene(titleSceneName);
          }
      }
      #endregion

      public void FindInternetMatch(string matchName)
      {
          var startPageNumber = 0;                         //リストし始めるページ
          var resultPageSize = 10;                         //callbackに渡すリストのマッチの最大数
          var matchNameFilter = matchName;                 //*<matchNameFilter>*に該当するマッチが検索される
          var filterOutPrivateMatchesFromResults = true;   //プライベートマッチを検索結果に含めるかどうか
          var eloScoreTarget = 0;                          //検索するときのスキルレート
          var requestDomain = 0;                           //クライアントバージョンを区別するための番号

          text.text = "Searching Match...";

          NetworkManager.singleton.matchMaker.ListMatches(startPageNumber, resultPageSize, matchNameFilter, filterOutPrivateMatchesFromResults, eloScoreTarget, requestDomain, OnJoinInternetMatch);
      }

      private void OnJoinInternetMatch(bool success, string extendedInfo, List<MatchInfoSnapshot> matches)
      {
          if (success)
          {
              if (matches.Count == 0)
                  CreateInternetMatch();
              else
              {
                  text.text = "Join Match";
                  var earliestCreatedMatch = matches.Find(v => v.currentSize != v.maxSize);
                  NetworkManager.singleton.matchMaker.JoinMatch(earliestCreatedMatch.networkId, "", "", "", 0, 0, OnConnectMatch);
              }
          }
          else
          {
              text.text = "Couldn't connect to match maker";
              SceneManager.LoadScene(titleSceneName);
          }
      }

      private void OnConnectMatch(bool success, string extendedInfo, MatchInfo matchInfo)
      {
          if (success)
          {
              MatchInfo hostInfo = matchInfo;
              NetworkManager.singleton.StartClient(hostInfo);
          }
          else
          {
              CreateInternetMatch();
          }
      }
  }
  ```

1. ゲームを開始して自動でマッチングされれば成功。[参考動画](https://youtu.be/-_OR-3cjswg)


以上がUnityのMatchmakerを使ってマッチング機能を実装する手順でした。記事用に新しくプロジェクトを作ったとかではなく、いま作ってるゲームの画面とコードを適当に記載しただけなので分かりずらいかもしれませんが勘弁してください。。。
疑問があればコメントか[@mn_chorome](https://twitter.com/mn_chorome)にリプとばしてもらえれば返答します。