유니티 Netcode for GameObject, Connection approval

HandShake는 클라이언트의 NetworkConfig가 서버의 NetworkConfig에 일치됨을 보장합니다
 
NetworkManager 또는 NetworkManager.NetworkConfig.ConnectionApproval를 true로 코드 세팅함으로써 ConnectoinApproval를 가능하게 할 수 있습니다

Connection Approval는 연결별로 연결을 허락할지 말지 결정할 수 있게 해줍니다
 
Connection approval는 기본적인 NetworkManager에 플레이어마다 기본적으로 등록한  Player Prefab에 override를 허락해줌으로써, 특정한 Player Prefab을 만드는 것 또한 가능하게 해줍니다

NetworkManager의 ConnectionApproval의 속성을 true로 세팅하지 않으면 Netcode는 기본적인 유저 인증을 사용해서, 자동적으로 기본적인 player prefab을 사용하도록 합니다
NetworkManager.ConnectionApprovalCallback


 

NetworkManager.ConnectionApprovalRequest

이 함수는 다음을 포함합니다!

  • ClientNetworkId: 연결하는 클라이언트 identifier
  • Payload: 추가 사용자 정의 연결 데이터

NetworkManager.ConnectionApprovalResponse


서버측에서, 이 함수에 연결을 시도하는 플레이어를 허용하거나 거부하는데 필요한 모든 연결 승인 응답에 관한 정보가 있습니다

  • Approved: 플레이어가 승인되면 true, 거부되면 false
  • CreatePlayerObject:  클라이언트를 위한 Player Prefab이 소환되면 true, 아니면 false
  • PlayerPrefabHash: 인증된 플레이어에 사용할 Player Prefab의 유형(null인 경우에는 기본 NetworkManeger에 정의된 기본 player Prefab을 사용함)
  • Position and Rotation: 플레이어가 스폰될 위치와 회전
  • Pending: 다른 사용자별 코드가 승인 프로스세를 완료할 때까지 승인을 지연시키기 위해 승인을 보류 중으로 표시할 수 있는 기능을 제공
  • Reason: 만약 승인이 false라면, 클라이언트가 승인되지 않은 이유를 보내기 위해 문자열 기반 메세지 또는 JSON으로 채울 수 있다

private void Setup() 
{
    NetworkManager.Singleton.ConnectionApprovalCallback = ApprovalCheck;
    NetworkManager.Singleton.StartHost();
}

private void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
{
    // The client identifier to be authenticated
    var clientId = request.ClientNetworkId;

    // Additional connection data defined by user code
    var connectionData = request.Payload;

    // Your approval logic determines the following values
    response.Approved = true;
    response.CreatePlayerObject = true;

    // The Prefab hash value of the NetworkPrefab, if null the default NetworkManager player Prefab is used
    response.PlayerPrefabHash = null;

    // Position to spawn the player object (if null it uses default of Vector3.zero)
    response.Position = Vector3.zero;

    // Rotation to spawn the player object (if null it uses the default of Quaternion.identity)
    response.Rotation = Quaternion.identity;
    
    // If response.Approved is false, you can provide a message that explains the reason why via ConnectionApprovalResponse.Reason
    // On the client-side, NetworkManager.DisconnectReason will be populated with this message via DisconnectReasonMessage
    response.Reason = "Some reason for not approving the client";

    // If additional approval steps are needed, set this to true until the additional steps are complete
    // once it transitions from true to false the connection approval response will be processed.
    response.Pending = false;
}

Sending an approval declined reason

특별한 이유로(예를 들어, 방의 인원수가 꽉찬 경우) 클라이언트가 접속을 실패할 때, 서버 측에서 클라이언트에게 DisconnectReasonMessage를 보냅니다(클라이언트측에서의 연결요청을 거절할 수 밖에 없었던 이유로 가득 찬 NetworkManager.ConnectionApprovalResponse.Reason)
 
클라이언트 측에선  DisconnectReasonMessage를 받습니다(서버측에서 왜 연결요청을 거절할 수 밖에 없었던 이유에 대한 설명이 적힌 NetworkManager.ConnectionApprovalResponse.Reason)

using UnityEngine;
using Unity.Netcode;

/// <summary>
/// Connection Approval Handler Component
/// </summary>
/// <remarks>
/// This should be placed on the same GameObject as the NetworkManager.
/// It automatically declines the client connection for example purposes.
/// </remarks>
public class ConnectionApprovalHandler : MonoBehaviour
{
    private NetworkManager m_NetworkManager;

    private void Start()
    {
        m_NetworkManager = GetComponent<NetworkManager>();
        if (m_NetworkManager != null)
        {
            m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
            m_NetworkManager.ConnectionApprovalCallback = ApprovalCheck;
        }
    }

    private void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
    {
        response.Approved = false;
        response.Reason = "Testing the declined approval message";
    }

    private void OnClientDisconnectCallback(ulong obj)
    {
        if (!m_NetworkManager.IsServer && m_NetworkManager.DisconnectReason != string.Empty)
        {
            Debug.Log($"Approval Declined Reason: {m_NetworkManager.DisconnectReason}");
        }
    }
}

 


Connection data(NetworkManager.ConnectionApprovalRequest.Payload)

Connection Data는 주로 Room 비밀번호, 서버가 연결을 승인해줄지 말지 결정하는데 도움을 주는거와 같은 종류로 사용되곤 합니다
 
Connection Data는  클라이언트 측에서 정의됩니다
NetworkConfig.ConnectionData는 자동으로 서버측에게 연결 요청 메세지를 보냅니다(NetworkManager.StartClient가 호출되기 전에 클라이언트 측에 의해서)

using Unity.Netcode;

NetworkManager.Singleton.NetworkConfig.ConnectionData = System.Text.Encoding.ASCII.GetBytes("room password");
NetworkManager.Singleton.StartClient();

Payload : 서버에게 보낼 클라이언트의 Custom data를 담은 파라미터입니다

 Connection data는 선택적 사용가능하며 Player의 특별한 Prefabs을 할당 및 다양한 위치에서 플레이어들을 생성 가능하게 해줍니다
 
 

Timeout

시간이 걸리는 작업은 서버가 허락해줄 때까지 Pending을 true로 설정하면 됩니다!
 
만약, NetworkConfig.ClientConnectionBufferTimeout에 의해 지정된 시간보다 더 걸리게 된다면 연결은 끊기고 맙니다

Timer는 서버가 새 클라이언트 연결을 통보받을 때 시작됩니다
Timer시간은 NetworkManager인스펙터창에서 설정할 수 있습니다


 


Connection data security

Connection Data는 암호화 되지 않습니다
해커로부터 공격을 방지하려면 연결 승인을 통해 인증 토큰을 보내지 않는 것이 좋습니다!
 
 

Changing the player Prefab (= Custom Client Data가능)

연결중인 플레이어가 사용하고 있는 Player Prefab을 특정한 다른것으로 사용(오버라이딩)하고 싶을 때가 있을 것입니다!
Connection Approbal Process는 이것을 가능하게 해줍니다!
 

[Step 1] : 씬안에 놓여진 Connection approval compoent를 바꾸거나 만들면 됩니다!

  public class ClientConnectionHandler : NetworkBehaviour
    {
        public List<uint> AlternatePlayerPrefabs;

        public void SetClientPlayerPrefab(int index)
        {
            if (index > AlternatePlayerPrefabs.Count)
            {
                Debug.LogError($"Trying to assign player Prefab index of {index} when there are onlky {AlternatePlayerPrefabs.Count} entries!");
                return;
            }
            if (NetworkManager.IsListening || IsSpawned)
            {
                Debug.LogError("This needs to be set this before connecting!");
                return;
            }
            NetworkManager.NetworkConfig.ConnectionData = System.BitConverter.GetBytes(index);
        }

        public override void OnNetworkSpawn()
        {
            if (IsServer)
            {
                NetworkManager.ConnectionApprovalCallback = ConnectionApprovalCallback;
            }
        }

        private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
        {
            var playerPrefabIndex = System.BitConverter.ToInt32(request.Payload);
            if (AlternatePlayerPrefabs.Count < playerPrefabIndex)
            {
                response.PlayerPrefabHash = AlternatePlayerPrefabs[playerPrefabIndex];
            }
            else
            {
                Debug.LogError($"Client provided player Prefab index of {playerPrefabIndex} when there are onlky {AlternatePlayerPrefabs.Count} entries!");
                return;
            }
            // Continue filling out the response
        }
    }

위 예시는
1. Alternate Player Prefab GlobalObjectIdHash 값(=AlternatePlayerPrefabs)을 저장하기 위한 <uint>의 List를 만듭니다
 
2. 이 코드는 클라이언트가 AlternatePlayerPrefabs에 관련하여 Player Prefabs의 index에 선택된 설정을 불러오기 위한 public 함수입니다
 
이 방식은, 클라이언트는 서버에게 원하는 Alternate Player Prefab index를 서버에게 제공한다는 아이디어 입니다!
 
3. 서버는 씬 안에 놓여진 ClientConnectionHandler부착된 NetworkObject를 소환될 때, ConnectionAppreovalCallback를 등록합니다
 
4. 서버가 연결 요청을 처리할 때, 서버는 RequestPayload로부터 Alternate Player Prefab index를 가져온 다음,  AlternatePlayerPrefabs List로부터 GlobalObjectIdHash 을 얻은 후  response.PlayerPrefabHash에 할당합니다! 
 

[Step 2]: Alternate Player Prefabs의 GlobalObjectIdHash 값을 복사하자!

AlternatePlayerPrefabs List에 채우기 위해

  • 씬 안에 놓여 있는 ConnectoinApprovalCallback이 붙어 있는 NetworkObject가 포함된 Scene 엽니다!
  • List에 추가되길 바라는 Alternate Player Prefab을 찾고, 프리팹으로 선택합니다, 그리고 GlobalObjectIdHash 값을 복수합니다!
  • AlternatePlayerPrefabs List안에 새로운 List item으로 복사된 GlobalObjectIdHash 값을 붙여줍니다!

1. Scene안에 2개의 NetworkObject 컴포넌트가 부착된 게임오브젝트를 생성해두었습니다!
 
2.

Client Connection Handler 컴포넌트를 부착해주었습니다!
 
3.

씬에 놓여 있는 Box의 Network Object 컴포넌트에서 GlobalObjectIdHash Value값을 Copy해주었습니다
 
4.

Ball의 Client Connectoin Handler에 Alternat Player Prefabs List에 [+]버튼을 눌러 item을 등록할 수 있는 칸을 생성해주었습니다. 거기에 아까 복사한 Box의 Value값을 붙여넣어줬습니다!
 
 

[Step 3] :

위 예시는 클라이언트가 호출할 수 있는 방법입니다만, 클라이언트가 해당 장면을 로드해야 하는 메서드가 필요합니다!
Alternate Player Prefab GlobalObjectIdHash 값들의 List를 가진 Scriptable Ojbect를 선택할 수 있습니다
이 방식은 클라이언트가 시작하기 전에 NetworkConfig.ConnectionData를 채우게 해야 합니다
 


Alternate 방식은 언제 사용되냐면,
클라이언트가 게임 캐릭터를 선택할 때 주로 사용되는 방법입니다!
게임이 시작되면 역동적으로 클라이언트가 선택한 캐릭터를 소환하게 해주게 합니다!!!