PUN Classic (v1), PUN 2, Bolt는 휴업 모드입니다. Unity2022에 대해서는 PUN 2에서 서포트하지만, 신기능의 추가는 없습니다. 현재 이용중인 고객님의 PUN 및 Bolt 프로젝트는 중단되지 않고, 퍼포먼스나 성능이 떨어지는 일도 없습니다. 앞으로의 새로운 프로젝트에는 Photon Fusion 또는 Quantum을 사용해 주십시오.

상세 해설 - 프로토콜 토큰

Bolt에는 프로토콜 토큰이라고 하는 기능을 가지고 있는데 Bolt에서 발생할 수 있는 특정 액션으로 독단적인 데이터(C# 객체들)를 붙이기 위해서 사용되는 것은 아래와 같은 것 입니다:

  • 엔티티 인스턴스화
  • 서버에 접속
  • 씬 로딩

프로토콜 토큰이 존재하는 이유는 많은 경우에 특정 작업이 발생할 때 작은 데이터 조각을 사용하기를 원할 때가 있기 때문입니다.
네트워크의 특성상 데이터의 신뢰성이 보장되지 않기 때문에 액션 자체와같이 전송되어야 합니다.
프로토콜 토큰을 사용하여 이것을 수행할 수 있습니다.

특정 작업이 수행될 때 사용할 수 있는 데이터의 예는 다음과 같습니다:

  • 플레이어 캐릭터를 인스턴스화 할 때 플레이어가 선택한 커스터마이징된 옵션 전송을 원할 때.
  • 서버에 접속 시 플레이어가 사용자명과 비밀번호를 전송하기 원할 때.
  • 씬이 로딩을 시작할 때 계절(가을, 겨울 등)에 대한 올바른 텍스쳐를 로드하기를 원하는 경우

프로토콜 토큰은 일반적인 C# 클래스를 정의하여 생성되고 Bolt.IProtocolToken 인터페이스에서 상속 됩니다.
이 인터페이스는 두 개의 메소드를 정의합니다: WriteRead 입니다.
이 메소드들은 클래스 내에서 구현할 필요가 있습니다.

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 번호와 모자 id를 나타냅니다.

Write 메소드에서 전달할 패킷 객체에 WriteXXX() 메소드를 호출하여 패킷으로 데이터를 씁니다.
반대로 Read 에서는 전달된 패킷에 ReadXXX() 를 호출 합니다.

중요: WriteXXXReadXXX 는 상응되는 WriteRead 메소드에서 동일한 순서로 호출 해야 합니다.

이제 토큰이 자주 사용하도록 설계된 경우 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);
    }
}

사용 예제

기본적인 사용

Protocol Token 클래스를 사용하려면 Bolt에 등록해야 합니다. 이 작업은 Bolt.GlobalEventListener에서 상속되는 Unity behaviour를 생성하여 구현할 수 있는 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 버튼을 클릭하면 볼트가 프로젝트를 스캔하고 모든 IProtocolToken을 나열합니다.
이게 다 입니다. 수동 등록 코드를 모두 제거하고 Bolt에게 맡기면 돼요.
레지스트리 목록에 따라 볼트는 런타임에 모든 피어에서 항상 동일한 순서로 토큰을 자동으로 등록합니다.

Enable Source Provider
Protocol Token Registry

등록 후 다음 두 가지 방법으로 토큰 인스턴스를 생성할 수 있습니다.

  1. Bolt.IProtocolToken: new 키워드를 사용한 일반적인 인스턴스화입니다.;
  2. Bolt.PooledProtocolToken: 내부 풀에서 인스턴스를 얻기 위해서, 간단하게 ProtocolTokenUtils.GetToken<T>()를 호출하기만 하면 되며, T는 풀된 프로토콜 토큰 유형입니다.

엔티티 인스턴스화 예제

새로운 엔티티 생성을 위해 BoltNetwork.Instantiate 메소드를 호출 할 때 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
    }
}

인증 예제

이 예에서는 Protocol Token을 사용하여 사용자/암호 페어를 전송하여 서버를 통해 클라이언트를 인증하고 인증 결과에 대한 서버의 응답을 수신하여 플레이어가 적절하게 처리할 수 있도록 합니다.

이 예제가 제대로 작동하려면 Bolt 설정 창에서 수동자동에서 수동으로 변경해야 합니다.
이렇게 하면 서버는 클라이언트가 보낸 토큰을 처리하고 토큰이 유효한지 여부를 결정할 수 있습니다.

먼저 다음 두 가지 새 프로토콜 토큰을 정의합니다.

  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 파일을 수정할 것입니다.
서버에 연결하려고 할 때, 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 콜백을 구현해야 합니다(). 다음을 참고하세요:

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