MirrorとLiteNetLib4Mirrorを使って、UnityでのNetwork Discoveryを行うメモ

# はじめに
LiteNetLib4Mirrorとは、[LiteNetLib](https://github.com/RevenantX/LiteNetLib)という.NET用のUDPライブラリを、Unity用ネットワークライブラリ[Mirror](https://github.com/vis2k/Mirror)のLow level networking transportとして使えるようにするものです。特に、Mirrorには無い**Network Discovery**と**uPnP**をサポートしてくれるのが嬉しいので、その辺の話を中心にメモします。なお、原稿執筆時の**LiteNetLib4Mirrorのバージョンは1.2.5**です。

# LiteNetLib4Mirrorの入手

[こちら](https://github.com/MichalPetryka/LiteNetLib4Mirror/releases)からUnityPackageをダウンロードして、自分のプロジェクトにインポートしてください。ただし、これにはサンプルシーンが含まれないので、初心者は[こちらのレポジトリ](https://github.com/MichalPetryka/LiteNetLib4Mirror)をクローンして試してみるのが良いと思います。使うUnityのバージョンはUnity 2018.3.6f1以降が推奨です（IL2CPPでのIPv6サポートの問題のため）。私は執筆時点でUnity2018.3.8f1を使ってます。

# サンプルシーンから学ぶ

公式ドキュメントが少ないので、サンプルシーンを勉強してみましょう。

レポジトリをクローンしてプロジェクトを開くとAssetフォルダに"Scene"というシーンのファイルがあるので開いてください。Mirrorに付属のPongデモと同じようなシーンが開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/91fb21c0-eae6-f18b-8937-1cd2e0f925d5.png)

ただし、NetworkManagerゲームオブジェクトにアタッチされてるコンポーネントに、以下のような違いがあります。

## LiteNetLib4MirrorTransport

MirrorのPongデモではTelepathy Transportがアタッチされてますが、こちらではLiteNetLib4MirrorTransportがアタッチされています。このコンポーネントはLiteNetLib4Mirrorを使う上で必須であり、必ずNetwork Managerと同じゲームオブジェクトにアタッチされている必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/7d383de5-d53e-d7a9-df60-e9c617ee9924.png)

コンポーネントのパラメータについては、以下の参考資料が役に立つかもしれません。

[127.0.0.1とlocalhostと0.0.0.0の違い](https://qiita.com/1ain2/items/194a9372798eaef6c5ab)
[UPnPでNAT越え ~オンラインゲーム開発における課題~](https://qiita.com/younaship/items/5dd08a10aeeb5cd55d81)

## LiteNetLib4MirrorDiscovery

Network Discoveryを行うためのコンポーネントです。On Discovery Responseを設定しておくことで、Hostが見つかった後の処理を行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/ad9b247c-3635-a71e-9b67-b988df9b776c.png)

## NetworkDiscoveryHUD

これがあると、シーンに"Start Discovery"というボタンが表示され、これを**Client側**で押すとHostを探し始めてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/a1b1b17c-9466-0bfd-4d22-3278ecc8a095.png)

## Network Manager

このコンポーネント自体はMirrorのPongデモにもありますが、**Network Infoの"Transport"がLiteNetLib4MirrorTransport**になっているのに注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/3ce3721d-1cc3-1c0b-f718-ec228679c692.png)

## ためしてみる
とりあえず、プロジェクトをビルドして試してください。ビルドしたアプリを二つ起動させて、一方をClient、一方をHostにしてみましょう。Host側で"LAN Host"、Client側で"Start Discovery"を押せば接続されてPongが始まります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/a29c17ec-cca3-8684-499d-fd8a086e192f.png)

# Network Discoveryの方法
サンプルシーンのNetworkDiscoveryHUD.csが参考になります。この中のOnGUIメソッドの中に、Discoveryを始めるか止めるかの処理があります。コード中の_noDiscoveringは、初期状態ではtrueです。

```NetworkDiscoveryHUD.cs
//OnGUI内
if (!NetworkClient.isConnected && !NetworkServer.active){
	if (_noDiscovering){
		if (GUILayout.Button("Start Discovery")){
			StartCoroutine(StartDiscovery());
		}
	}
	else{
		GUILayout.Label("Discovering..");
		GUILayout.Label($"LocalPort: {LiteNetLib4MirrorTransport.Singleton.port}");
		if (GUILayout.Button("Stop Discovery")){
			_noDiscovering = true;
		}
	}
}
else{
	_noDiscovering = true;
}
```

"Start Discovery"ボタンを押すと、StartDiscoveryメソッドがコルーチンとして実行されます。

StartDiscoveryメソッドでは、LiteNetLib4MirrorDiscoveryコンポーネントのInitializeFinder()で初期化し、Singleton.onDiscoveryResponse.AddListenerでOnClientDiscoveryResponseをイベントハンドラとして登録した後に、LiteNetLib4MirrorDiscovery.SendDiscoveryRequestでリクエストを送信し続けます。

```NetworkDiscoveryHUD.cs
private IEnumerator StartDiscovery()
{
	_noDiscovering = false;

	LiteNetLib4MirrorDiscovery.InitializeFinder();
	LiteNetLib4MirrorDiscovery.Singleton.onDiscoveryResponse.AddListener(OnClientDiscoveryResponse);
	while (!_noDiscovering)
	{
		LiteNetLib4MirrorDiscovery.SendDiscoveryRequest("NetworkManagerHUD");
		yield return new WaitForSeconds(discoveryInterval);
	}

	LiteNetLib4MirrorDiscovery.Singleton.onDiscoveryResponse.RemoveListener(OnClientDiscoveryResponse);
	LiteNetLib4MirrorDiscovery.StopDiscovery();
}
```
サンプルでは、LiteNetLib4MirrorDiscovery.SendDiscoveryRequestの引数として"NetworkManagerHUD"を与えてるのですが、他の文字列を与えても動作に影響しないです。理由は調査中です 

(追記: shienaさんのコメントにあるとおり、与えた引数はホスト側のLiteNetLib4MirrorDiscoveryのProcessDiscoveryRequestに渡されるようです。ProcessDiscoveryRequestの中では渡された文字列を使わないので何を与えていても影響ないですが、LiteNetLib4MirrorDiscoveryを継承したクラスを作る場合、この文字列を使って処理を行うこともできます。)


いずれにせよDiscoveryが上手くいくと、登録したOnClientDiscoveryResponseにホストのIP Addressが渡されるので、Mirrorの通常の方法でクライアントを起動できます。

```NetworkDiscoveryHUD.cs
private void OnClientDiscoveryResponse(IPEndPoint endpoint, string text)
{
	string ip = endpoint.Address.ToString();
	NetworkManager.singleton.networkAddress = ip;
	NetworkManager.singleton.maxConnections = 2;
	LiteNetLib4MirrorTransport.Singleton.clientAddress = ip;
	LiteNetLib4MirrorTransport.Singleton.port = (ushort)endpoint.Port;
	LiteNetLib4MirrorTransport.Singleton.maxConnections = 2;
	NetworkManager.singleton.StartClient();
	_noDiscovering = true;
}
```

#おわりに

多分、もっと色々なことができるんだと思いますが、サンプルから理解できたのはこれくらいです。公式サンプルが追加されたら、こちらにも情報追加します。