プロトコルトークン
Boltには、プロトコルトークン
と呼ばれる機能が実装されています。これは属性データ(C#オブジェクト)を、Bolt内で行われる特定のアクションに付与するのに用いられます。以下がいくつかの例です:
- エンティティのインスタンス化
- サーバーへの接続
- シーンの読み込み
プロトコルトークンが存在している理由は、多くのケースにおいて特定のアクションが発生した際に利用できる小さなデータが必要なためです。
データが確実に存在することを保証するためのネットワーキングは本質敵に信頼できないため、データはアクション自体とともに送信する必要があります。
このため、プロトコルトークンによってこれを実現します。
特定のアクションが行われた際に必要となるデータの例として以下があります。
- プレイヤーキャラクターをインスタンス化する際に、プレイヤーが選択したキャラクターに対してカスタマイズされたオプションを送信したい場合
- サーバーに接続した際に、プレイヤーにユーザーネームとパスワードを送信してほしい場合
- 新たなシーンを読み込む際に、正しいテクスチャなどを読み込むためにシーンが読み込みを開始する際に季節(秋、冬など)を利用可能にしたい場合
プロトコルトークンは通常のC#クラスの定義により作成され、Bolt.IProtocolToken
から継承されます。
このインターフェイスは、Write
と Read
の2つのメソッドを定義します。
これらは、以下のようにクラス内にも実装されなければなりません。
C#
public class CharacterCustomization : Bolt.IProtocolToken
{
public int SkinId;
public int HatId;
public void Write(UdpKit.UdpPacket packet) {
packet.WriteInt(SkinId);
packet.WriteInt(HatId);
}
public void Read(UdpKit.UdpPacket packet) {
SkinId = packet.ReadInt();
HatId = packet.ReadInt();
}
}
ここでエンティティのインスタンス化のため、キャラクターのカスタムデータを含むプロトコルトークンを定義する必要があります。
これには、キャラクターのスキンとハットのID番号を表す、シンプルな整数フィールドが2つ含まれています。
Write
メソッドでは、渡されたパケットオブジェクト上で、適切なWriteXXX()
メソッドを呼び出してデータをパケットに書き込むこととなります。
これの逆がRead
で行われ、渡されたパケット上で適切なReadXXX()
メソッドが呼び出されることとなります。
重要: Write
メソッドおよび Read
メソッドでは、パケット上で対応するWriteXXXとReadXXXを同じ順番で呼び出さなくてはいけません。
トークンが頻繁に使われるように設計されている場合は、PooledProtocolToken
(Bolt v1.2.14以降)を実装することをお勧めします。
このタイプは通常のプロトコルトークンを拡張したもので、トークンを処理する際に割り当てられたメモリを再利用するために、内部のプーリングシステムを利用しています。
以下のとおり、プールされたバージョンを定義するための唯一の違いは、クラスを Bolt.IProtocolToken
ではなく Bolt.PooledProtocolToken
で拡張し、新しい Reset
メソッドを実装することです。
C#
public class PooledCharacterCustomization : PooledProtocolToken
{
public int SkinId;
public int HatId;
public override void Read(UdpPacket packet)
{
// Deserialize Token data
SkinId = packet.ReadInt();
HatId = packet.ReadInt();
}
public override void Write(UdpPacket packet)
{
// Serialize Token Data
packet.WriteInt(SkinId);
packet.WriteInt(HatId);
}
public override void Reset()
{
// Reset Token Data
SkinId = default(int);
HatId = default(int);
}
}
使用例
インスタンス化の例
プロトコルトークンのクラスを使用するには、それをBoltに登録する必要があります。これは、Bolt.GlobalEventListener
から継承されるUnityの挙動を作成することで実装されるBoltStartBegin
コールバックで行われます。
Bolt.GlobalEventListener
の機能に関する詳細は、"全体コールバック"の章で確認してください。
トークンクラスの登録はBoltNetwork.RegisterTokenClass<T>()
の呼び出しと、一般的なパラメーターT
としてクラスに渡すことで行われます。例は以下を確認してください。
C#
[BoltGlobalBehaviour]
public class NetworkCallbacks : Bolt.GlobalEventListener {
public override void BoltStartBegin() {
BoltNetwork.RegisterTokenClass<CharacterCustomization>();
}
}
もう一つの方法は、Protocol Token Registry
を使ってトークンを登録することです。
Bolt v1.2.14
以降では、Bolt/Protocol Token Registry
メニューでトークンレジストリを開くことができます。まだレジストリがない場合は新規作成を求められます。
レジストリが作成されたら、Refresh Protocol Token Registry
ボタンをクリックすると、Boltがプロジェクトをスキャンして、見つかった全てのIProtocolToken
をリストアップします。
これで、手動で行っていた登録コードをすべて削除して、Boltが処理を行います。
レジストリのリストに基づいて、Boltは実行時に自動的にトークンを登録し、常にすべてのピアで同じ順序で登録します。
登録後、以下の2つの方法でTokenのインスタンスを作成することができます。
Bolt.IProtocolToken
:new
キーワードを使った通常のインスタンス化です。Bolt.PooledProtocolToken
: 内部プールからインスタンスを取得するには、T
をプールされたプロトコルトークンとしたProtocolTokenUtils.GetToken<T>()
を呼び出します。
エンティティのインスタンス化の例
BoltNetwork.Instantiate
メソッドを呼び出して新しいEntity
を作成する際、CharacterCustomization
クラスのインスタンスを渡すことができます。
C#
// ...
public void SpawnCharacter()
{
var token = new CharacterCustomization();
token.SkinId = 5;
token.HatId = 12;
BoltNetwork.Instantiate(BoltPrefabs.CharacterPrefab, token);
}
// ...
Bolt.EntityBehaviour
やBolt.EntityBehaviour<T>
を継承したC#スクリプトのAttached
コールバックで、このトークンに次のようにアクセスすることができます。
C#
public class MyEntityBehaviour : Bolt.EntityBehaviour<ICharacterState> {
public override void Attached() {
var customization = (CharacterCustomization)entity.attachToken;
// ... use the customization data here
}
}
認証例
この例ではプロトコルトークンを使用し、ユーザー/パスワードのセットを送信してクライアントをサーバーで認証します。また、認証結果についてサーバーのレスポンスを受信し、プレイヤーがそれによって処理をおこなえるようにします。
この例が正常に作動するようにするため、Bolt Settings
ウィンドウ内でAccept Mode
をAuto
から Manual
に変更する必要があります。
これによって、サーバーはクライアントによって送信されたトークンを処理でき、有効であるか否かを判定します。
まず、新たに2つのプロトコルトークンを定義します:
UserToken
: 認証情報とともにクライアントによって送信されます。AuthResultToken
: 操作の結果とともにサーバーによって送信されます。
C#
using UdpKit;
namespace ProtocolTokenExample
{
public class UserToken : Bolt.IProtocolToken
{
public string Username { get; set; }
public string Password { get; set; }
public void Read(UdpPacket packet)
{
this.Username = packet.ReadString();
this.Password = packet.ReadString();
}
public void Write(UdpPacket packet)
{
packet.WriteString(this.Username);
packet.WriteString(this.Password);
}
}
}
C#
using System;
using UdpKit;
namespace ProtocolTokenExample
{
public class AuthResultToken : Bolt.IProtocolToken
{
public static AuthResultToken Invalid = new AuthResultToken();
public int Ticket { get; private set; }
public string Id { get; private set; }
private static int CurrentTicket = 0;
public AuthResultToken()
{
this.Ticket = CurrentTicket++;
this.Id = "";
}
public AuthResultToken(string id) : this()
{
this.Id = id;
}
public void Read(UdpPacket packet)
{
this.Ticket = packet.ReadInt();
this.Id = packet.ReadString();
}
public void Write(UdpPacket packet)
{
packet.WriteInt(this.Ticket);
packet.WriteString(this.Id);
}
}
}
認証システムを構築するため、サーバーおよびクライアントイベントリスナーを実装します。
これによって、クライアントからのリクエストをホストで処理できるようになります。
まず、デフォルトのBoltInit.cs
ファイルを修正します。
このファイルはBolt SDKのサンプル内に含まれています。このコードを更新し、サーバーに接続する際にUserToken
を送信するようにします。以下を参照してください:
C#
// BoltInit.cs
public override void BoltStartBegin()
{
// In order to use the Tokens, we need to register them
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
// ...
void State_SelectRoom()
{
GUI.Label(labelRoom, "Looking for rooms:", labelRoomStyle);
if (BoltNetwork.SessionList.Count > 0)
{
GUILayout.BeginVertical();
GUILayout.Space(30);
foreach (var session in BoltNetwork.SessionList)
{
var photonSession = session.Value as PhotonSession;
if (photonSession.Source == UdpSessionSource.Photon)
{
var matchName = photonSession.HostName;
var label = string.Format("Join: {0} | {1}/{2}", matchName, photonSession.ConnectionsCurrent, photonSession.ConnectionsMax);
if (ExpandButton(label))
{
// Using the UserToken to send our credential information
UserToken credentials = new UserToken();
credentials.Username = "Bob";
credentials.Password = "654321";
BoltNetwork.Connect(photonSession, credentials);
state = State.Started;
}
}
}
GUILayout.EndVertical();
}
}
// ...
コードを適切に更新すると、クライアントがゲームホストへの接続を試行した際に、ユーザー名
とパスワード
情報を渡したうえでトークンが送信されます。
このトークンをサーバー側で受信する方法が必要です。これを実現するには、ホストプレイヤー上でのみ動作するすべてのBolt.GlobalEventListener
にConnectRequest
コールバックを実装しなければなりません(たとえば、[BoltGlobalBehaviour(BoltNetworkModes.Server)]
を使用して)。以下を参照してください:
C#
using System;
using Bolt;
using UdpKit;
namespace ProtocolTokenExample
{
[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class NetworkServerCallbacks : Bolt.GlobalEventListener
{
public override void BoltStartBegin()
{
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
private bool AuthUser(string user, string pass)
{
// Authenticate your client...
return false;
}
public override void ConnectRequest(UdpEndPoint endpoint, IProtocolToken token)
{
BoltLog.Warn("Connect Request");
var userToken = token as UserToken;
if (userToken != null)
{
if (AuthUser(userToken.Username, userToken.Password))
{
AuthResultToken resultToken = new AuthResultToken(Guid.NewGuid().ToString());
BoltNetwork.Accept(endpoint, resultToken);
return;
}
}
BoltNetwork.Refuse(endpoint, AuthResultToken.Invalid);
}
}
}
最後に、サーバーから送信されたこの情報をConnectRefused
または Connected
コールバック上で、クライアント側で取得できます。これによってプレイヤーに適切なレスポンスを表示することが可能です。
これはBolt.GlobalEventListener
で実行可能ですが、今回はクライアント上でのみ作動します。
C#
using Bolt;
using UdpKit;
namespace ProtocolTokenExample
{
[BoltGlobalBehaviour(BoltNetworkModes.Client)]
public class NetworkClientCallbacks : Bolt.GlobalEventListener
{
public override void BoltStartBegin()
{
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
public override void Connected(BoltConnection connection)
{
BoltLog.Warn("Connection accepted!");
AuthResultToken acceptToken = connection.AcceptToken as AuthResultToken;
if (acceptToken != null)
{
BoltLog.Info("AcceptToken: " + acceptToken.GetType());
BoltLog.Info("Token: {0}-{1}", acceptToken.Ticket, acceptToken.Id);
}
else
{
BoltLog.Warn("AcceptToken is NULL");
}
}
public override void ConnectRefused(UdpEndPoint endpoint, IProtocolToken token)
{
BoltLog.Warn("Connection refused!");
var authToken = token as AuthResultToken;
if (authToken != null)
{
BoltLog.Warn("Token: {0}-{1}", authToken.Ticket, authToken.Id);
}
}
}
}
Back to top