Unity Transport を試す
The Unity Transport package (com.unity.transport) is a low-level networking library for multiplayer game development.
It’s the underlying protocol of both Netcode for GameObjects and Netcode for Entities, but you can also use it with a custom solution.
Unity Transport seamlessly supports all platforms the Unity Engine supports thanks to a connection-based abstraction layer (built-in network driver) provided over UDP and WebSockets.
You can set up both UDP and WebSockets with or without encryption. The following illustration shows encrypted connections with a closed padlock and unencrypted connections with an open padlock.
ブラウザベースの WebSocket は TCP だが、こっちは UDP らしい。ゲーム用途で UDP を採用したいケースは多そうなので、まあわかる。
パッケージのインストール
- Unity Editor から、
Window > Package Manager
を選択 -
Add (+) > Add package by name
を選択 - Name フィールドに
com.unity.transport
を入力
サーバの実装
以下、ほぼ公式ドキュメントのコピペ。
NetworkDriver のセットアップ
public class ServerBehaiviour : MonoBehaviour
{
private NetworkDriver _driver;
private NativeList<NetworkConnection> _connections;
void Start()
{
_driver = NetworkDriver.Create();
var endpoint = NetworkEndPoint.AnyIpv4;
endpoint.Port = 9000;
if (_driver.Bind(endpoint) != 0)
Debug.Log("Failed to bind to port 9000");
else
_driver.Listen();
_connections = new NativeList<NetworkConnection>(16, Allocator.Persistent);
}
ポート 9000 で listen を開始。NetworkEndPoint.AnyIpv4
はつまり 0.0.0.0
のことで、任意のアドレスを指す。
接続の状態はリストに保持しておく。
更新処理
void Update()
{
_driver.ScheduleUpdate().Complete();
NetworkDriver の更新。
for (int i = 0; i < _connections.Length; i++)
{
if (!_connections[i].IsCreated)
{
_connections.RemoveAtSwapBack(i);
--i;
}
}
新規の接続を処理する前に、接続リストの中から、古くなったものを一掃しておく。
NetworkConnection c;
while ((c = _driver.Accept()) != default(NetworkConnection))
{
_connections.Add(c);
Debug.Log("Accepted a connection");
}
新規の接続があるかどうかをチェック。
DataStreamReader stream;
for (int i = 0; i < _connections.Length; i++)
{
NetworkEvent.Type cmd;
while ((cmd = _driver.PopEventForConnection(_connections[i], out stream)) != NetworkEvent.Type.Empty)
{
各接続に対して PopEventForConnection
をコールして新規のイベントがあるかどうかを問い合わせる。
if (cmd == NetworkEvent.Type.Data)
{
uint number = stream.ReadUInt();
Debug.Log("Got " + number + " from the Client adding + 2 to it.");
number += 2;
_driver.BeginSend(NetworkPipeline.Null, _connections[i], out var writer);
writer.WriteUInt(number);
_driver.EndSend(writer);
}
Data
イベントのとき、uint
として読み取った値に処理を加えてクライアントに送り返す。
else if (cmd == NetworkEvent.Type.Disconnect)
{
Debug.Log("Client disconnected from server");
_connections[i] = default(NetworkConnection);
}
}
}
}
Disconnect
イベントのとき、その接続が次の Update
のタイミングで破棄されるよう、初期化する。
後処理
void OnDestroy()
{
if (_driver.IsCreated)
{
_driver.Dispose();
_connections.Dispose();
}
}
}
クライアントの実装
サーバへの接続
public class ClientBehaviour : MonoBehaviour
{
private NetworkDriver _driver;
private NetworkConnection _connection;
private bool _isDone;
void Start()
{
_driver = NetworkDriver.Create();
_connection = default(NetworkConnection);
var endpoint = NetworkEndPoint.LoopbackIpv4;
endpoint.Port = 9000;
_connection = _driver.Connect(endpoint);
}
NetworkEndPoint.LoopbackIpv4
はつまり 127.0.0.1
のこと。
更新処理
void Update()
{
_driver.ScheduleUpdate().Complete();
if (!_connection.IsCreated)
{
if (!_isDone)
Debug.Log("Something went wrong during connect");
return;
}
接続に成功したかどうかを確認。
DataStreamReader stream;
NetworkEvent.Type cmd;
while ((cmd = _connection.PopEvent(_driver, out stream)) != NetworkEvent.Type.Empty)
{
if (cmd == NetworkEvent.Type.Connect)
{
Debug.Log("We are now connected to the server");
uint value = 1;
_driver.BeginSend(_connection, out var writer);
writer.WriteUInt(value);
_driver.EndSend(writer);
}
接続が確立したタイミングで、サーバに値を送信。
else if (cmd == NetworkEvent.Type.Data)
{
uint value = stream.ReadUInt();
Debug.Log("Got the value = " + value + " back from the server");
_isDone = true;
_connection.Disconnect(_driver);
_connection = default(NetworkConnection);
}
サーバから値を受け取ったら切断する。
else if (cmd == NetworkEvent.Type.Disconnect)
{
Debug.Log("Client got disconnected from server");
_connection = default(NetworkConnection);
}
}
}
後処理
void OnDestroy()
{
_driver.Dispose();
}
}
実行結果
パイプライン
Pipelines are a core functionality of Unity Transport that allows selectively adding layers of functionality on top of the standard unreliable datagrams that UTP provides by default.
You can use pipelines to add sequencing, reliability, and fragmentation features.
Pipelines are a sequence of one or more stages. When you send a message through a pipeline, Unity Transport runs through the stages in order, piping the output of the first stage into the second stage. As a result, if the first stage adds a header to the packet, the second stage processes the entire packet, including the header added by the first stage. When you receive a message, it goes through the same chain of stages in reverse order.
信頼性の担保
UDP はその性質上、TCP と比較して信頼性に欠けるが、パイプラインを使うことで通信に信頼性を持たせることができる。
var myPipeline = _driver.CreatePipeline(typeof(ReliableSequencedPipelineStage));
_driver.BeginSend(myPipeline, connection, out var writer);
指定したパイプラインがクライアント側とサーバ側で一致していないとエラーが返る。
また、クライアント側は、Connect
する前にパイプラインを初期化しておく必要がある。