Unreal Engine/기타

[UE] Variable Replication

KANTAM 2023. 12. 17. 20:09

멀티플레이어 게임에서 특수한 판정은 모두 서버에서 계산된 다음, 클라이언트에게 Replicate되어야 한다. 이 Replicate는 코드상에서 어떻게 구현되어야 하는지 알아보자. 

 

현재 무기와 overlap된 클라이언트에게만 위젯이 표시된다.

현재 땅에 떨어진 무기를 픽업을 위해서 무기의 SphereComponent에 Overlap되었을 때, 위젯 컴포넌트를 표시해야 한다. 여기서 Overlap 판정은 서버에서 실행되어야 한다. 현재 ENetRole을 검사해서 ROLE_Authority인 경우에만, Overlap 델리게이트를 바인드한다. 

AWeapon::AWeapon()
{
	bReplicates = true;			// replcate가 필요하므로(AreaSphere 판정) true로 설정한다.
}

void AWeapon::BeginPlay()
{
	Super::BeginPlay();
	
	// Authority가 있다면 == 현재 ENetRole가 ROLE_Authority라면 == 서버라면
	// 콜리전을 활성화해서 오버랩을 판정한다.
	if (HasAuthority())
	{
		AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
		AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
		AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);
	}

	if (PickupWidget)
	{
		PickupWidget->SetVisibility(false);
	}
}

void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
	if (BlasterCharacter)
	{
		BlasterCharacter->SetOverlappingWeapon(this);
	}
}

void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
	if (BlasterCharacter)
	{
		BlasterCharacter->SetOverlappingWeapon(nullptr);
	}
}

 

게임에서 컨트롤하는 캐릭터의 클래스에 현재 Overlap된 Weapon을 표현하는 멤버변수인 OverlappingWeapon이 있다. OverlappingWeapon은 위의 코드에서 SphereCollision과 Overlap되었을 때, 해당 Weapon이 저장된다. 이는 서버에서만 이루어지기 때문에, 이 변수를 Replicate해주어야 한다. 

private:
	// OverlappingWeapon이 Replicate될 때마다 자동적으로 호출되는 함수이다.
	// OnRep_는 Replicate될 때마다 호출된다. == Replicate는 서버에서 클라이언트 원웨이이므로 서버에서는 절대 호출되지 않는다.
	// OnRep_는 하나의 매개변수를 추가할 수 있는데, Replicate되기 전의 해당 변수를 받을 수 있다.
	UFUNCTION()
	void OnRep_OverlappingWeapon(AWeapon* LastWeapon);
    
	// 오버랩된 무기가 서버에서 변경되었을 경우, 다른 클라이언트에도 알려야 한다. 
	// UPROPERTY(Replicated)를 붙이면 OverlappingWeapon은 Replicated variable이 된다. 
	// 추가적으로 ReplicatedUsing을 사용하면 변경될 때마다 자동적으로 호출되는 함수를 설정할 수 있다.
	UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
	AWeapon* OverlappingWeapon;

UPROPERTY(Replicated)나 UPROPERTY(ReplicatedUsing = OnRep_...)로 UPROPERTY를 설정해주면 해당 변수는 Replicated variable로 사용할 수 있다. ReplicatedUsing을 사용하면 해당 변수가 Replicate되었을 때, 자동적으로 호출되는 함수를 지정해 줄 수 있다. 이렇게 Replicated variable을 사용하려면 GetLifetimeReplicatedProps함수를 재정의해서 해당 변수를 등록해주어야만 한다.

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

	// OverlappingWeapon은 서버에서 Overlap판정이 실행된다.
	// 판정된 OverlppingWeapon은 서버에서 클라이언트에게 Replicate되어야 한다.
	// 밑은 그 등록을 의미하며, COND_OwerOnly이므로 Overlap된 주인에게만 Replicate된다. 
	DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);
}

여기서 주목해야 할 점은, COND_OwerOnly를 매크로에 추가적으로 입력하는데, 이는 Overlap된 주인 클라이언트에게만 Replicate된다는 점이다.

 

무기가 Overlap되었을 때, this를 매개변수로 캐릭터의 SetOverlappingWeapon함수가 호출된다. 

void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
	// IsLocallyControlled는 현재 컨트롤하고 있는 폰인지 확인한다. 
	// == overlap 판정과 SetOverlappingWeapon함수는 서버에서 이루어진다. 즉, 서버가 컨트롤하는 폰인지 확인한다.
	if (IsLocallyControlled())
	{
		if (OverlappingWeapon)
		{
			OverlappingWeapon->ShowPickupWidget(false);
		}
	}

	OverlappingWeapon = Weapon;

	// IsLocallyControlled는 현재 컨트롤하고 있는 폰인지 확인한다. 
	// == overlap 판정과 SetOverlappingWeapon함수는 서버에서 이루어진다. 즉, 서버가 컨트롤하는 폰인지 확인한다.
	if (IsLocallyControlled())
	{
		if (OverlappingWeapon)
		{
			OverlappingWeapon->ShowPickupWidget(true);
		}
	}
}

이 함수는 OverlappingWeapon을 넘겨받은 AWeapon객체로 변경한다. OverlappingWeapon이 서버에서 변경되었기에, Replicate되어 OnRep_OverlappingWeapon이 호출된다. 서버에서는 Replicate가 동작되지 않기에 OnRep_OverlappingWeapon이 호출되지 않으므로, 이 함수에서 위젯을 표시/비활성화한다.

// 클라이언트에서 위젯이 표시되도록 한다.
void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
{
	if (OverlappingWeapon)
	{
		OverlappingWeapon->ShowPickupWidget(true);
	}
	// OverlappedWeapon이 바뀌고(Replicate되고) 이전에 OverlappedWeapon의 위젯은 끈다.
	if (LastWeapon)
	{
		LastWeapon->ShowPickupWidget(false);
	}
}

OnRep_OverlappingWeapon에서 Overlap된 AWeapon의 위젯 컴포넌트를 표시한다. OnRep_함수는 하나의 매개변수를 추가할 수 있는데 Replicate되기 전의 해당 변수를 받을 수 있다. 이를 통해 Overlap이 끝났을 때, 위젯 컴포넌트를 다시 비활성화 시킬 수 있다.

 

클라이언트에서 Overlap시 위젯 컴포넌트 표시, End시 위젯 컴포넌트 비활성화