NetLoadOnClient 속성
레벨에 정적으로 배치되고 값이 변하지 않는 액터는 서버가 굳이 복제해줄 필요가 없다
이런 액터는 NetLoadOnClient 속성을 true로 설정해서 각 클라이언트가 레벨을 로드할 때 알아서 스폰하게 만들 수 있다
동기화는 곧 네트워크 트래픽이고 비용이다
안 변하는 건 굳이 동기화하지 않는 게 효율적이다
주의할 점
NetLoadOnClient와 bReplicates는 같이 켜는 조합이 아니라 둘 중 하나를 선택하는 관계다.
- bReplicates = true → 서버가 액터를 만들고 클라에게 복제해준다
- NetLoadOnClient = true → 서버와 클라가 각자 레벨 로드 시점에 알아서 액터를 만든다 (복제 불필요)
둘 다 true로 켜면 클라이언트는 레벨 로드하면서 액터를 하나 만들고, 동시에 서버가 복제로 또 하나를 만들려고 시도하기 때문에 같은 액터가 중복으로 생기는 문제가 발생한다
그래서 NetLoadOnClient를 실습할 때는 bReplicates를 false로 둬야 한다
Property Replication 복습 - 회전값 동기화
Property Replication 3단계 패턴
- bReplicates = true - 액터 자체를 복제 대상으로 만든다
- UPROPERTY(Replicated) - 변수를 복제 후보로 선언한다
- DOREPLIFETIME - 실제로 복제 목록에 등록한다 (선언만 하고 등록하지 않으면 복제되지 않는다)
// DXBox.h
class DEDICATEDX_API ADXBox : public AActor
{
public:
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
virtual void Tick(float DeltaSeconds) override;
protected:
UPROPERTY(Replicated)
float ServerRotationYaw;
float RotationSpeed;
};
// DXBox.cpp
#include "Net/UnrealNetwork.h"
ADXBox::ADXBox()
: ServerRotationYaw(0.0f)
, RotationSpeed(30.0f)
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
}
void ADXBox::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, ServerRotationYaw);
}
void ADXBox::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (HasAuthority() == true)
{
AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
}
else
{
SetActorRotation(FRotator(0.f, ServerRotationYaw, 0.f));
}
}
- 서버: 실제로 회전을 계산해서 돌리고 그 결과값을 ServerRotationYaw에 저장한다. 이 값이 자동으로 클라에게 복제된다
- 클라이언트: 복제로 받은 ServerRotationYaw 값을 그대로 받아서 그 각도로 맞추기만 한다
매 틱 읽기의 비효율성
네트워크 복제는 매 틱마다 오는 게 아니라 서버가 일정 주기로 보내준다
하지만 클라이언트의 Tick()은 그것과 상관없이 매 프레임 계속 돌면서 SetActorRotation(ServerRotationYaw)를 호출한다
Replication Notify (OnRep)
이 매 틱 확인 문제를 해결하는 게 Replication Notify
서버에서 속성 값 변경에 따라 복제가 발생할 때 호출되는 콜백 함수
- 해당 속성의 UPROPERTY()에는 ReplicatedUsing 키워드가 붙어야 한다
- 콜백 함수에는 UFUNCTION() 매크로와 OnRep_ 접두사가 붙어야 한다 (엔진이 자동으로 찾아서 호출해주는 네이밍 규칙)
// DXBox.h
private:
UFUNCTION()
void OnRep_ServerRotationYaw();
protected:
UPROPERTY(ReplicatedUsing = OnRep_ServerRotationYaw)
float ServerRotationYaw;
// DXBox.cpp
void ADXBox::Tick(float DeltaSeconds)
{
if (HasAuthority() == true)
{
AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
}
else
{
// SetActorRotation(FRotator(0.f, ServerRotationYaw, 0.f)); // Tick에서 빼버림
}
}
void ADXBox::OnRep_ServerRotationYaw()
{
SetActorRotation(FRotator(0.f, ServerRotationYaw, 0.f));
}
이제 새 값이 도착한 순간에만 OnRep_ServerRotationYaw()가 자동으로 호출되면서 회전을 맞춰준다
Tick()은 더 이상 회전 처리를 매번 하지 않는다
Replication Notify의 특징
서버에서는 호출되지 않고 클라에서만 호출된다
OnRep은 복제를 받았다는 신호에 묶인 콜백인데 서버는 복제를 받는 입장이 아니라 보내는 입장이라서 호출될 이유가 없다
만약 서버에서도 같은 로직이 필요하다면 직접 명시적으로 호출해줘야 한다
if (HasAuthority() == true)
{
AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
OnRep_ServerRotationYaw(); // 서버도 직접 호출
}
Conditional Property Replication
레플리케이션에 등록된 프로퍼티를 조건식을 통해 더 세밀하게 조정할 수 있게 해준다
단점은 조건식 값이 너무 자주 바뀌면 오버헤드가 발생할 수 있다는 점이다
void ADXBox::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// DOREPLIFETIME(ThisClass, ServerRotationYaw);
DOREPLIFETIME_CONDITION(ThisClass, ServerRotationYaw, COND_InitialOnly);
}
COND_InitialOnly는 "이 액터가 처음 클라에 나타날 때만 복제하고, 그 이후로는 값이 바뀌어도 복제하지 않는다"는 조건이다
한 번 정해지면 절대 안 바뀌는 값(캐릭터 초기 스폰 위치 등)에는 유용하다
'학습 > Unreal' 카테고리의 다른 글
| 멀티플레이 디버깅 (0) | 2026.06.24 |
|---|---|
| 게임플레이 프레임워크 (0) | 2026.06.24 |
| Property Replication (기초) (0) | 2026.06.22 |
| 서버를 거치는 통신 구조 (0) | 2026.06.22 |
| Remote Procedure Call 기초 (RPC 기초) (0) | 2026.06.19 |
