[UE Net] 액터 리플리케이션 Actor Replication
Actor Replication의 동작 원리를 이해하고 Property Replication을 구현하기 위한 방법을 학습하자. Property Replication은특정 플레이어에 속한 Actor의 정보를 Network 내 다른 플레이어에게 복제하는 작업이다. Client-Server 모델에서는 대부분 Server에서 Client로 데이터를 전달하고 액터를 Client에 전달하기 위해 사용하는 Replication의 방법에는 크게 2가지가 있다.
인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기!
목차
Actor Replication
Actor Replication의 동작 원리를 이해하자.
Property Replication을 구현하기 위한 방법을 학습하자.
Actor Replication 기초
특정 플레이어에 속한 Actor의 정보를 Network 내 다른 플레이어에게 복제하는 작업.
Client-Server 모델에서는 대부분 Server에서 Client로 전달함.
Replication의 방법에는 크게 2가지가 있음 (속성: Property Replication, 함수: RPC)
- Property Replication
- RPC (Remote Procedure Call) (예전에는 Function Replication이라 불렸다.)
기본 Actor의 로딩
- Client가 초기화 될 때 모든 Actor 정보를 서버로부터 받는 것은 비효율적
- 따라서 기본 배경에 관련된 Actor는 맵을 통해 스스로 로딩하도록 설계되어 있음.
- 고정으로 제공하는 Actor와 동적으로 생성하는 Actor
- 고정으로 제공하는 Actor의 예: 레벨을 구성하는 배경 Actor
- 동적으로 제공하는 Actor의 예: PlayerController와 Pawn
- 고정 Actor에 대해 NetLoadOnClient 속성을 체크해야 함 (기본값)
- 언리얼 엔진은 모든 Actor에 NetLoadOnClient라는 속성을 제공한다.
- 이 속성들은 레벨에 배치된 Actor라면 true가 기본값이다.
- true로 설정된 Actor들은 Server와 통신없이 Client가 초기화될 때 자체적으로 로딩함으로 Server에서 콘텐츠를 배포한 것과 동일한 효과를 효과적으로 얻어낼 수 있다.
- 클라이언트가 네트워크를 사용하지 않고 서버에서 제공하는 아주 많은 양의 데이터를 효과적으로 복제하는 효율적인 방법이다.
프로퍼티 리플리케이션의 구현
액터의 리플리케이션 설정 Actor Replication
- 고정으로 보여주는 Actor 중, 게임 중 변경 사항이 발생하는 Actor는 그 값을 전달해야 함.
- Network 데이터를 최소화하기 위해 변경 사항을 보내기보다, 변경을 유발한 속성 값을 전달함.
- 이를 위해 Actor의 Replicates 옵션을 체크해야 함.
Replication Property 리플리케이션 프로퍼티의 지정
#1. Actor의 Replication Property를 true로 지정
- bReplicates 속성을 true로 설정.
#2. Network으로 복제할 Actor의 Property를 키워드로 지정
- UPROPERTY에 Replicated 키워드 설정
- 매 Frame 마다 실행되는 Tick의 코드가 방대해지는 단점이 있다. 상대방의 네트워크 전송 주기가 만일 Tick 보다 느리다면 데이터를 받기 전에 Tick이 여러번 호출돼 있어서 비효율적으로 동작할 수 있다.
- UPROPERTY에 ReplicatedUsing 키워드 설정 + ReplicatedUsing에 호출될 콜백 함수를 지정
- 호출될 콜백 함수는 UFUNCTION으로 선언해야 함.
- 일반적으로 OnRep_의 접두사를 가지는 이름 규칙을 가짐.
- 콜백 함수는 Server가 아닌 Client에서만 호출됨.
- OnRep_함수는 항상 클라이언트에서만 호출된다. 서버에서는 자동으로 호출되지 않기 때문에 서버 로직의 경우, 이 함수가 필요한 경우 명시적으로 호출해야 한다.
- 필요한 타이밍에만 해당 로직을 처리할 수 있어 효율적인 구현이 가능함.
- C++ ReplicatedUsing + OnRep_ vs. Blueprint RepNotify (유사하지만 동작 방식에 미세한 차이가 있음).
- C++: Client에서만 호출됨.명시적 호출 가능. 값이 변경된 때만 호출됨.
- BP: Server, Client에서 호출됨. 명시적 호출 불가능. Server는 항시 호출됨. Client는 값이 변경된 때만 호출됨.
#3. GetLifetimeReplicatedProps 함수에 Network으로 복제할 속성을 추가
- #include "Net/UnrealNetwork.h" 헤더 파일 지정
- DOREPLIFETIME 매크로를 사용해 복제할 속성을 명시
Lifetime은 Actor Channel의 Lifetime을 의미한다.
즉, 활성화된 Actor Channel로 전송할 복제될 속성을 의미한다.
리플리케이션 콜백 함수 호출
1. 클라이언트에 속성이 복제될 때 콜백 함수가 호출되도록 구현
- UPROPERTY의 Replicated 키워드를 ReplicatedUsing 키워드로 변경
- ReplicatedUsing에 호출될 콜백함수를 지정
- 호출될 콜백함수는 UFUNCTION으로 선언해야 함.
2. 콜백 함수의 구현
- 일반적으로 OnRep_의 접두사를 가지는 이름 규칙을 가짐.
- 콜백 함수는 서버가 아닌 클라이언트에서만 호출됨.
필요한 타이밍에만 해당 로직을 처리할 수 있어 효율적인 구현이 가능함
C++ OnRep vs. 블루프린트 RepNotify
- 필요한 타이밍에만 해당 로직을 처리할 수 있어 효율적인 구현이 가능함.
- C++ ReplicatedUsing + OnRep_ vs. Blueprint RepNotify (유사하지만 동작 방식에 미세한 차이가 있음).
- C++: Client에서만 호출됨. 명시적 호출 가능. 값이 변경된 때만 호출됨.
- BP: Server, Client에서 호출됨. 명시적 호출 불가능. Server는 항시 호출됨. Client는 값이 변경된 때만 호출됨.
예시 코드
Fountain.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABFountain.generated.h"
UCLASS()
class ARENABATTLE_API AABFountain : public AActor
{
GENERATED_BODY()
public:
AABFountain();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr<class UStaticMeshComponent> Body;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr<class UStaticMeshComponent> Water;
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void OnActorChannelOpen(class FInBunch& InBunch, class UNetConnection* Connection) override;
UPROPERTY(ReplicatedUsing = OnRep_ServerRotationYaw)
float ServerRotationYaw;
UFUNCTION()
void OnRep_ServerRotationYaw();
float RotationRate = 30.0f;
};
Fountain.cpp
#include "Prop/ABFountain.h"
#include "Components/StaticMeshComponent.h"
#include "Net/UnrealNetwork.h"
#include "ArenaBattle.h"
AABFountain::AABFountain()
{
PrimaryActorTick.bCanEverTick = true;
Body = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
Water = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Water"));
RootComponent = Body;
Water->SetupAttachment(Body);
Water->SetRelativeLocation(FVector(0.0f, 0.0f, 132.0f));
static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01'"));
if (BodyMeshRef.Object)
{
Body->SetStaticMesh(BodyMeshRef.Object);
}
static ConstructorHelpers::FObjectFinder<UStaticMesh> WaterMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Fountain_02.SM_Plains_Fountain_02'"));
if (WaterMeshRef.Object)
{
Water->SetStaticMesh(WaterMeshRef.Object);
}
bReplicates = true;
}
// Called when the game starts or when spawned
void AABFountain::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
FTimerHandle Handle;
GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([&]
{
ServerRotationYaw += 1.0f;
}
), 1.0f, true, 0.0f);
}
}
void AABFountain::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (HasAuthority())
{
AddActorLocalRotation(FRotator(0.0f, RotationRate * DeltaTime, 0.0f));
ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
}
}
void AABFountain::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AABFountain, ServerRotationYaw);
}
void AABFountain::OnActorChannelOpen(FInBunch& InBunch, UNetConnection* Connection)
{
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
Super::OnActorChannelOpen(InBunch, Connection);
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}
void AABFountain::OnRep_ServerRotationYaw()
{
AB_LOG(LogABNetwork, Log, TEXT("Yaw : %f"), ServerRotationYaw);
FRotator NewRotator = RootComponent->GetComponentRotation();
NewRotator.Yaw = ServerRotationYaw;
RootComponent->SetWorldRotation(NewRotator);
}
'⭐ Unreal Engine > UE 개념정리 - Network' 카테고리의 다른 글
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow (0) | 2024.02.12 |
---|---|
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight (0) | 2024.02.10 |
[UE Net] Connection Handshaking (0) | 2024.02.09 |
[UE Net] 액터의 역할과 커넥션 핸드셰이킹 Actor Role & Connection Handshaking (0) | 2024.02.09 |
[UE Net] 커넥션과 오너십 Connection & Ownership (0) | 2024.02.09 |
댓글
이 글 공유하기
다른 글
-
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
2024.02.12 -
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight
2024.02.10 -
[UE Net] Connection Handshaking
[UE Net] Connection Handshaking
2024.02.09 -
[UE Net] 액터의 역할과 커넥션 핸드셰이킹 Actor Role & Connection Handshaking
[UE Net] 액터의 역할과 커넥션 핸드셰이킹 Actor Role & Connection Handshaking
2024.02.09