게임개발/ue server

[UE] 게임 모드와 플레이어 컨트롤러를 활용한 로그인 플로우의 이해

차차냥 2024. 5. 8. 01:14

 

이번 포스팅에서는 언리얼 엔진의 게임 모드 내 네트워크 호출 함수의 흐름을 파악해봅니다.

게임을 시작할 때 네트워크를 사용하는 게임이라면 PreLogin, Login, PostLogin, StartPlay, BeginPlay 함수를 거치게 되는데요.

눈으로 보고 싶다면 로그를 찍는 매크로를 만들어 확인해볼 수 있습니다.

 

로그 매크로 만들기

// Log Macro 사용
// GetNetMode : 게임의 인스턴스가 수행하는 역할을 정의하는 열거형타입
// GPlayInEditorID : Play In Editor (PIE) 는 각 에디터의 ID 반환, 그렇지 않은 경우는 -1을 반환
// NM_Client : NM은 NetMode의 약자. 게임을 하는 사람을 통해 서버에 접속된 게임의 인스턴스를 묘사
// NM_Standalone : 플레이어가 네트워크 접속을 수반하지 않는 단독 플레이어 게임에 사용되는 모드
#define LOG_NETMODEINFO ((GetNetMode() == ENetMode::NM_Client) ? *FString::Printf(TEXT("Client %d"), GPlayInEditorID) : ((GetNetMode() == ENetMode::NM_Standalone) ? TEXT("STANDALONE") : TEXT("SERVER")))

// __FUNCTION__ : 현재 함수명을 출력함
#define LOG_CALLINFO ANSI_TO_TCHAR(__FUNCTION__)

#define LYRA_TEMP_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("[%s] %s %s"), LOG_NETMODEINFO, LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

DECLARE_LOG_CATEGORY_EXTERN(LogLyraNetwork, Log, All);

 

상속받은 네트워크 함수 내에 로그 매크로 추가

*예시
void ALyraGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
// 상속 함수 호출 전
    LYRA_TEMP_LOG(LogLyraNetwork, Log, TEXT("%s"), TEXT("Begin"));
    Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
    // 상속 함수 호출 후
    LYRA_TEMP_LOG(LogLyraNetwork, Log, TEXT("%s"), TEXT("End"));
}

 


게임 모드의 주요 함수

PreLogin

클라이언트의 접속 요청을 처리하는 함수

void AGameModeBase::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)

Login

접속을 허용한 클라이언트에 대응하는 플레이어 컨트롤러를 만드는 함수

APlayerController* AGameModeBase::Login(UPlayer* NewPlayer,ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)

PostLogin

플레이어 입장을 위해 플레이어의 필요한 기본 설정을 마무리하는 함수

void AGameModeBase::PostLogin(APlayerController* NewPlayer)

StartPlay

게임의 시작을 지시하는 함수

void AGameModeBase::StartPlay()

BeginPlay

게임 모드의 StartPlay 를 통해 게임이 시작될 때 클라이언트 내 모든 액터에서 호출하는 함수

void AActor::BeginPlay()

 

StartPlay 함수에서 BeginPlay 함수까지의 흐름

GameModeBase 의 StartPlay 함수에서 각 클라이언트의 Actor 클래스 BeginPlay 함수까지, 호출이 어떻게 이어지는걸까요?

 

서버의 GameModeBase 가 StartPlay 를 지시하면,  GameStateBase 의 HandleBeginPlay 를 호출합니다.

void AGameModeBase::StartPlay()
{
    GameState->HandleBeginPlay();
}

 

현재 서버 내 월드에 있는 모든 액터들에게 클라이언트 내 게임 시작을 알립니다(Notify).

void AGameStateBase::HandleBeginPlay()
{
    bReplicatedHasBegunPlay = true;

    GetWorldSettings()->NotifyBeginPlay();
    GetWorldSettings()->NotifyMatchStarted();
}

 

만약 클라이언트가 추가 생성 (클라이언트 창을 킨다거나) 되면, 해당 클라이언트에 맞는 PlayerController 가 같이 생성됩니다.

이 때의 클라이언트는 게임이 시작되었는지를 알 수 없는 상태로 시작되는데요.

같이 복사된 GameStateBase에 의해 bReplicatedHasBegunPlay  프로퍼티의 속성이 변경(true)되면서 OnRep_ReplicatedHasBegunPlay 함수가 호출됩니다.

UPROPERTY(Transient, ReplicatedUsing = OnRep_ReplicatedHasBegunPlay)
bool bReplicatedHasBegunPlay;
void AGameStateBase::OnRep_ReplicatedHasBegunPlay()
{
    if (bReplicatedHasBegunPlay && GetLocalRole() != ROLE_Authority)
    {
       GetWorldSettings()->NotifyBeginPlay();
       GetWorldSettings()->NotifyMatchStarted();
    }
}
 

이 OnRep_ReplicatedHasBegunPlay 함수는GameState를 복제한 각각의 클라이언트의 BeginPlay 함수를 호출해 액터를 시작시킵니다.


언리얼 엔진 게임 플레이 시작 흐름

리슨서버   클라이언트 1 클라이언트 2
게임모드
(GameMode)
     
플레이어 컨트롤러 0
(PlayerController)
플레이어 컨트롤러 1
(PlayerController)
복제
--------------------------->
플레이어 컨트롤러 0
(PlayerController)
플레이어 컨트롤러 2
(PlayerController)
복제
----------------------------------------------------------->
플레이어 컨트롤러 0
(PlayerController)
게임 스테이트
(GameState)
복제
--------------------------->
게임 스테이트
(GameState)
 
복제
----------------------------------------------------------->
게임 스테이트
(GameState)

 

  • 리슨 서버는 '나 자신' 의 컨트롤러 뿐만 아니라, 추가 생성되는 모든 클라이언트의 컨트롤러의 오리지널을 소유
  • 리슨 서버에 접속한 클라이언트들은 서버 속에 존재하는 자신의 컨트롤러와 게임 스테이트를 복제한 것을 소유