액터의 역할과 커넥션 체결 과정 확인

  • 네트웍 멀티플레이어에서 서버와 클라이언트에 위치한 액터의 역할 이해
  • 클라이언트 서버간에 커넥션이 맺어지는 과정에 대한 심층적인 학습

 

 

인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다. 
😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기!

 

 

 

목차

     

     


     

     

    액터의 역할 Actor Role

     

    Network Multiplayer에서 Server와 Client에 위치한 Actor가 수행하는 역할을 이해하자.


     

    학습 목표:  Connection & (Actor) Role

     


     

     

    Server와 Client에 위치한 Actor의 역할

     

    Client - Server 모델에서는 항상 Server에 있는 Actor만이 신뢰되고, 이를 Autority를 가진다고 표현한다.

     

    Client의 Actor는 " 대부분 Server Actor를 복제한 허상 "에 불과하다. 이러한 Actor를 Proxy라고 표현한다. 

    서버에서 클라이언트로 복제되는 방향성을 가지고 있다.


     

     

    Local 역할과 Remote 역할

     

    • Listen Server의 경우, Host 역할을 수행하는 플레이어도 게임에 참여하므로, Application의 게임 로직을 사용한다.
    • Application의 게임 로직은 Server Actor에 대해서만 게임에 관련된 작업을 수행해야 한다.
    • 이를 구분하기 위해 현재 동작하는 어플리케이션에서의 역할을 로컬 역할(Local Role), Connection으로 연결된 어플리케이에서의 역할을 리모트 역할(Remote Role)이라고 한다.

     

     

    동일한 Application을 사용하는 Server와 Client

    Server와 Client에서 동일한 아이덴티티를 가진 Actor를 게임 로직에서 어떻게 구분할것인가? 


     

     

    Actor의 역할의 종류:  None, Authority, AutonomousProxy, SimulatedProxy

     

    Actor의 종류 역할
     None  Actor가 존재하지 않음
    서버 
    Authority
     서비스를 대표하는 신뢰할수 있는 역할. 게임 로직을 수행.  
    클라이언트 
    AutonomousProxy
     Authority를 가진 오브젝트의 복제품. 일부 게임 로직을 수행.
    클라이언트의 입력 정보를 서버에 보내는 능동적인 역할을 일부 수행한다.
    AutonomousProxy 역할을 하는 액터로는 입력과 관련된 것을 처리하는 PlayerController와 Pawn이 있다.
    클라이언트 
    SimulatedProxy
     Authority를 가진 오브젝트의 복제품. 게임 로직을 전혀 수행하지 않음.
     (= Server가 보내주는 정보만 사용하겠다)
    일방적으로 서버로부터 데이터를 수신하고 이를 반영한다.
    SimulatedProxy 역할을 하는 액터로는 배경 액터가 있다.

     

     


     

    Actor의 역할을 파악하는 API

     

    • 신뢰할 수 있는 Actor, 즉 Authority를 가진 Actor만 게임 로직을 수행할 수 있음. 
    • 액터가 신뢰할 수 있는지 확인할 수 있기 위해 언리얼은 다음과 같은 API를 제공함.
      • AActor::HasAuthority

     

    • Autonomous Proxy는 예외적으로 입력에 관련된 로직을 수행할 수 있음
    • 따라서 입력에 관련된 게임 로직의 실행은 Authority와 Autonomous Proxy에서만 허용됨.
    • 이를 위해 언리얼은 다음과 같은 함수를 제공함.
      • APawn::IsLocallyControlled
      • AController::IsLocalController

     

       
     Authority 신뢰할 수 있는 Actor, 즉 Authority를 가진 Actor만 게임 로직을 수행할 수 있음. 

    UE API:
    -  AActor::HasAuthority
     Autonomous Proxy 예외적으로 입력에 관련된 로직을 수행할 수 있음.
    입력에 관련된 게임 로직의 실행은 Authority와 Autonomous Proxy에서만 허용됨.

    UE API:
    -  AControlller::IsLocalController
    -  APawn::IsLocallyControlled

     


     

     

    Net Mode 에 따른 오브젝트 배치

     

      Actor 종류 예시  
     Server에만 존재하는 Actor   GameMode GameMode는 HasAuthority 함수를 호출할 필요가 없음
     Server와 모든 Client에 존재하는 Actor
     ( Simulated Proxy )
     배경 액터, Pawn Pawn은 Autonomous와 Simulated가 혼재되어 있음.
    API를 사용해 로직을 구분해야 함
     Server와 소유하는 Client에만 존재하는 Actor 
     ( Autonomous Proxy )
     PlayerController, Pawn PlayerController는 빙의(OnPosses)하는 과정에서 Owner가 설정되면서 SimulatedProxy -> AutonomousProxy로 변경됨.
     Client에만 존재하는 오브젝트  Animation Blueprint, HUD 애니메이션 재생이나 UI 관련 로직은 클라이언트에만 사용 (Server는 변경된 속성을 전달하고, 변경된 속성에 따라 애니메이션과 UI를 바꾸도록 설계)  

     


     

    EngineTypes.h에 선언된 ENetRole - None, SimulatedProxy, AutonomousProxy, Authority

     

     

     

    ArenaBattle.h에 디버깅 로그를 위해 코드 추가

    • #define LOG_LOCALROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetLocalRole()))
    • #define LOG_REMOTEROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetRemoteRole()))
    #pragma once
    #include "CoreMinimal.h"
    
    #define LOG_LOCALROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetLocalRole()))
    #define LOG_REMOTEROLEINFO *(UEnum::GetValueAsString(TEXT("Engine.ENetRole"), GetRemoteRole()))
    #define LOG_NETMODEINFO ((GetNetMode() == ENetMode::NM_Client) ? *FString::Printf(TEXT("CLIENT%d"), GPlayInEditorID) : ((GetNetMode() == ENetMode::NM_Standalone) ? TEXT("STANDALONE") : TEXT("SERVER"))) 
    #define LOG_CALLINFO ANSI_TO_TCHAR(__FUNCTION__)
    #define AB_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("[%s][%s/%s] %s %s"), LOG_NETMODEINFO, LOG_LOCALROLEINFO, LOG_REMOTEROLEINFO, LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))
    
    DECLARE_LOG_CATEGORY_EXTERN(LogABNetwork, Log, All);

     

     

    예제 코드 실행화면

     

     

    클라이언트 접속 후

     

    < 서버 관점 >

    AABCharacterPlayer는 처음에 생성되었을때는 서버에서 보내주는 정보만 사용하는 SimulatedProxy 였지만

    빙의하는 과정에서 OwnerBP_ABPlayerController_C가 설정되면서 AutonomousProxy로 변경되었다.

     

    < 클라이언트 관점 (거꾸로다) >

    AABPlayerController::PostInitializeComponents 시점에는 역할이 없다.

    AABPlayerController::PostNetInit을 통해서 네트워크를 통해서 모든 설정이 완료된다.

    • 로컬 역할: ROLE_AutonomousProxy
    • 원격 역할: ROLE_Authority

     

     

     

    커넥션 핸드셰이킹 Connection Handshaking

     


     

    커넥션 핸드셰이킹

     

    • 핸드셰이킹이란?
      • 네트웍으로 접속하는 두 컴퓨터가 잘 연결되었는지 확인하는 과정
    • 언리얼 네트웍 멀티플레이 접속을 위한 핸드셰이킹 과정


     

     

    게임의 준비

     

    • 커넥션을 허용하면 게임을 시작할 수 있도록 클라이언트와 서버는 준비 과정을 거친다
    • 클라이언트: 맵의 로딩
      • 클라이언트가 서버에 접속 완료 →  게임 참여 →   게임 맵에 대한 정보를 서버로부터 받아서 로딩
      • 로딩이 완료되면 SendJoin() 함수를 통해 NMT_Join 패킷을 서버에 보냄.
    • 서버: 클라이언트를 대표하는 플레이어 컨트롤러의 설정
      • NMT_Join 패킷을 받은 서버는 UWorld::SpawnPlayActor 함수를 호출
      • AGameModeBase::Login(), AGameModeBase::PostLogin() 함수가 호출되며 클라이언트의 PlayerController와 Pawn이 설정


     

     

    커넥션 핸드셰이킹의 확인

     

    • 관련 소스코드 1:  DataChannel.h
    • 관련 소스코드 2:  UWorld::NotifyControlMessage (서버)
    • 관련 소스코드 3:  UPendingNetGame::NotifyControlMessage (클라이언트)
    • 패킷의 처리 콜스택 예


     

     

    알아두면 좋은 언리얼 네트웍 시스템 구성

     

    • NetDriver.h :  통신에 필요한 패킷이 정의되어 있음
    • 용도에 따라 패킷을 처리하는 다양한 NetDriver 클래스를 제공함.
      • GameNetDriver : 게임 데이터를 처리하는데 사용하는 네트웍 드라이버
      • DemoNetDriver : 게임 리플레이 데이터를 처리하는데 사용하는 네트웍 드라이버
      • BeaconNetDriver : 게임 외 데이터를 처리하는데 사용하는 네트웍 드라이버
    • 언리얼 엔진은 게임 데이터를 처리하는 게임넷드라이버로 IpNetDriver를 사용함.
    • 초기 접속에 관련된 데이터 패킷은 ControlChannel을 통해 분석됨.
    • 언리얼 엔진에서 번치(Bunch)를 처리하는데 사용하는 주요 채널
      • ControlChannel : 클라이언트 서버 간의 커넥션 핸드셰이킹을 다룰 때 사용하는 채널
      • ActorChannel : 액터 리플리케이션 작업을 다룰 때 사용하는 채널
      • VoiceChannel : 음성 데이터를 전달할 때 사용 (이번 강좌에서는 다루지 않음)

     

     

    로그인 과정

     

     

    void UPendingNetGame::SendInitialJoin()

    서버에서 NMT_Hello를 수신

    void World::NotifyControlMessage(UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch)

    Connection->SendChallengeContolMessage를 콜

    void UPendingNetGame::NotifyControlMessage( UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch)

    if (FNetControlMessage<NMT_Challenge>::Receive(Bunch, Connection->Challenge))로 Challenge를 받음

    FNetControlMessage<NMT_Login>::Send() 로그인 시도

    void AGameModeBase::PreLogin()

    클라이언트에서 NMT_Welcome 수신

    맵 로딩. Join 패키지를 서버에 보냄

     


     

     

    정리

     

    • 게임 로직을 구현하기 위해 알아야 하는 액터의 역할
    • 리슨 서버에서 액터 역할을 구분하기 위한 API와 이의 활용 방법
    • 클라이언트와 서버간의 접속이 맺어지고 게임이 준비되는 과정의 심층적인 이해