[CH4-22] 서버와 클라이언트 분기 2 & RPC 자세하게 정리

김여울·2025년 10월 9일
0

내일배움캠프

목록 보기
94/111

코드 수정 순서

1. GameMode 관련 코드

  • 대표 게임 모드 클래스들
  • 게임 진행, 룰, 세션 관리 등이 담긴 부분

2. PlayerController 관련 코드

  • 모드별 또는 공용 PlayerController 클래스들
  • 입력 처리, UI 호출, 로컬 플레이어 전용 기능 포함 부분
void ADCPlayerController::BeginPlay()
{
    Super::BeginPlay();
    
    if (IsLocalController()) // 로컬 플레이어만
    {
        // UI 표시, 카메라 설정
    }
}

3. Pawn/Character 관련 코드

  • 플레이어 캐릭터 및 AI Pawn 관련 코드(예: DCNPCController.cpp/.hDCNPC.cpp/.h, 캐릭터 이동 및 입력 관련)
  • 카메라 제어, 로컬 입력 처리 포함
  • 주요 수정 사항:
    • 입력 핸들러IsLocallyControlled() 체크
    • UI 업데이트OnRep 함수에서 IsLocallyControlled() 체크
    • 이펙트/사운드: Multicast RPC 사용
    • 게임 로직: 서버(HasAuthority())에서만 실행
    • 비주얼 효과: 클라이언트 전용
    • 관전자 모드: 클라이언트 전용
void ADCCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    if (IsLocallyControlled()) // 내가 조종하는 캐릭터
    {
        // 사운드 재생, 카메라 효과
    }
}

4. HUD/Widget 관련 코드(C++로 구현된 부분)

→ 근데 이건 수정 안 함

  • 게임 HUD, UI 위젯 C++ 부분
  • UI 초기화, 상태 동기화 부분
  • HUD는 클라이언트 전용이므로 별도 체크 불필요

5. GameState/PlayerState

  • 게임 상태 관리용 클래스들
  • 게임 점수, 상태 동기화 관련 부분

GameMode

분기 방법

  • 서버 전용 게임 진행 로직
    • 게임 오버 조건 판단, 점수 집계, 승패 결정 → 서버에서만 수행
    • HasAuthority() 또는 IsRunningDedicatedServer() 를 사용해 서버 권한을 체크한 후 실행하기
  • 클라이언트 전용 UI/사운드/카메라 처리
    • UI 표시, 사운드 재생 등 → 클라이언트에서만 수행
    • IsLocallyControlled() 또는 GetNetMode() == NM_Client 조건으로 분기해서 클라이언트에서만 실행되게 함
  • BeginPlay, Tick 등 반복 실행 함수 분기
    • 서버에서 클라이언트 전용 코드 실행 안되게 반드시 분기문 추가하기!

서버 전용 로직

GameMode는 서버에만 존재하니까 클라이언트 전용 로직은 없음!

주요 서버 전용 함수들

  • PostLogin() - 플레이어 입장 처리, AlivePlayerCount 증가
  • Logout() - 플레이어 퇴장 처리, AlivePlayerCount 감소
  • BeginPlay() - 게임 상태 초기화, 타이머 시작
  • HandlePlayerDeath() - 플레이어 사망 처리, 랭킹 부여
  • HandleGameOver() - 게임 종료 처리, ServerTravel 실행
  • TriggerNightEvent() - NPC 춤 이벤트 관리
  • OnEnemyKilled() - 킬 카운트 증가

HasAuthority() 체크

각 함수 시작 부분의 if (!HasAuthority()) return; 은 방어코드일 뿐
GameMode는 이미 서버 전용이므로 항상 true

클라이언트 전용 로직

클라이언트에서 실행되는 부분은 다른 클래스의 Client RPC임

// HandleGameOver()에서 호출
MyPC->Client_ShowGameOverUI(GameOverWidgetClass);

// ShowHiddenVictoryUI()에서 호출  
MyPC->Client_ShowHiddenVictoryUI(HiddenVictoryWidgetClass, WinnerNickname);

// QuitGameAfterHiddenVictory()에서 호출
MyPC->Client_QuitGame();
  • 위 함수들은 DCPlayerController 클래스에 구현되어 있고 클라이언트에서 실행됨
  • GameMode는 서버에서 이 RPC들을 호출만!
  • 실제 UI 표시, 사운드 재생은 각 클라이언트의 PlayerController에서 처리 됨

배경음악 재생 코드 (GameInstance)

[CH4-18] Unreal Engine 배경음악 겹침 문제 해결

  • 히든승리 위젯은 레벨이 따로 없어서 배경음악이 메인레벨의 음악과 겹침
    Play Sound2D 가 아니라 GameInstance 중앙 관리 방식으로 Play Background Music 함수 호출했음
  • GameInstance는 서버에 1개만 존재하고
    각 클라이언트마다 별도의 GameInstance가 존재함
  • GameInstance는 복제되지 않음 (Not Replicated)

if (UDCGameInstance* GI = GetGameInstance<UDCGameInstance>())
{
    GI->PlayBackgroundMusic(MainLevelMusic); // GameInstance만 재생
}
  • GameMode는 서버에서만 실행됨
  • GetGameInstance() 는 현재 실행 중인 머신의 GameInstance를 반환
  • 서버 GameMode에서 호출 → 서버 GameInstance만 음악 재생
  • 클라이언트들은 각자의 GameInstance가 있찌만 이 코드가 실행되지 않음

RPC

📎[이득우 네트워크 멀티플레이] 08. RPC 기초
📎RPC 이해하기
📎[UE Net] RPC (Remote Procedure Call)

  • Remote Procedure Call의 약자
  • 원격 컴퓨터에 있는 함수를 호출할 수 있게 해주는 통신 프로토콜
  • 언리얼 엔진에서는 클라이언트가 서버로 통신하는 유일한 수단

Ownership (소유권)

  • Server RPC는 PlayerController가 소유한 Character/Pawn에서만 동작함
  • 소유권 없는 액터에서 호출하면 무시됨
  • AActor::GetNetConnection() 을 통해 소유권 확인 가능
  • 주로 Autonomous Proxy인 플레이어 캐릭터가 호출

Reliable vs Unreliable

  • Reliable: RPC 호출을 보장하지만 느림 (중요한 로직에 사용)
  • Unreliable: 호출을 보장하지 않지만 빠름 (이펙트/사운드에 사용)

1. Server RPC

  • 클라이언트에서 서버로 호출하는 RPC
  • 클라이언트가 서버의 함수를 호출할 수 있는 유일한 방법
  • 플레이어 입력/액션을 서버로 전달해 서버에서 권한 있는 처리를 할 때 사용
    - 공격 요청
    - 아이템 습득
    - 문 열기
    - 플레이어 행동 검증

선언부

UFUNCTION(Server, Reliable)
void Server_IncrementDanceCount();

// 보안 검증 추가 선언
UFUNCTION(Server, Reliable, WithValidation)
void Server_IncrementDanceCount();
  • UFUNCTION 지정자: Server, Reliable - 클라이언트에서 서버로 호출
  • 함수 이름: Server_IncrementDanceCount - Server_ 접두사 관례 사용
  • WithValidation: 서버에서 클라이언트 요청을 검증하기 위한 지정자 (선택사항)

구현부

void ADCCharacter::Server_IncrementDanceCount_Implementation()
{
    // Server RPC - 서버에서 실행
    DanceCount++;
    UE_LOG(LogTemp, Warning, TEXT("[Server] DanceCount: %d"), DanceCount);

    if (DanceCount >= 15)
    {
        HandleHiddenWinCondition();
    }
}

// 검증 함수 (WithValidation 사용 시 필수)
bool ADCCharacter::Server_IncrementDanceCount_Validate()
{
    // 악의적인 요청 검증
    // false 반환 시 해당 클라이언트는 강제 접속 해제됨
    return true; // 검증 통과
}
  • _Implementation 접미사가 필수: 실제 로직이 실행되는 함수
  • _Validate 함수:
    • WithValidation 지정자를 사용하면 반드시 구현해야 함
    • 서버가 클라이언트의 요청을 검증할 수 있음
    • false 반환 시 해당 클라이언트는 스탠드얼론 모드로 전환되며 접속이 끊어짐
    • 악의적인 사용자의 데이터 변조를 방지하기 위한 보안 장치

동작 방식

  1. 클라이언트가 호출: 플레이어의 캐릭터에서 Server_IncrementDanceCount() 호출
  2. 네트워크 전송: 호출 명령이 네트워크를 통해 서버로 전송됨
  3. 검증 단계 (WithValidation 사용 시): 서버에서 _Validate() 함수 먼저 실행
  • true 반환 → 다음 단계 진행
  • false 반환 → 클라이언트 접속 해제
  1. 서버에서 실행: _Implementation() 함수가 서버에서만 실행됨
  2. 결과 처리: DanceCount 증가 및 히든 승리 조건 체크 등 권한 있는 로직 수행

호출 예시

// ADCCharacter.cpp - 클라이언트에서 실행
void ADCCharacter::OnDanceInput()
{
    // 서버에 춤 카운트 증가 요청
    Server_IncrementDanceCount();
}

이 함수는 클라이언트에서 호출되지만 실제 실행은 서버에서만 이루어짐

실행 위치

  • 클라이언트에서 호출하지만 서버에서만 실행됨
  • 서버에서 이 함수를 호출하면 로컬(서버)에서만 실행됨
  • 권한 있는 게임 로직 (체력 감소, 점수 증가 등)은 반드시 서버에서 처리해야 함
    → 클라이언트는 신뢰할 수 없으니까 항상 서버에서 검증해야함
    → WithValidation을 사용해 악의적인 데이터 변조 차단 가능

2. Client RPC

  • 서버에서 특정 클라이언트로 호출하는 RPC
  • 특정 플레이어에게만 시각/청각 효과를 보여줄 때 사용
    • UI 업데이트
    • 개인 전용 이펙트/사운드
    • 카메라 효과
    • 알림 메세지
  • 만약 모든 플레이어가 봐야한다면 NetMulticast RPC 사용하기

선언부

UFUNCTION(Client, Reliable)
void Client_OverlapEffect(class UNiagaraSystem* Effect, float DurationSeconds);
  • UFUNCTION 지정자: Client, Reliable - 서버에서 특정 클라이언트로 호출
  • 함수 이름: Client_OverlapEffect - Client_접두사 관례 사용
  • 매개변수: Niagara 이펙트와 지속시간 전달 가능

구현부

void ADCCharacter::Client_OverlapEffect_Implementation(UNiagaraSystem* Effect, float DurationSeconds)
{
    // Client RPC - 클라이언트에서 실행
    if (!Effect) return;	// 유효성 검사: 전달받은 나이아가라 이펙트가 null이면 함수 즉시 종료
	
    // 나이아가라 이펙트 생성 및 부착
    UNiagaraComponent* EffectComp = UNiagaraFunctionLibrary::SpawnSystemAttached(
        Effect,	// 재생할 나이아가라 시스템
        GetMesh(),	// 캐릭터 메시에 부착
        NAME_None,	// 특정 소켓 이름 : 없음
        FVector::ZeroVector,	// 상대 위치 (0, 0, 0)
        FRotator::ZeroRotator,	// 상대 회전 (0, 0, 0)
        EAttachLocation::KeepRelativeOffset,	// 상대 오프셋 유지
        true	// 자동 활성화
    );

    if (EffectComp)
    {
    	// 자동 제거 타이머 설정
        FTimerHandle EffectTimer;
        GetWorldTimerManager().SetTimer(
            EffectTimer,
            [EffectComp]()	// 람다 함수로 EffectComp 캡처
            {
                if (IsValid(EffectComp))
                {
                    EffectComp->DestroyComponent();	// 컴포넌트 삭제
                }
            },
            DurationSeconds,	// 서버에서 전달받은 지속시간
            false	// 반복 안 함 (한 번만 실행)
        );
    }
}
  • _Implementation 접미사가 필수

동작 방식

  1. 서버가 호출: Client_OverlapEffect() 실행
  2. 네트워크 전송: 해당 액터를 소유한 클라이언트에게만 RPC 전송
  3. 서버에서는 실행 안 됨: 서버는 _Implementation() 함수를 실행하지 않음
  4. 소유 클라이언트에서만 실행: 해당 액터를 소유한 클라이언트에서만 _Implementation() 함수 실행
  5. 결과: 해당 플레이어만 Niagara 이펙트를 화면에서 볼 수 있음

코드 설명

  • SpawnSystemAttached() 는 나이아가라 이펙트를 캐릭터 메시에 부착해 생성 → 캐릭터가 움직여도 이펙트가 따라다님
  • 실행 위치: 클라이언트의 캐릭터 인스턴스에서만 실행됨
  • 서버에서는 이 함수를 호출하지만 실제 나이아가라 이펙트 재생은 클라이언트 화면에서만 재생됨
    → 불필요한 서버 리소스 사용 방지하고 각 플레이어가 자신의 화면에서만 이펙트 볼 수 있게 함

서버에서 호출

  • BaseItem에서 서버가 실행하는 함수
// 서로 다른 2개의 Client RPC 호출함
void ABaseItem::SendOverlapFeedbackTo(class ADCCharacter* Target)
{
    // 대상 검증에 실패하면 아무 것도 하지 않음
    if (!Target) return;

    if (ADCPlayerController* PC = Cast<ADCPlayerController>(Target->GetController()))
    {
        if (USoundBase* S = ResolveGlobalOverlapSound())
        {
            // 플레이어 컨트롤러에서 사운드를 클라이언트 재생해요
            // [1번] PlayerController의 Client RPC
            PC->ClientPlaySound(S);
        }
    }

    if (OverlapEffect)
    {
        // 지정 시간만큼 이펙트를 재생해요
        // [2번] Character의 Client RPC
        Target->Client_OverlapEffect(OverlapEffect, OverlapEffectPlaySeconds);
    }
}

[1번] PlayerController의 Client RPC 호출

  • Target 캐릭터의 PlayerController 가져옴
  • ClientPlayerSound() 는 PlayerController에 정의된 Client RPC
  • 해당 PlayerController를 소유한 클라이언트에서만 사운드 재생

[2번] Character의 Client RPC 호출

  • Client_OverlapEffetct() 는 ADCCharacter에 정의된 Client RPC
  • Target 캐릭터를 소유한 클라이언트에서만 나이아가라 이펙트 재생

실행 흐름
1. BaseItem에서 Overlap 감지 (서버)
2. 네트워크 전송
- PC->ClientPlaySound() → 해당 클라이언트의 PlayerController로 전송
- Target->Client_OverlapEffect() → 해당 클라이언트의 Character로 전송
3. 클라이언트에서 실행

  • 각 Client RPC는 소유권이 있는 클라이언트에서만 실행됨
  • 사운드 재생과 이펙트 재생이 동시에

3. NetMulticast RPC

  • 서버를 포함한 모든 플레이어에게 명령을 보내는 RPC
  • 반드시 서버에서만 호출해야 함
  • 클라이언트에서 호출하면 로컬에서만 실행됨
  • 주요 사용되는 곳:
    • 폭발 이펙트 재생
    • 총소리 사운드 동기화
    • 몽타주 애니메이션 재생
    • 모든 플레이어가 봐야 하는 시각적 효과

선언부

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayEffect();
  • UFUNCTION 지정자: NetMulticast, Unreliable - 서버에서 모든 클라이언트로 브로드캐스트
  • 함수 이름: Multicast_PlayEffect - Multicast_ 접두사 관례 사용
  • Unreliable 권장: NetMulticast는 주로 시각/청각 효과용이므로 Unreliable 사용이 일반적
  • Reliable 사용 가능: 중요한 이벤트의 경우 Reliable 사용 가능하지만 네트워크 부하 증가

구현부

void AMyActor::Multicast_PlayEffect_Implementation()
{
    // NetMulticast RPC - 서버 + 모든 클라이언트에서 실행
    
    if (ExplosionEffect)
    {
        UNiagaraFunctionLibrary::SpawnSystemAtLocation(
            GetWorld(),
            ExplosionEffect,
            GetActorLocation(),
            GetActorRotation()
        );
    }
    
    if (ExplosionSound)
    {
        UGameplayStatics::PlaySoundAtLocation(
            GetWorld(),
            ExplosionSound,
            GetActorLocation()
        );
    }
    
    UE_LOG(LogTemp, Warning, TEXT("Effect played on %s"), 
        HasAuthority() ? TEXT("Server") : TEXT("Client"));
}
  • _Implementation 접미사가 필수
  • 실행 위치: 서버 + 모든 연결된 클라이언트
  • 로컬 효과 처리: 각 머신에서 독립적으로 이펙트/사운드 재생

동작방식

서버에서 호출한 경우
1. 서버가 호출: Multicast_PlayEffect() 실행
2. 네트워크 전송: 모든 클라이언트에게 RPC 브로드캐스트
3. 서버에서 실행: 서버 자신도 _Implementation() 함수 실행
4, 모든 클라이언트에서 실행: 연결된 모든 클라이언트에서 _Implementation() 함수 실행
5. 결과: 모든 플레이어가 동일한 이펙트/사운드를 보고 듣게 됨

클라이언트에서 호출한 경우 (잘못된 사용)
1. 클라이언트가 호출: Multicast_PlayEffect() 실행
2. 로컬에서만 실행: 해당 클라이언트에서만 실행됨
3. 네트워크 전송 안됨: 서버나 다른 클라이언트에 전달되지 않음
4. 결과: 호출한 클라이언트만 이펙트를 보게 되어 동기화 실패

올바른 사용 - 서버에서만 호출

void AMyGrenade::Explode()
{
    if (HasAuthority())  // 서버 체크
    {
        // 폭발 데미지 처리 (서버 전용 로직)
        ApplyRadialDamage();
        
        // 모든 플레이어에게 폭발 이펙트 표시
        Multicast_PlayExplosionEffect();
    }
}

// 실제 게임 예시
// 서버에서 공격 검증 후 모든 플레이어에게 애니메이션 동기화
void AMyCharacter::Attack()
{
    if (HasAuthority())
    {
        // 서버: 데미지 처리
        ApplyDamageToTarget();
        
        // 모든 플레이어: 공격 애니메이션 재생
        Multicast_PlayAttackAnimation();
    }
}

void AMyCharacter::Multicast_PlayAttackAnimation_Implementation()
{
    // 서버 + 모든 클라이언트에서 몽타주 재생
    if (AttackMontage)
    {
        PlayAnimMontage(AttackMontage);
    }
}

특징

  1. Ownership 불필요
  • Server RPC와 Client RPC와 달리 소유권이 필요 없음
  • 연관성(Relevancy) 기반으로 동작
  • 모든 액터에서 호출 가능하지만, 반드시 서버에서 호출해야 함!
  1. 네트워크 부하
  • 모든 클라이언트에게 데이터를 전송하므로 네트워크 부하가 큼
  • Unreliable 사용 권장: 이펙트/사운드는 일부 누락되어도 게임 플레이에 영향 없음
  • 빈번한 호출 지양: 네트워크 대역폭을 많이 소모함
  1. Property Replication과 차이점

    비교Property ReplicationNetMulticast RPC
    동작 방식값 변경 시 자동 동기화, OnRep 콜백 실행서버가 명시적으로 호출할 때만 실행
    데이터 유지서버에 값이 저장되어 지속됨호출 시점에만 실행, 데이터는 유지되지 않음
    새 플레이어 접속자동으로 현재 값이 동기화됨이전 호출은 재실행되지 않음
    사용 목적게임 상태, 체력, 점수 등 지속 데이터일회성 이펙트, 사운드, 애니메이션
    네트워크 부하값이 변경될 때만 전송호출할 때마다 전송

주의사항

  1. 반드시 서버에서만 호출 - 클라이언트에서 호출하면 로컬에서만 실행됨
  2. Tick 에서 사용 금지 - 네트워크 폭주 유발
  3. 게임 로직에서 사용 금지 - 게임 상태는 Property Replication 사용하기

언제 뭘 사용하지?

NetMulticast RPC 사용

  • 모든 플레이어가 봐야 하는 이펙트
  • 일회성 이벤트 (폭발, 스킬 이펙트)
  • 애니메이션 동기화

Client RPC 사용

  • 특정 플레이어만 봐야 하는 UI
  • 개인 전용 알림, 효과

Property Replication 사용

  • 지속적인 게임 상태 (체력, 점수)
  • 새 플레이어가 접속해도 동기화되어야 하는 데이터

RPC 비교표

비교Server RPCClient RPCNetMulticast RPC
호출 위치클라이언트에서 호출서버에서 호출서버에서 호출 (권장)
실행 위치서버에서만 실행해당 클라이언트에서만 실행서버와 모든 클라이언트에서 실행
호출 방향Client → ServerServer → 특정 ClientServer → All Clients (+ Server)
Ownership 필요필수필수불필요 (연관성 기반)
서버에서 실행실행됨실행 안됨실행됨
네트워크 전송1개 서버로 전송1개 클라이언트로 전송모든 클라이언트에게 브로드캐스트
주요 사용 목적클라이언트 액션을 서버로 전달특정 플레이어 전용 효과모든 플레이어가 봐야 하는 효과
사용 예시공격 요청, 아이템 습득, 문 열기UI 업데이트, 개인 알림, 카메라 효과폭발 이펙트, 총소리, 애니메이션 동기화
Validation 지원WithValidation 사용 가능지원 안함지원 안함
보안 중요도높음 (클라이언트 검증 필요)낮음 (서버가 호출)낮음 (서버가 호출)
네트워크 부하낮음 (1:1 전송)낮음 (1:1 전송)높음 (1:N 브로드캐스트)
권장 ReliabilityReliableUnreliable (이펙트용)Unreliable (이펙트용)
클라이언트에서 호출 시서버로 전송되어 실행됨작동 안함로컬에서만 실행 (동기화 안됨)

0개의 댓글