プロトコルトークン
Boltには、プロトコルトークン
と呼ばれる機能が実装されています。これは属性データ(C#オブジェクト)を、Bolt内で行われる特定のアクションに付与するのに用いられます。
以下がいくつかの例です:
- エンティティのインスタンス化
- サーバーへの接続
- シーンの読み込み
プロトコルトークンは、多くのケースにおいて特定のアクションが発生した際に用いることのできる小さなデータが必要なため存在しています。 データが確実に存在することを保証するためのネットワーキングはその本質として信頼できないため、データはアクション自体とともに送信する必要があります。 このため、プロトコルトークンによってこれを実現します。
特定のアクションが行われた際に必要となるデータの例として以下があります。
- プレイヤーキャラクターをインスタンス化する際に、プレイヤーが選択したキャラクターに対してカスタマイズされたオプションを送信したい場合
- サーバーに接続した際に、プレイヤーにユーザーネームとパスワードを送信してほしい場合
- 新たなシーンを読み込む際に、正しいテクスチャなどを読み込むためにシーンが読み込みを開始する際に季節(秋、冬など)を利用可能にしたい場合
プロトコルトークンは通常のC#クラスの定義により作成され、Bolt.IProtocolToken
から継承されます。
このインターフェイスは、Write
と Read
の2つのメソッドを定義します。
これらは、以下のようにクラス内にも実装されなければなりません。
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を同じ順番で呼び出さなくてはいけません。
使用例
インスタンス化の例
プロトコルトークンのクラスを用いるには、それをBoltに登録する必要があります。これは、Bolt.GlobalEventListener
から継承されるUnityの挙動を作成することで実装されるBoltStartBegin
コールバックで行われます。Bolt.GlobalEventListener
の機能に関する詳細は、"全体コールバック"の章で確認してください。
トークンクラスの登録はBoltNetwork.RegisterTokenClass<T>()
の呼び出しと、一般的なパラメーターT
としてクラスに渡すことで行われます。例は以下を確認してください。
[BoltGlobalBehaviour]
public class NetworkCallbacks : Bolt.GlobalEventListener {
public override void BoltStartBegin() {
BoltNetwork.RegisterTokenClass<CharacterCustomization>();
}
}
以上の設定でBoltNetwork.Instantiate
メソッドを呼び出した際には、CharacterCustomization
クラスのインスタンスを渡すことができるようになりました。
// ...
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
コールバック内でこのトークンにアクセスできます。
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
: 操作の結果とともにサーバーによって送信されます。
using UdpKit;
namespace ProtocolTokenExample
{
public class UserToken : Bolt.IProtocolToken
{
public string username;
public string password;
public UserToken(string username, string password)
{
this.username = username;
this.password = password;
}
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);
}
}
}
using System;
using UdpKit;
namespace ProtocolTokenExample
{
public class AuthResultToken : Bolt.IProtocolToken
{
public static AuthResultToken Invalid = new AuthResultToken("");
private static int CurrentTicket = 0;
private int ticket;
private string id;
public AuthResultToken(string id = "")
{
this.ticket = CurrentTicket++;
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
を送信するようにします。以下を参照してください:
// BoltInit.cs
public override void BoltStartBegin()
{
// In order to use the Tokens, we need first to register
// then whitin Bolt
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)]
を使用して)。
以下を参照してください:
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
で実行可能ですが、今回はクライアント上でのみ作動します。
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);
}
}
}
}