Vetenir 2025. 3. 26. 22:07

개요

언리얼 리슨서버로 야구게임을 구현을 위해

함수가 호출될 때 GEngine->AddOnScreenDebugMessage() 바로 호출하는 방식으로 구현을 했고

작동도 잘 되는 것을 확인했으나 해당 방식이 실제 멀티플레이에는

올바른 방식이 아님을 알고 수정한 결과를 담고 있습니다.

 

해당 글에서 말하는 브로드캐스트는 Multicast Delegate의 Broadcast()가 아님.


BaseballGameMode.cpp

void ABaseballGameMode::HandleOutCondition(APlayerController* OutPlayer)
{
    if (IsHostPlayer(OutPlayer))
    {
        //FString DebugMessage = TEXT("Guest Won!! 다시 게임이 시작됐다.");
        //GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, DebugMessage);
        BroadcastMessage(TEXT("Guest Won!! 다시 게임이 시작됐다."));
    }
    else
    {
        //FString DebugMessage = TEXT("Host Won!! 다시 게임이 시작됐다.");
        //GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, DebugMessage);
        BroadcastMessage(TEXT("Host Won!! 다시 게임이 시작됐다."));
    }

    InitializeGame();
}
  • 주석 처리된 부분이 기존의 구현하던 방식이고 아래의 BroadcastMessage()로 변경
void ABaseballGameMode::BroadcastMessage(const FString& Message)
{
    const ENetMode NetMode = GetWorld()->GetNetMode();

    // 모든 플레이어 컨트롤러 처리
    for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
    {
        ABaseballPlayerController* PC = Cast<ABaseballPlayerController>(It->Get());
        if (!PC) continue;

        // 데디케이트 서버: 모든 클라이언트에 RPC 전송
        if (NetMode == NM_DedicatedServer)
        {
            PC->ClientReceiveMessage(Message);
        }
        // 리스닝 서버: 로컬 플레이어 제외하고 RPC 전송
        else if (NetMode == NM_ListenServer)
        {
            if (!PC->IsLocalController())
            {
                PC->ClientReceiveMessage(Message);
            }
        }
    }
}

🔥 1. "직접 출력" vs "브로드캐스트" 방식 비교

특징 직접 출력 방식 브로드캐스트 방식
실행 주체 함수 호출 즉시 로컬에서 출력 중앙 집중식 관리 (서버 클라이언트 전파)
네트워크 영향 ❌ 단일 플레이어/로컬 전용 ⭕ 멀티플레이어 지원
코드 분리 로직과 출력이 결합 (간단하지만 유지보수 어려움) 출력 로직이 분리 (유지보수 용이)
예시 GEngine->AddOnScreenDebugMessage() 바로 호출 BroadcastMessage()로 래핑 후 전송

🎯 2. 각 방식이 "맞는" 경우

(1) 직접 출력 방식이 좋은 경우

  • 단일 플레이어 게임
    • 네트워크 복제가 필요 없을 때 (예: 디버그 메시지).
  • 간단한 프로토타입
    • 빠르게 구현할 때 유용.
  • 예시 코드:
     
    void AMyActor::ShowDamage(float Damage)
    {
        // 즉시 출력 (네트워크 불필요)
        GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::Printf(TEXT("Damage: %f"), Damage));
    }

(2) 브로드캐스트 방식이 필수인 경우

  • 멀티플레이어 게임
    • 서버가 모든 클라이언트에 동기화해야 할 때 (예: 채팅, 게임 이벤트).
  • UI 일관성 요구
    • 모든 플레이어가 같은 메시지를 동시에 받아야 할 때.
  • 예시 코드:
     
    void ABaseballGameMode::BroadcastMessage(const FString& Message)
    {
        // 서버 → 모든 클라이언트 전송
        for (auto& PC : GetAllPlayers())
        {
            PC->ClientReceiveMessage(Message);
        }
    }

🌟 3. 두 방식의 주요 차이점

(1) 네트워크 복제 유무

  • 직접 출력: 로컬에서만 즉시 실행 (클라이언트 간 불일치 가능성 있음).
  • 브로드캐스트: 서버가 신뢰할 수 있는 단일 소스로 관리 (동기화 보장).

(2) 유지보수성

  • 직접 출력: 출력 로직이 코드 전체에 분산되어 수정이 어려움.
  • 브로드캐스트: 출력 로직을 한 곳에서 관리해 일관성 유지.

(3) 확장성

  • 직접 출력: 새로운 기능 추가 시 모든 호출부를 수정해야 함.
  • 브로드캐스트: BroadcastMessage()만 수정하면 전체 시스템에 적용.

🛠 4. 실제 적용 예시

직접 출력 (단순한 경우)

void APlayerCharacter::TakeDamage(float Damage)
{
    Health -= Damage;
    // 즉시 출력 (로컬만 확인 가능)
    GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::Printf(TEXT("체력: %f"), Health));
}

브로드캐스트 (멀티플레이어 필수)

void AGameMode::PlayerWin(APlayerController* Winner)
{
    FString Message = FString::Printf(TEXT("%s 승리!"), *Winner->GetName());
    BroadcastMessage(Message); // 모든 플레이어에게 전송
}

// 서버 → 클라이언트 RPC
void APlayerController::ClientReceiveMessage_Implementation(const FString& Message)
{
    DisplayOnUI(Message); // UI 업데이트
}

 5. 결론: 어떤 방식을 선택해야 할까?

  1. 멀티플레이어 + 동기화 필요  브로드캐스트 필수.
  2. 단일 플레이어/디버그  직접 출력으로 간단히 처리.
  3. 유지보수성 중요 → 중앙 집중식 브로드캐스트를 권장.