PUN Classic (v1)、PUN 2、Boltはメンテナンスモードとなっております。Unity2022についてはPUN 2でサポートいたしますが、新機能が追加されることはありません。お客様のPUNプロジェクトおよびBoltプロジェクトが停止することはなく、将来にわたってパフォーマンス性能が落ちることはありません。 今後の新しいプロジェクトについては、Photon FusionまたはQuantumへ切り替えていただくようよろしくお願いいたします。

プロトコルトークン

Boltには、プロトコルトークンと呼ばれる機能が実装されています。これは属性データ(C#オブジェクト)を、Bolt内で行われる特定のアクションに付与するのに用いられます。以下がいくつかの例です:

  • エンティティのインスタンス化
  • サーバーへの接続
  • シーンの読み込み

プロトコルトークンが存在している理由は、多くのケースにおいて特定のアクションが発生した際に利用できる小さなデータが必要なためです。
データが確実に存在することを保証するためのネットワーキングは本質敵に信頼できないため、データはアクション自体とともに送信する必要があります。
このため、プロトコルトークンによってこれを実現します。

特定のアクションが行われた際に必要となるデータの例として以下があります。

  • プレイヤーキャラクターをインスタンス化する際に、プレイヤーが選択したキャラクターに対してカスタマイズされたオプションを送信したい場合
  • サーバーに接続した際に、プレイヤーにユーザーネームとパスワードを送信してほしい場合
  • 新たなシーンを読み込む際に、正しいテクスチャなどを読み込むためにシーンが読み込みを開始する際に季節(秋、冬など)を利用可能にしたい場合

プロトコルトークンは通常のC#クラスの定義により作成され、Bolt.IProtocolTokenから継承されます。
このインターフェイスは、WriteReadの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は実行時に自動的にトークンを登録し、常にすべてのピアで同じ順序で登録します。

Enable Source Provider
プロトコルトークンの登録

登録後、以下の2つの方法でTokenのインスタンスを作成することができます。

  1. Bolt.IProtocolToken: new キーワードを使った通常のインスタンス化です。
  2. 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.EntityBehaviourBolt.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 ModeAuto から Manualに変更する必要があります。
これによって、サーバーはクライアントによって送信されたトークンを処理でき、有効であるか否かを判定します。

まず、新たに2つのプロトコルトークンを定義します:

  1. UserToken: 認証情報とともにクライアントによって送信されます。
  2. 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.GlobalEventListenerConnectRequestコールバックを実装しなければなりません(たとえば、[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