배틀그라운드처럼 땅에 아이템을 습득할 수 있고 이를 이용해 멀티플레이어 대전을 할 때, 아이템에 다가가면 pick up 메시지를 출력해보자.
아래 순서대로 진행하면 될 것 같다.
this
로 설정한다.this
로 설정됐으면 값이 변했으므로, Replicate 된다.WeaponClass를 만들고, overlap을 감지할 SphereComponent를 만든다. HUD 유저위젯도 부착한다.
Character 클래스에서는 OverlappingWeapon
이라는 이름으로 레퍼런스를 만들고, 이 변수를 Replicate로 등록함과 동시에 OnRep_OverlappingWeapon()
이라는 RepNotify 함수도 만든다.
UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
class AWeapon* OverlappingWeapon;
UFUNCTION()
void OnRep_OverlappingWeapon();
OverlappingWeapon
의 세터는 퍼블릭으로 만든다.
FORCEINLINE void SetOverlappingWeapon(AWeapon* Weapon) { OverlappingWeapon = Weapon; }
OnSphereBeginOverlap
함수를 이용해, Character 클래스의 OverlappingWeapon
을 지정한다.
GetLifetimeReplicatedProps
함수를 오버라이딩해서 이 변수를 Replicate 변수로 등록한다.
void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ABlasterCharacter, OverlappingWeapon);
}
RepNotify에는 Weapon의 ShowPickupWidget()
함수를 통해 visibility를 true로 만든다.
void ABlasterCharacter::OnRep_OverlappingWeapon()
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
}
// Weapon::ShowPickupWidget 함수
void AWeapon::ShowPickupWidget(bool bShowWidget)
{
if (PickupWidget)
{
PickupWidget->SetVisibility(bShowWidget);
}
}
이렇게 되면 캐릭터가 가지고 있는 Replicate 변수인 OverlappingWeapon
이 Weapon 클래스에 의해 업데이트되며, 업데이트 될 때마다 RepNotify 함수인 OnRep_OverlappingWeapon()
함수가 실행될 것이다.
그런데 위 사진과 같이 문제가 발생한다. 다른 캐릭터가 접근해도 클라이언트에서 pickup 메시지가 뜬다.
이 문제가 발생하는 이유는 다음과 같다.
OverlappingWeapon
이 변경되고, OnRep_OverlappingWeapon
이 실행됨OnRep_OverlappingWeapon
함수에 의해 아이템의 위젯 visibility가 변경됨즉 내가 조종하고 있지 않은, Simulated Proxy 권한을 가진 캐릭터들의 움직임에 의해서 아이템의 HUD가 활성화 된 것이다.
따라서, Simulated Proxy 권한의 캐릭터들의 OverlappingWeapon
이 나에게까지 replicate 되면 안 된다.
이와 같은 문제를 해결하기 위해 Replicate 변수를 등록할 때 조건을 명시할 수 있다.
뒤에 _CONDITION
을 붙여서 특정 클라이언트에만 복제할 수 있도록 조건을 명시할 수 있는 매크로를 사용할 수 있다.
마지막 파라미터로 COND
컨디션 enum 값을 설정할 수 있으며 여기서 COND_OwnerOnly
로 설정하면 이 폰을 소유하고 있는 클라이언트에만 복제가 일어나도록 할 수 있다.
따라서 GetLifetimeReplicatedProps
함수를 다음과 같이 변경한다.
void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);
}
이제 아이템에 접촉한 클라이언트에만 UI가 뜨는 것을 볼 수 있다.
그런데 서버이자 클라이언트로 동작하는 클라이언트에서는 내가 가지고 있는 OverlappingWeapon
이 수정되긴 하지만 이것이 복제된 것이 아니므로 OnRep_OverlappingWeapon
이 실행되지 않는다. 즉 로직을 약간 수정할 필요가 있다.
SetOverlappingWeapon
세터 함수에서 서버의 역할을 겸하고 있는 클라이언트에서도 위젯 표시가 실행될 수 있도록 OverlappingWeapon->ShowPickupWidget(true);
구문을 추가한다. 물론, 다른 클라이언트에서는 영향이 없어야 하므로 내가 조종하는 화면에만 보이게끔 하기 위해 IsLocallyControlled()
를 먼저 검증한다.
void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
OverlappingWeapon = Weapon;
if (IsLocallyControlled())
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
}
}
최종 코드 모습
이제 아이템에서 멀어지면 메시지를 다시 숨기는 기능을 만들어야 한다.
간단한 생각으로는, OnSphereEndOverlap
와 같은 함수를 만들어서 OverlappingWeapon
을 nullptr
로 설정해 주고, SetOverlappingWeapon
함수에서 nullptr
에 대한 처리를 추가로 해 주면 될 것 같다.
그런데 기존 아이템에 접근해서 위젯의 visibility를 false로 설정해야 하는데, 아이템에서 멀어졌을 때 OverlappingWeapon
을 null로 설정하면, 다시 그 아이템에 접근할 방법이 없지 않을까?
나이브한 해결책은 SetOverlappingWeapon
함수에서 이전 무기값을 계속 갱신해 나가는 것이다. 예컨대 LastOverlappedWeapon
등의 이름의 변수로 갖고 있는 것이다.
그러나 RepNotify 함수에 파라미터를 넘겨줌으로써 보다 간편하게 이전 값에 접근할 수 있다.
Replicate 변수에 바인딩되어 있는
OnRep
함수에는 그 변수값과 동일한 타입의 파라미터를 넘겨줄 수 있으며, 넘겨줄 경우 이 변수는 Replicate 되기 직전 값을 가지고 있다.
즉 OnRep_OverlappingWeapon
함수에다가 AWeapon
파라미터를 넣어줌으로써 이전값을 처리할 수 있다.
void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
if (LastWeapon)
{
LastWeapon->ShowPickupWidget(false);
}
}
참고용 : OnSphereEndOverlap
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if (BlasterCharacter)
{
BlasterCharacter->SetOverlappingWeapon(nullptr);
}
}