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.BasicNetworkDriver
がUnity.Networking.Transport.GenericNetworkDriver
に置き換わったのでそこを書き換える。
Unity.Networking.Transport.GenericNetworkDriver
は最新版で新しく導入されたpipelinesをテンプレート引数に指定する必要がある。
コードを検索したところUnity.Networking.Transport.DefaultPipelineStageCollection
が妥当そうだったのでそれを指定している。
pipelinesはレイヤーを使ってソケット実装のふるまいを指定できるものとかないとか。
信頼性があるReliableSequencedPipelineStage
と高レイテンシー環境をシミュレーションするためのSimulatorPipelineStage
などがあらかじめ用意されている。
ReliableSequencedPipelineStage
などを使うときはUnity.Networking.Transport.GenericNetworkDriver
を作成時にパラメータを渡す必要がある。
が、NullPipelineStage
も提供されているので今回はこれを使っている。
あと、System.Net
はUnity.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が新しく追加されたのでその周りを修正するだけという感じでした。