본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 3 - 액터 리플리케이션 기초 (5/15)

728x90
반응형

 

 

 

  • 정리
    • 액터 리플리케이션 기초
      1. 액터 리플리케이션의 개요에 대한 학습
      2. C++을 활용한 프로퍼티 리플리케이션의 구현
      3. C++와 블루프린트간의 프로퍼티 리플리케이션 방식의 차이 비교

 

  • 액터 리플리케이션
    • 특정 플레이어에 속한 액터의 정보를 네트웍 내 다른 플레이어에게 복제하는 작업
    • 클라이언트-서버 모델에서는 대부분 서버에서 클라이언트로 전달함
      • 서버만이 Authority를 가지기 때문
    • 리플리케이션의 방법에는 크게 두 가지가 있음
      • 프로퍼티 리플리케이션 : 속성 복제
      • RPC(Remote Procedure Call) : 함수 호출

 

액터 리플리케이션

 

  • 기본 액터의 로딩
    • 클라이언트가 초기화 될 때 모든 액터 정보를 서버로부터 받는 것은 비효율적
    • 따라서 기본 배경에 관련된 액터는 맵을 통해 스스로 로딩하도록 설계되어 있음
    • 고정으로 제공하는 액터동적으로 생성하는 액터
      • 고정으로 제공하는 액터의 예 : 레벨을 구성하는 배경 액터
        • 앞으로 크게 변할 일이 없기 때문에 클라이언트에서 직접 초기 값을 로딩하는 것이 효율적이다.
      • 동적으로 제공하는 액터의 예 : 플레이어 컨트롤러
    • 고정 액터에 대해 NetLoadOnClient 속성을 체크해야 함 (기본값)
      • 기본적으로 true로 설정되어 있음
      • true로 설정된 액터들은 서버와 통신없이 클라이언트가 초기화될 때 자체적으로 로딩

 

기본 액터의 로딩

 

  • 액터의 리플리케이션 설정
    • 고정으로 보여지는 액터 중, 게임 중 변경 사항이 발생하는 액터는 그 값을 전달해야 함
    • 네트웍 데이터를 최소화하기 위해 변경 사항을 보내기보다, 변경을 유발한 속성 값을 전달
      • 그렇기 때문에 데이터 기반으로 설계하는것이 중요
    • 이를 위해 액터의 Replicates 옵션을 체크해야 함

 

액터의 리플리케이션 설정

 

  • 리플리케이션 프로퍼티(속성)의 지정
    1. 액터의 리플리케이션 속성을 참으로 지정
      1. bReplicates 속성을 true로 설정
    2. 네트웍으로 복제할 액터의 속성을 키워드로 지정
      1. UPROPERTYReplicated 키워드 설정
    3. GetLifetimeReplicatedProps 함수에 네트웍으로 복제할 속성을 추가
      1. #include “Net/UnrealNetwork.h” 헤더 파일 지정
      2. DOREPLIFETIME 매크로를 사용해 복제할 속성을 명시
    • 여기서 Lifetime은 액터 채널의 Lifetime을 의미. 즉 활성화된 액터 채널로 전송할 복제될 속성을 의미

 

public:
	// Lifetime 함수 추가
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	// Replicated 키워드를 붙여줘야 자동으로 언리얼 엔진의 네트워크 기능을 사용해서 다른 클라이언트로 복제가 된다.
	UPROPERTY(Replicated)
	float ServerRotationYaw;

	float RotationRate = 30.0f;
  • ABFountain.h
// Called every frame
void AABFountain::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 현재는 리슨 서버로 구성되어 있기 때문에 HasAuthority와 같은 체크 함수를 통해 클라이언트와 서버의 로직을 분리시켜줘야한다.
	// 데디케이트 서버로 구성되어 있다면 서버로직만 구성하면 된다.
	// Server
	if (HasAuthority())
	{
		AddActorLocalRotation(FRotator(0.0f, RotationRate * DeltaTime, 0.0f));
		ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
	}
	// Client
	else
	{
		FRotator NewRotator = RootComponent->GetComponentRotation();
		NewRotator.Yaw = ServerRotationYaw;
		RootComponent->SetWorldRotation(NewRotator);
	}
}

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

	// ServerRotationYaw 속성을 추가하기 위해서 매크로 사용
	// 기본적인 프로퍼티 리플리케이션 기능 구현할 수있다.
	DOREPLIFETIME(AABFountain, ServerRotationYaw);
}
  • ABFountain.cpp

 

  • Replicated 키워드를 붙여줘야 자동으로 언리얼 엔진의 네트워크 기능을 사용해서 다른 클라이언트로 복제가 된다.
  • 현재는 리슨 서버로 구성되어 있기 때문에 HasAuthority와 같은 체크 함수를 통해 클라이언트와 서버의 로직을 분리시켜줘야한다.
  • 만약, 데디케이트 서버로 구성되어 있다면 서버로직만 구성해주면 된다.

 

 

서버와 클라이언트 모두 분수대가 회전하는 모습

 

 

  • 다만 위의 코드는 매 Tick마다 서버로부터 받은 속성을 업데이트 해주었는데, 이렇게 되면 틱의 코드가 방대해지고, 상대방의 네트워크 전송 주기가 틱보다 느리다면 데이터를 받기 전에 틱이 여러번 호출돼 있어서 비효율적으로 동작할 수 있다.
  • 리플리케이션 콜백 함수 호출
    1. 클라이언트에 속성이 복제될 때 콜백 함수가 호출되도록 구현
      1. UPROPERTYReplicated 키워드를 ReplicatedUsing 키워드로 변경
      2. ReplicatedUsing에 호출할 콜백 함수를 지정
      3. 호출될 콜백 함수는 UFUNCTION으로 선언해야 함
    2. 콜백 함수의 구현
      1. 일반적으로 OnRep_의 접두사를 가지는 이름 규칙을 가짐.
      2. 콜백 함수는 서버가 아닌 클라이언트에서만 호출됨
    • 필요한 타이밍에만 해당 로직을 처리할 수 있어 효율적인 구현이 가능함

 

// ... 

public:
	// Lifetime 함수 추가
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
	// 액터 리플리케이션을 진행하기 위해서는 액터 채널이라고 하는 특별한 채널을 사용한다.
	virtual void OnActorChannelOpen(class FInBunch& InBunch, class UNetConnection* Connection) override;

	// Replicated 키워드를 붙여줘야 자동으로 언리얼 엔진의 네트워크 기능을 사용해서 다른 클라이언트로 복제가 된다.
	// ReplicatedUsing 키워드를 사용하고 함수를 지정해주면 서버에서 값이 변경이 되고, 클라이언트에 전달이 될 때 클라이언트에서는 이 함수가 자동으로 콜백함수가 호출된다.
	UPROPERTY(ReplicatedUsing = OnRep_ServerRotationYaw)
	float ServerRotationYaw;

	UFUNCTION()
	void OnRep_ServerRotationYaw();
  • ABFountain.h
// ...

// 액터 리플리케이션을 진행하기 위해서는 액터 채널이라고 하는 특별한 채널을 사용한다.
// 클라이언트만 사용하며, 서버와의 포탈이 열렸다라고 생각하면 이해가 쉽다.
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"));
}

// 콜백 함수
// 콜백 함수가 tick보다 더 적게 돌아가는 경우에는 보다 효율적으로 로직을 처리할 수 있다.
void AABFountain::OnRep_ServerRotationYaw()
{
	AB_LOG(LogABNetwork, Log, TEXT("Yaw : %f"), ServerRotationYaw);

	FRotator NewRotator = RootComponent->GetComponentRotation();
	NewRotator.Yaw = ServerRotationYaw;
	RootComponent->SetWorldRotation(NewRotator);
}
  • ABFountain.cpp

 

 

클라이언트를 실행하는 순간 계속해서 로그가 찍히는 것을 볼 수 있다.

 

OnActorChannelOpen 함수가 호출된 뒤, 리플리케이션이 진행되는 것을 볼 수 있다.

 

  • 네트워크로 전달될 때마다 콜백함수의 로그가 찍히는 것을 볼 수 있다.
  • 또한, OnActorChannelOpen함수를 통해 서버와의 연결이 된 것을 보고 리플리케이션이 진행되는 것을 볼 수 있다.

 

C++ OnRep vs Blueprint RepNotify

 

BP RepNotify 테스트

 

  • BP에서도 변수를 생성하고 ReplicationReplicatedRepNotify로 변경하면 FunctionsOnRep이라는 접두사를 가진 함수가 생성되게 된다.
  • 해당 함수는 BP에서 호출할 수 없고 오직 구현만 가능하다.
  • BP의 경우 클라이언트는 0에서 10으로 변경되는 단 한번만 로그가 찍히게 되고, 서버는 매초 10이라는 로그가 찍히게 된다.

 

 

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이어 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해 강의 | 이득우 - 인프런

이득우 | 또 하나의 언리얼 엔진이라고도 불리는 네트웍 멀티플레이어 프레임웍을 학습합니다. 네트웍 멀티플레이어 게임을 제작할 때 반드시 알아야 하는 주요 개념, 내부 동작 원리, 최적화

www.inflearn.com

 

 

 

 

728x90