1
1

More than 5 years have passed since last update.

Unity multiplayer workflow-client-serverの変更点

Posted at

Unityのネットワーク機能として提供されていたUNetが廃止されるとのことで、
その代わりになるであろうUnityのGithubで公開されているmultiplayerを少し触ってみたところ、
書かれている部分が最新のコードと一致していなかったので修正した部分をメモしていく。

Unity multiplayerはUNetの低レベル部分を置き換えるものになるそうだ。

インストール

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

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

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

workflow-client-server.mdについて

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

修正点

サーバー側

usingのBasicNetworkDriverをGenericNetworkDriverに置き換え

- 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.BasicNetworkDriverUnity.Networking.Transport.GenericNetworkDriverに置き換わったのでそこを書き換える。
Unity.Networking.Transport.GenericNetworkDriverは最新版で新しく導入されたpipelinesをテンプレート引数に指定する必要がある。
コードを検索したところUnity.Networking.Transport.DefaultPipelineStageCollectionが妥当そうだったのでそれを指定している。

pipelinesはレイヤーを使ってソケット実装のふるまいを指定できるものとかないとか。

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

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

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

Start()とOnDestroy()

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

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

// 接続設定に使うクラスの変更と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についてはこちら

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を指定したのと同じになる。

// 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が新しく追加されたのでその周りを修正するだけという感じでした。

1
1
0

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
1
1