ScrollChat C++ Implement

정혜창·2025년 3월 27일
0

내일배움캠프

목록 보기
39/43

이전에 BluePrint로 만들었던 ScrollChat을 C++로 구현을 해보았다. 나아가 실제 게임에서 RPC와 Replecated 가 어떻게 작동하는지 학습하기 위해 ChatBased-Baseball 게임을 만들어보았다.

Project : Baseball MultiGame Project
Duration : 2025.03.10 ~ 2025.03.28
GitHub : https://github.com/JungHyaechang/TBaseballMultiGame
YouTube : https://www.youtube.com/watch?v=Jttnoeh8Wf8

🎮 GameMode

1️⃣ UML

앞서 만들었던 BluePrint를 기반으로 로직을 설계하여 UML을 작성.
BluePrint으로 작성한 ScrollChat : https://velog.io/@hch9097/ScrollChat-BP-Implement

2️⃣ BBGameMode

📌 PostLogin

PostLogin 은 Unreal에서 서버에서 플레이어가 정상적으로 접속을 완료한 후 처리할 로직을 넣는 데 사용.

📋 ABBGameMode::PostLogin(AplayerController* NewPlayer)
void ABBGameMode::PostLogin(APlayerController* NewPlayer)
{
	Super::PostLogin(NewPlayer);
	if (APlayerState* PS = NewPlayer->PlayerState)
	{
		FString Name = FString::Printf(TEXT("Player%d"), PlayerIndex++);
		PS->SetPlayerName(Name);
	}
}
  • GameMode의 InitNewPlayer() 에서 접속한 Client에 PlayerController와 PlayerState가 할당 됨. 따라서 안전하게 PlayerState를 가져올 수 있었음.

  • PlayerIndex는 GameMode에서 선언된 변수로 GameMode생성자에서 PlayerIndex = 1 로 초기화됨.

  • 따라서 접속한 Player마다 1,2,3...순서로 Player1, Player2, Player3 ... 이런식으로 이름이 부여되고 PlayerName의 SetPlayerName을 통해 이름을 변경.

  • PlayerNameGetPlayerName(), SetPlayerName() 은 Unreal Engine 에서 기본으로 제공하는 APlayerState클래스에 내장된 기능. Replecated 되어 있어서 서버->클라이언트로 자동 동기화 된다.

📌 BroadcastMessage

📋 ABBGameMode::BraodcastMessage(const FString& Message)
void ABBGameMode::BraodcastMessage(const FString& Message)
{
	for (APlayerState* PS = GameState->PlayerArray)
    {
    	if (APlayerController* PC = Cast<APlayerController>(PlayerState->GetOwner()))
        {
        	if (UChatComponent* Chat = PC->FindComponentByClass<UChatComponent>())
            {
            	Chat->AddSystemChatMessageOnOwningClient(Message);
            }
        }
    }
}
  • GetGameMode()를 사용하지 않고 GameState포인터를 활용하여 바로 PlayerArray에 접근할 수 있는 이유는 AGameModeBase클래스가 이미 GameState를 멤버 변수로 가지고 있기 때문.
// AGameModeBase.h (언리얼 내부 코드 일부)
UPROPERTY(Transient)
AGameModeBase* GameState
  • GameMode가 시작될 때 엔진이 자동으로 생성한 GameState 인스턴스를 여기에 할당함.

    • 그래서 GameMode 코드에서 그냥 GameState->로 쓰는것이 가능. 선언 안해도 됌.
  • PlayerArray는 Unreal Engine이 기본으로 제공하는 시스템용 배열이다.

    • 클라이언트가 서버에 접속하면 GameMode::InitNewPlayer() 가 호출이 된다
    • PlayerController가 생성이되면서 PlayerController->InitPlayerState() 호출
    • PlayerState가 이과정을 통해 생성.
    • 이때 PlayerState가 GameState->PlayerArray에 PlayerState가 추가됌.
    • PlayerState는 Replicated 변수이기 때문에 클라이언트에도 자동 전파
    • 이 과정에서 각 클라이언트는 자신의 PlayerState 뿐만이 아니라 다른 플레이어의 PlayerState도 보유하게 됌.
// AGameStateBase.h (언리얼 내부 코드 일부)
UPROPERTY(Transient)
TArray<TObjectPtr<APlayerState>> PlayerArray;
  • FindComponentByClass를 이용하여 PlayerController에 있는 ChatComponent에 접근

    • AddSystemChatMessageOnOwningClient(Message)를 호출
    • ChatComponent에 있는 AddSystemChatMessageOnOwiningClient가 호출이 됌 (서버에서 이루어짐)
    • 실제로는 이 함수 안에는 우리가 코드를 작성하지 않지만 Unreal이 자동으로 네트워크로 보내는 코드를 생성(라우팅)
    • UFUNCTION(Client) 같은 RPC 함수는 이름만 선언하면 Unreal이 자동으로 _Implementation() 이 클라이언트에서 실행되도록 해줌.
    • 사실 과제를 제출하고 이 방식이 되게 비효율적인 것을 알아차림. 모든 for문을 돌려서 계속해서 함수를 호출하게 되면 클라이언트 수만큼 N번 호출을 하게 된다. 따라서 데이터 직렬화에 매우 불리
      • 직렬화는 데이터를 네트워크로 보낼 때 필요한 과정, 패킷화, 바이트화를 말함
    • Multicast를 하게되면 1번만 호출하게 되고 Actor를 보고 있는 Client에 한해서 복제를 해서 NetConnetion을 이용해 전송.
    • 1번 직렬화해서 클라 수만큼 복사 전송하게 되는 것이므로 네트워크 성능 + 코드 유지보수 + 안정성에 매우 유리하다.
  • 따라서 NetMulticast 에 대한 함수를 GameMode에 만들어 본다면

📋 ABBGameMode::BraodcastMessage(APlayerController* Sender, const FString& Message)
void ABBGameMode::BraodcastMessage(APlayerController* Sender, const FString& Message)
{
	if (UChatComponent* Chat = PC->FindComponentByClass<UChatComponent>())
    {
    	Chat->MulticastAddSystemChatMessageOnOwningClient(Message);
    }
}

이렇게 하는게 훨씬 효과적임.

  • 그렇다면 이렇게 비효율적인데 for문 + RPC(Client)는 언제 적용되어야 할까?
    • Actor를 보고있는 Client가 아닌 특정 Client에 뭔가를 보낼때 씀

      • 팀A 에게는 승리 메세지를 팀B 에는 패배 메세지를 보낼때
      • 길드채팅, 팀채팅에 효과적


🎮 Widget

확장성과 수정이 용이하게 설계를 하기 위해여 PlayerController 하위로 ActorComponent를 이용하여 플레이어의 입력, UI 관리

profile
Unreal 1기

0개의 댓글

관련 채용 정보