목차

  1. 개요: 왜 변수 복제가 중요한가?
  2. 변수 복제(Replication)의 기본 구조
  3. RepNotify(= Replicated Using) 함수로 가시성 제어하기
  4. 서버-클라이언트 간 다른 처리 방식
  5. 실전 예시 코드 및 흐름도
  6. 추가 팁 및 마무리

1. 개요: 왜 변수 복제가 중요한가?

멀티플레이 게임에서는 “서버가 모든 중요한 결정(아이템 습득, 대미지 처리 등)을 맡고, 그 결과를 각 클라이언트와 동기화”하는 구조가 일반적입니다. 이를 위해 **변수 복제(Replication)**가 사용되는데, 변수 하나하나를 어떻게 동기화할지를 엔진 차원에서 지정할 수 있어 매우 편리합니다.

예를 들어,

  • “플레이어가 바닥에 있는 무기와 겹쳤다”는 사실은 서버에서만 판정
  • 해당 “겹치는 무기”를 클라이언트에서 표시하기 위해선 서버 → 클라이언트로 정보가 복제되어야 함

이러한 과정을 간소화하는 것이 바로 RepNotify(= Replicated Using) 또는 조건부 복제 기능입니다.


2. 변수 복제(Replication)의 기본 구조

언리얼에서 어떤 클래스를 “네트워크 복제가 가능한(Replicates)” 배우(Actor)로 두고 싶다면,

  1. 클래스의 헤더에 #include "Net/UnrealNetwork.h"를 포함
  2. GetLifetimeReplicatedProps 함수를 오버라이드해 “복제하고 싶은 변수”를 등록
  3. “특정 조건(예: COND_OwnerOnly)”이 있으면, 해당 조건을 추가로 붙여서 네트워크 트래픽을 줄일 수 있음

예시 (BlasterCharacter.cpp)

void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // 예: OverlappingWeapon이라는 변수만 복제할 때
    DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);
}
  • DOREPLIFETIME를 쓰면 모든 클라이언트로 복제
  • DOREPLIFETIME_CONDITION를 쓰면, 조건(OwnerOnly 등)에 맞춰 특정 클라이언트에게만 복제

이렇게 설정된 “겹치는 무기(OverlappingWeapon)” 변수가 서버에서 바뀌면, 소유자(Owner) 클라이언트에게만 자동으로 동기화됩니다.


3. RepNotify(= Replicated Using) 함수로 가시성 제어하기

변수 복제를 좀 더 잘 활용하기 위해, 언리얼은 ReplicatedUsing이라는 특수한 UPROPERTY 설정을 제공합니다.

UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
AWeapon* OverlappingWeapon;
  • OverlappingWeapon이라는 변수가 서버에서 바뀔 때, 클라이언트에 복제된 직후 자동으로 OnRep_OverlappingWeapon() 함수가 호출됩니다.
  • 이 함수를 “RepNotify 함수(= ‘담당자 알림’ 함수)”라고도 부릅니다.
  • RepNotify 함수 내부에서 “UI 위젯 켜기/끄기” 등의 처리를 쉽게 할 수 있습니다.

예시: RepNotify 함수

UFUNCTION()
void ABlasterCharacter::OnRep_OverlappingWeapon()
{
    if (OverlappingWeapon)
    {
        // 새로 겹친 무기가 생겼으므로, 해당 무기의 위젯을 켠다
        OverlappingWeapon->ShowPickupWidget(true);
    }
    else
    {
        // 무기 겹침이 해제되었다면 위젯을 끄는 등 처리
        // OverlappingWeapon->ShowPickupWidget(false);
    }
}
  • 이렇게 하면 **Tick(매 프레임)**에서 일일이 UI 갱신을 체크할 필요 없이,
    “변수값이 바뀌는 시점에만 해당 함수가 자동 호출”되어 UI를 깔끔하게 업데이트할 수 있습니다.

4. 서버-클라이언트 간 다른 처리 방식

멀티플레이 환경에서 서버클라이언트가 하나의 코드를 공유하지만, 실제 동작은 꽤 다릅니다.

  • 서버(Server): 모든 결정(Overlap 판정, 아이템 습득 판정 등)이 일어남.
  • 클라이언트(Client): 서버가 내려준 결과를 시각적으로 보여주거나 조작함.
  • RepNotify 함수: 오직 클라이언트에서만 자동 호출됨(“서버에서 → 클라이언트로 복제될 때” 발생).
    • “서버에서 변수값이 바뀌었다” → “클라이언트에 복제 완료” 시점에만 작동
    • 따라서 “서버 자신이 UI를 보고 싶다”면(서버에서 플레이 중이라면), 따로 로직을 짜서 처리해야 함.

대표적인 혼동 사례

  • 서버가 자기 캐릭터로 무기 위에 서 있어도, RepNotify 함수는 “서버 → 클라이언트 복제”가 발생하지 않으므로 서버에서는 호출되지 않음.
  • 즉, 서버 플레이어가 “내 캐릭터가 무기와 겹쳤을 때 UI 보고 싶다”면, 별도의 로직으로 직접 ShowPickupWidget(true) 등을 호출해야 합니다.

5. 실전 예시 코드 및 흐름도

5.1 캐릭터 클래스(BlasterCharacter)

// BlasterCharacter.h
UCLASS()
class ABlasterCharacter : public ACharacter
{
    GENERATED_BODY()
    
public:
    ABlasterCharacter();

    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

protected:
    UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
    class AWeapon* OverlappingWeapon;

    UFUNCTION()
    void OnRep_OverlappingWeapon();

    // 무기를 중첩(set)하는 함수 (서버가 OverlappingWeapon을 변경)
    void SetOverlappingWeapon(AWeapon* Weapon);
};
// BlasterCharacter.cpp
#include "Net/UnrealNetwork.h"
#include "Weapon.h"

void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    // OverlappingWeapon 변수를 '소유자'에게만 복제
    DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);
}

void ABlasterCharacter::OnRep_OverlappingWeapon()
{
    if (OverlappingWeapon)
    {
        OverlappingWeapon->ShowPickupWidget(true);
    }
    else
    {
        // 이전 무기와 겹침이 풀렸다면 위젯을 꺼야 할 수도 있음
        // 필요하다면 OverlappingWeapon->ShowPickupWidget(false) 같은 처리
    }
}

void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
    OverlappingWeapon = Weapon;
    
    // 만약 서버 + 로컬 플레이어라면, 직접 위젯 표시를 할 수도 있음
    // if (IsLocallyControlled()) { Weapon->ShowPickupWidget(true); }
}

5.2 무기 클래스(Weapon)

// Weapon.h
UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()

public:
    AWeapon();

    void ShowPickupWidget(bool bShowWidget);

protected:
    UPROPERTY(VisibleAnywhere)
    class UWidgetComponent* PickupWidget;
};

// Weapon.cpp
#include "Weapon.h"
#include "Components/WidgetComponent.h"

AWeapon::AWeapon()
{
    PrimaryActorTick.bCanEverTick = false;

    PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
    SetRootComponent(PickupWidget);
    
    // 처음에는 위젯 숨김
    PickupWidget->SetVisibility(false);
}

void AWeapon::ShowPickupWidget(bool bShowWidget)
{
    if (PickupWidget)
    {
        PickupWidget->SetVisibility(bShowWidget);
    }
}

5.3 흐름도 (예시)

[서버: 캐릭터가 무기 SphereComponent와 Overlap] 
    ↓
(Overlap 이벤트) 
    ↓
SetOverlappingWeapon(무기) --> OverlappingWeapon이 서버에서 변경됨
    ↓
[언리얼 엔진] OverlappingWeapon 변수를 소유 클라이언트에 복제
    ↓
[클라이언트] OnRep_OverlappingWeapon() 자동 호출
    ↓
OverlappingWeapon->ShowPickupWidget(true);
    ↓
UI에 "이 무기를 주울 수 있음" 표시
  • 이렇게 “서버 판정” → “클라이언트 표시”로 자연스럽게 연결됩니다.
  • Overlap이 해제되면, 마찬가지로 “서버에서 OverlappingWeapon = nullptr;” → “클라이언트에서 OnRep_OverlappingWeapon() 재호출 → ShowPickupWidget(false);” 순서가 진행됩니다.

6. 추가 팁 및 마무리

  1. 서버에서 위젯 표시가 필요하다면?
    • RepNotify 함수는 “서버 → 클라이언트”로만 작동합니다. 서버 자체는 호출되지 않습니다.
    • 서버도 UI가 필요하면, Overlap 시점에 직접 Weapon->ShowPickupWidget(true)를 호출해야 합니다(서버 전용 로직).
  2. Tick 함수 남용은 지양
    • 초창기에 “매 프레임마다 OverlappingWeapon이 있는지 체크해서 위젯을 표시”하는 예시가 있었지만, 네트워크 비용이 크고 불필요한 연산이 많습니다.
    • RepNotify가 더 효율적이고 깔끔합니다.
  3. 조건부 복제로 최적화
    • COND_OwnerOnly 외에도 COND_SkipOwner, COND_AutonomousOnly 등 다양한 조건들이 있습니다.
    • 예: 다른 플레이어가 “누군가가 무기를 주웠는지” 알 필요 없으면, 복제 조건을 OwnerOnly로 최소화해 네트워크 트래픽을 줄일 수 있습니다.
  4. 끝나지 않은 이야기
    • 이 강의를 발판 삼아, “무기 장착”, “사격 로직”, “애니메이션 몽타주”, “서버에서 떨어뜨리는 동작” 등 점차 확장할 수 있습니다.
    • 멀티플레이는 “서버 권위(Authority)”, “클라이언트 표시”의 구분이 가장 중요하므로, 계속해서 익숙해지는 것이 핵심입니다.

결론

이 강의의 핵심은 변수 복제RepNotify를 통해 “서버에서 발생한 이벤트(무기와의 Overlap)를 클라이언트 UI로 안전하게 연결”하는 흐름을 파악하는 것입니다.

  • 서버: 무기 겹침 판정 → OverlappingWeapon 변경
  • 클라이언트(소유주): OnRep_OverlappingWeapon() 자동 호출 → UI 표시

이 과정을 통해 Tick을 남용하지 않고, 효율적으로 UI 상태를 제어할 수 있습니다.
직접 프로젝트에 적용해보며, “조건부 복제”, “서버/클라이언트 분기 처리” 등을 자연스럽게 이해해 나가시길 바랍니다.


도움이 되셨길 바랍니다.
더 궁금한 점이나, 다른 부분이 잘 이해되지 않는다면 언제든 댓글이나 문의를 남겨주세요!

'UnraealEngine > Multiplayer Shooter' 카테고리의 다른 글

48. Remote Procedure Calls  (0) 2025.03.13
47. Equipping Weapons  (0) 2025.03.13
44~46  (0) 2025.03.13
41.Seamless Travel and Lobby  (0) 2025.03.12
42.Network Role  (0) 2025.03.12

+ Recent posts