언리얼 multiplay는 Authorative server model을 사용하기 때문에
서버는 게임 세계의 상태를 관리하고 갱신하며 클라이언트는 이 정보를 서버와 동기화해준다.
이때 변수를 서버에서 클라이언트로 복제하여 동기화하는 방식이 variable replication 이다.
멀티플래이 fps게임에서 캐릭터가 바닥에 떨어져있는 weapon과 overlap 될때
pickup widget을 보여주는 코드다.
void AWeapon::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
}
if (PickupWidget)
{
PickupWidget->SetVisibility(false);
}
}
먼저 weapon class를 보면 땅에 떨어진 weapon은 주인인 character class가 없기 때문에
character가 overlap되는 부분은 서버에서 관리해주는게 합리적이다.
따라서 HasAuthority (Local Role에서 ENetRole::ROLE_Authority을 의미한다 따라서 서버다)
를 조건문으로 체크해 서버에서 overlap관련 코드를 다룬다.
pickup widget은 디폴트로 안보이게 설정해둔다.
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if (BlasterCharacter && PickupWidget)
{
PickupWidget->SetVisibility(true);
}
}
overlap이 일어난 character가 있을때 pickup widget을 보이게 설정한다.
위 코드대로 실행했을경우
서버에서는 서버의 캐릭터가 무기와 overlap 되었을때
화면에 pickup widget을 정상적으로 보여주지만
client 캐릭터가 무기와 overlap되었을때 문제가 생긴다.
위 사진을 보면 오른쪽 아래 화면 client가 무기에 다가갔지만 pickup widget을 보여주지 않고
무기에 다가가지 않은 서버의 화면에서 pickup widget이 보인다.
overlap에 관한 함수를 서버에서만 실행해 생긴일인데 이 문제를 해결하기 위해 variable replication이 사용된다.
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
class AWeapon* OverlappingWeapon;
void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);
}
복제될 변수는 선언할때 UPROPERTY에 Replicated 키워드를 사용해야한다.
또한 GetLifetimeReplicatedProps함수를 override해 변수를 등록해야한다.
DOREPLIFETIME_CONDITION(클래스, 변수, 복제 조건) 매크로를 통해 변수를 복제하는데 유의해야할점은 복제 조건이다.
COND_InitialOnly
이 프로퍼티는 초기 번치에만 전송을 시도합니다.
COND_OwnerOnly
이 프로퍼티는 액터의 오너에만 전송합니다.
COND_SkipOwner
이 프로퍼티는 오너를 제외한 모든 접속에 전송합니다.
COND_SimulatedOnly
이 프로퍼티는 시뮬레이션되는 액터에만 전송합니다.
COND_AutonomousOnly
이 프로퍼티는 자율 액터에만 전송합니다.
COND_SimulatedOrPhysics
이 프로퍼티는 시뮬레이션되는 또는 bRepPhysics 액터에 전송합니다.
COND_InitialOrOwner
이 프로퍼티는 초기 패킷시, 또는 액터의 오너에 전송합니다.
COND_Custom
이 프로퍼티에는 별다른 조건이 없지만, SetCustomIsActiveOverride 를 통해 껐다 켰다 토글 기능을 원합니다.
이 프로그램에서는 weapon이 overlap되는 character 오너에만 복제할 것 이므로
COND_OwnerOnly를 사용하는 것이 맞다.
void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
Super::Tick(DeltaTime);
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(false);
}
//change value
OverlappingWeapon = Weapon;
//for server
if (IsLocallyControlled())
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
}
}
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 ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
{
if (OverlappingWeapon)
{
OverlappingWeapon->ShowPickupWidget(true);
}
if (LastWeapon)
{
LastWeapon->ShowPickupWidget(false);
}
}
character class에 overlap되고있는 변수를 선언해 줬으니
SetOverlappingWeapon 함수를 만들어주고 Weapon class에서 overlap이 발생하면
해당 character에 함수를 호출해 변수값을 변경해준다.
변수값이 변경되면 client에서 OnRep_OverlappingWeapon함수가 호출되어 weapon class에 ShowPickupWidget을 호출해 client 화면에서 widget을 보여주게 된다.
Weapon class에서 overlap은 서버에서만 관리해왔으므로
Character class에 SetOverlappingWeapon함수는 서버에서 실행되는 경우밖에 없으며
OnRep_OverlappingWeapon함수는 서버에서 client에 변수값이 복제될때 client에서 호출되는 함수이므로
client에서 실행되는 경우밖에 없다.
따라서 client character가 overlap해 widget을 보여주는 경우는 OnRep_OverlappingWeapon함수에서 다루고
서버 character가 overlap하는경우는 SetOverlappingWeapon 함수에서 IsLocallyControlled() 서버가 컨트롤 하는 캐릭터인지 확인하고 widget을 보여준다.
(만약 IsLocallyControlled를 확인안하면 서버의 화면에서는 다른 캐릭터가 overlap해도 widget을 보여주게된다)