목차
- 개요: 왜 변수 복제가 중요한가?
- 변수 복제(Replication)의 기본 구조
- RepNotify(= Replicated Using) 함수로 가시성 제어하기
- 서버-클라이언트 간 다른 처리 방식
- 실전 예시 코드 및 흐름도
- 추가 팁 및 마무리
1. 개요: 왜 변수 복제가 중요한가?
멀티플레이 게임에서는 “서버가 모든 중요한 결정(아이템 습득, 대미지 처리 등)을 맡고, 그 결과를 각 클라이언트와 동기화”하는 구조가 일반적입니다. 이를 위해 **변수 복제(Replication)**가 사용되는데, 변수 하나하나를 어떻게 동기화할지를 엔진 차원에서 지정할 수 있어 매우 편리합니다.
예를 들어,
- “플레이어가 바닥에 있는 무기와 겹쳤다”는 사실은 서버에서만 판정
- 해당 “겹치는 무기”를 클라이언트에서 표시하기 위해선 서버 → 클라이언트로 정보가 복제되어야 함
이러한 과정을 간소화하는 것이 바로 RepNotify(= Replicated Using) 또는 조건부 복제 기능입니다.
2. 변수 복제(Replication)의 기본 구조
언리얼에서 어떤 클래스를 “네트워크 복제가 가능한(Replicates)” 배우(Actor)로 두고 싶다면,
- 클래스의 헤더에 #include "Net/UnrealNetwork.h"를 포함
- GetLifetimeReplicatedProps 함수를 오버라이드해 “복제하고 싶은 변수”를 등록
- “특정 조건(예: 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. 추가 팁 및 마무리
- 서버에서 위젯 표시가 필요하다면?
- RepNotify 함수는 “서버 → 클라이언트”로만 작동합니다. 서버 자체는 호출되지 않습니다.
- 서버도 UI가 필요하면, Overlap 시점에 직접 Weapon->ShowPickupWidget(true)를 호출해야 합니다(서버 전용 로직).
- Tick 함수 남용은 지양
- 초창기에 “매 프레임마다 OverlappingWeapon이 있는지 체크해서 위젯을 표시”하는 예시가 있었지만, 네트워크 비용이 크고 불필요한 연산이 많습니다.
- RepNotify가 더 효율적이고 깔끔합니다.
- 조건부 복제로 최적화
- COND_OwnerOnly 외에도 COND_SkipOwner, COND_AutonomousOnly 등 다양한 조건들이 있습니다.
- 예: 다른 플레이어가 “누군가가 무기를 주웠는지” 알 필요 없으면, 복제 조건을 OwnerOnly로 최소화해 네트워크 트래픽을 줄일 수 있습니다.
- 끝나지 않은 이야기
- 이 강의를 발판 삼아, “무기 장착”, “사격 로직”, “애니메이션 몽타주”, “서버에서 떨어뜨리는 동작” 등 점차 확장할 수 있습니다.
- 멀티플레이는 “서버 권위(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 |