Unity multiplayer workflow-client-serverの変更点


[Unityのネットワーク機能として提供されていたUNetが廃止されるとのこと](https://blogs.unity3d.com/jp/2018/08/02/evolving-multiplayer-games-beyond-unet/?_ga=2.103543488.1373694563.1558076711-325351080.1556269341)で、
その代わりになるであろうUnityのGithubで公開されている[multiplayer](https://github.com/Unity-Technologies/multiplayer)を少し触ってみたところ、
書かれている部分が最新のコードと一致していなかったので修正した部分をメモしていく。

[Unity multiplayer](https://github.com/Unity-Technologies/multiplayer)はUNetの低レベル部分を置き換えるものになるそうだ。


## インストール

GithubからリポジトリをクローンしてUnityプロジェクトの`Packages/manifest.json`に以下のものを追記すればOK。

相対パスとして指定しているのでリポジトリのクローンする場所に合わせてパスも変更すること。
(以下のだとクローンしたリポジトリとUnityプロジェクトは同じディレクトリにある)

```json
{
  "dependencies": {
+    "com.unity.transport": "file:../../multiplayer/com.unity.transport",
  }
}
```

## workflow-client-server.mdについて

クライアントがサーバーに数値を送って、サーバーが適当な数を足してクライアントに返すという処理を作成する簡単なチュートリアル。

## 修正点

### サーバー側

#### usingのBasicNetworkDriverをGenericNetworkDriverに置き換え

```c#
- using System.Net;

// BasicNetworkDriver -> GenericNetworkDriver
- using UdpCNetworkDriver = Unity.Networking.Transport.BasicNetworkDriver<Unity.Networking.Transport.IPv4UDPSocket>;
+ using UdpCNetworkDriver = Unity.Networking.Transport.GenericNetworkDriver<Unity.Networking.Transport.IPv4UDPSocket, Unity.Networking.Transport.DefaultPipelineStageCollection>;
```

`Unity.Networking.Transport.BasicNetworkDriver`が`Unity.Networking.Transport.GenericNetworkDriver`に置き換わったのでそこを書き換える。
`Unity.Networking.Transport.GenericNetworkDriver`は最新版で新しく導入された[pipelines](https://github.com/Unity-Technologies/multiplayer/blob/master/com.unity.transport/Documentation/pipelines-usage.md)をテンプレート引数に指定する必要がある。
コードを検索したところ`Unity.Networking.Transport.DefaultPipelineStageCollection`が妥当そうだったのでそれを指定している。

[pipelines](https://github.com/Unity-Technologies/multiplayer/blob/master/com.unity.transport/Documentation/pipelines-usage.md)はレイヤーを使ってソケット実装のふるまいを指定できるものとかないとか。

信頼性がある`ReliableSequencedPipelineStage`と高レイテンシー環境をシミュレーションするための`SimulatorPipelineStage`などがあらかじめ用意されている。
`ReliableSequencedPipelineStage`などを使うときは`Unity.Networking.Transport.GenericNetworkDriver`を作成時にパラメータを渡す必要がある。

が、`NullPipelineStage`も提供されているので今回はこれを使っている。

あと、`System.Net`は`Unity.Networking.Transport.NetworkEndPoint`を使うようになっているので必要なくなっている。

#### Start()とOnDestroy()

上で見たように使用しているクラスが変わったので処理も少し変わる。

といっても使うクラスが変わっただけで処理内容は変わらない。

```c#
// 接続設定に使うクラスの変更とpipelineの生成を行う

public UdpCNetworkDriver m_Driver;
private NativeList<NetworkConnection> m_Connections;
+ NetworkPipeline m_Pipeline;

void Start()
{
    m_Driver = new UdpCNetworkDriver(new INetworkParameter[0]);
//接続情報がNetworkEndPointを使う形に変わった
-   if (m_Driver.Bind(new IPEndPoint(IPAddress.Any, 9000)) != 0) {
+   var endpoint = NetworkEndPoint.AnyIpv4;
+   endpoint.Port = 9000;
+   if (m_Driver.Bind(endpoint) != 0) {
        Debug.Log("Failed to bind to port 9000");
    } else {
        m_Driver.Listen();
    }
    m_Pipeline = new m_Driver.CreatePipeline(typeof(NullPipelineStage));

    m_Connections = new NativeList<NetworkConnection>(16, Allocator.Persistent);
}

void OnDestroy() {
    //pipelineはDisposeする必要はないみたい
    m_Driver.Dispose();
    m_Connections.Dispose();
}
```

#### Update()

multiplayerはUnity Job Systemを内部で利用しているので、チュートリアルでは同期待ちを行っている。

Update関数内では修正部分はあまりなかったが、クライアントへデータを送信するときにpipelineを指定する必要がある。
(NetworkPipeline.NullでもOK)
[pipelinesについてはこちら](https://github.com/Unity-Technologies/multiplayer/blob/master/com.unity.transport/Documentation/pipelines-usage.md)

```c#
void Update()
{
    // Job Systemの同期を行う
    m_Driver.ScheduleUpdate().Complete();

    // 古くなった接続を取り除く
    for (int i=0; i < m_Connections.Length; ++i) {
        if (!m_Connections[i].IsCreated) {
            m_Connections.RemoveAtSwapBack(i);
            i--;
        }
    }

    // 新しい接続があるなら追加する
    NetworkConnection c;
    while((c = m_Driver.Accept()) != default(NetworkConnection)) {
        m_Connections.Add(c);
        Debug.Log("Accepted a connection");
    }

    // 受信したクライアントからのデータを処理する
    DataStreamReader stream;
    for (int i=0; i<m_Connections.Length; ++i) {
        if (!m_Connections[i].IsCreated) {
            continue;
        }

        NetworkEvent.Type cmd;
        while((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != NetworkEvent.Type.Empty) {
            if (cmd == NetworkEvent.Type.Data) {
                //適当な数値を足してクライアントに返送する
                var readerCtx = default(DataStreamReader.Context);

                uint number = stream.ReadUInt(ref readerCtx);
                Debug.Log("Got " + number + " from the Client adding + 2 to it.");
                number += 2;

                using (var writer = new DataStreamWriter(4, Allocator.Temp)) {
                    writer.Write(number);
-                    m_Driver.Send(m_Connections[i], writer);
+                    m_Driver.Send(m_Pipeline, m_Connections[i], writer);
                }
            } else if (cmd == NetworkEvent.Type.Disconnect) {
                //　切断処理
                Debug.Log("Client disconnected from server");
                m_Connections[i] = default(NetworkConnection);
            }
        }
    }
}
```

サーバーサイドはこれで完成。

次はクライアントサイドを作る。

### クライアント側

といってもStart()内でNetworkEndPointを使うようにする、pipelineを使うぐらいであまり修正点はなかった。

ちなみにNetworkConnection.Sendでpipelineを渡さなくてもいい。

そのときは`NetworkPipeline.Null`を指定したのと同じになる。

```c#
// usingはサーバーと同じ
void Start()
{
    m_Driver = new UdpCNetworkDriver(new INetworkParameter[0]);
    m_Connection = default(NetworkConnection);
+    m_Pipeline = m_Driver.CreatePipeline(typeof(NullPipelineStage));

-    var endpoint = new IPEndPoint(IPAddress.Loopback, 9000);
+    var endpoint = NetworkEndPoint.LoopbackIpv4;
+    endpoint.Port = 9000;
    m_Connection = m_Driver.Connect(endpoint);
}

// ...

void Update()
{
    // ...
    DataStreamReader stream;
    NetworkEvent.Type cmd;
    while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty) {
        if (cmd == NetworkEvent.Type.Connect) {
            Debug.Log("We are now connected to the server");

            var value = 1;
            using (var writer = new DataStreamWriter(4, Allocator.Temp)) {
                writer.Write(value);
-                m_Connection.Send(m_Driver, writer);
+                m_Connection.Send(m_Driver, m_Pipeline, writer);
            }
        } // ...
    }
}

```

## 終わり

GenericNetworkDriverとNetworkPipeline、NetworkEndPointが新しく追加されたのでその周りを修正するだけという感じでした。