[UE5] 특정 클라이언트에만 Pickup widget 표시하기

kkado·2024년 6월 14일
0

UE5

목록 보기
43/61
post-thumbnail

배틀그라운드처럼 땅에 아이템을 습득할 수 있고 이를 이용해 멀티플레이어 대전을 할 때, 아이템에 다가가면 pick up 메시지를 출력해보자.

아래 순서대로 진행하면 될 것 같다.

  • 먼저 Weapon 클래스를 만든다.
  • Character 클래스에서는 가까이 다가간 Weapon의 레퍼런스가 필요하다. 이 레퍼런스를 Replicate 변수로 설정한다.
  • Weapon 클래스에서 Collision Overlap이 발생하면 그 액터의 레퍼런스를 this 로 설정한다.
  • this 로 설정됐으면 값이 변했으므로, Replicate 된다.
  • Rep Notify 함수를 정의해서, 이 함수 내에서 widget의 visibility를 true로 변경한다.

Weapon class, Character class

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 변수를 등록할 때 조건을 명시할 수 있다.

DOREPLIFETIME_CONDITION

뒤에 _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);
		}
	}
}

최종 코드 모습


EndOverlap

이제 아이템에서 멀어지면 메시지를 다시 숨기는 기능을 만들어야 한다.
간단한 생각으로는, OnSphereEndOverlap 와 같은 함수를 만들어서 OverlappingWeaponnullptr로 설정해 주고, 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);
	}
}
profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글