예시를 통한 RPC 구현
시나리오
- 플레이어가 키 입력을 통해 지뢰 아이템을 생성
- 플레이어가 지뢰 아이템을 밟을 시에 아이템이 동작함
- 아이템은 동작 시에 폭발 효과 발생
플레이어가 지뢰 아이템을 생성
- 플레이어가 생성한 지뢰 아이템이 다른 사람들에게 보이게 하려면
-> Server에서 액터를 스폰시키고, 해당 액터를 클라이언트들에게 복제(액터는 레플리케이션 필수)
-> Server에게 액터를 스폰시켜달라고 ServerRPC를 통해 요청
// DXPlayerCharacter.h
class ADXPlayerCharacter : public ADXCharacterBase
{
...
UFUNCTION(Server, Reliable, WithValidation)
void ServerRPCSpawnMine();
...
}
// DXPlayerCharacter.cpp
void ADXPlayerCharacter::HandleMineInput(const FInputActionValue& InValue)
{
if (IsLocallyControlled())
{
ServerRPCSpawnMine();
}
}
void ADXPlayerCharacter::ServerRPCSpawnMine_Implementation()
{
if (IsValid(MineClass))
{
ADXMine* SpawnedMine = GetWorld()->SpawnActor<ADXMine>(MineClass, SpawnLocation, SpawnRotation);
}
}
void ADXPlayerCharacter::ServerRPCSpawnMine_Validate()
{
return true;
}
지뢰가 작동하면서 폭발 이펙트를 재생
- 폭발 이펙트는 모든 클라이언트에서 재생되어야 한다.
-> 지뢰 작동 시에 NetMulticast RPC를 통해 모든 클라이언트에서 이펙트 재생 함수 실행
-> 이때 지뢰 폭발 데미지는 서버에서만 처리
// DXMine.h
class ADXMine : public AActor
{
...
UFUNCTION()
void OnMineBeginOverlap(AActor* OverlappedActor, AActor* OtherActor);
UFUNCTION()
void MulticastRPCSpawnEffect();
...
}
// DXMine.cpp
void ADXMine::BeginPlay()
{
if (!OnActorBeginOverlap.IsAlreadyBound(this, &ThisClass::OnMineBeginOverlap))
{
OnActorBeginOverlap.AddDynamic(this, &ThisClass::OnMineBeginOverlap);
}
}
void ADXMine::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (OnActorBeginOverlap.IsAlreadyBound(this, &ThisClass::OnMineBeginOverlap))
{
OnActorBeginOverlap.RemoveDynamic(this, &ThisClass::OnMineBeginOverlap);
}
}
void ADXMine::OnMineBeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
{
if (HasAuthority())
{
MulticastRPCSpawnEffect();
}
}
void ADXMine::MulticastRPCSpawnEffect_Implementation()
{
if (!HasAuthority())
{
Particle->Activate(true);
}
}
폭발한 지뢰가 다시 폭발하지 않게
- 위의 시나리오까지 코드를 작성하게 되면 밟을 시에 무한하게 터짐
-> 폭발한 지뢰가 다시 폭발하지 않게끔 하고 싶음
-> bIsExploded 변수를 통해서 폭발 여부를 확인하고 터지게 함
-> 해당 방법은 NetCullDistance 밖에 있거나, 새로 추가된 플레이어는 해당 사실을 모르기 때문에 또 폭발함
- 해당 변수를 Property Replication을 통해서 동기화시킴
// DXMine.h
class ADXMine : public AActor
{
...
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(Replicated)
uint8 bIsExploded : 1;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float NetCullDistance;
...
}
// DXMine.cpp
void ADXMine::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps)
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREFLIFETIME(ThisClass, bIsExploded);
}
void ADXMine::MulticastRPCSpawnEffect_Implementation()
{
if (HasAuthority())
{
bIsExploded = true;
return;
}
if (bIsExploded)
{
return;
}
Particle->Activate(true);
}
- Property Replication을 적용해도 폭발이 발생하지 않음
- 클라이언트로 Property Replication이 되는 시점이 너무 빠름
NetMulticastRPC가 실행되기 전에 이미 bIsExploded가 true가 되서 작동이 안 됨
서버 클라
MulticastRPCSpawnEffect 실행
bIsExploded = true
bIsExploded = true
MulticastRPCSpawnEffect 실행
- 따라서 충돌 검사 자체는 서버, 클라이언트 둘 다 하기 때문에 충돌할 때, 바로 로직을 실행
// DXMine.cpp
void ADXMine::OnMineBeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
{
// 서버에서 폭발 여부 변경
if (HasAuthority())
{
if (!bIsExploded)
{
bIsExploded = true;
}
}
// 클라이언트에서는 바로 이펙트 재생
else
{
if (!bIsExploded)
{
Particle->Activate(true);
}
}
}
출처 : 팀스파르타 내일배움캠프