float ATrapperPlayer::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float Damage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
if (IsDead) return Damage;
Damage = FMath::Min(CurrentHP, Damage);
if (HasAuthority())
{
MulticastCharacterDamaged(Damage);
}
else if (IsLocallyControlled())
{
ServerRPCCharacterDamaged(Damage);
}
return Damage;
}
TakeDamage()
함수 내에서 데미지를 계산 후, 각각의 롤에 맞게 멀티캐스트 RPC 또는 Server RPC를 호출해준다.
void ATrapperPlayer::ServerRPCCharacterDamaged_Implementation(float Damage)
{
MulticastCharacterDamaged(Damage);
}
void ATrapperPlayer::MulticastCharacterDamaged_Implementation(float Damage)
{
CurrentHP -= Damage;
UE_LOG(LogTemp, Warning, TEXT("Damage : %f, HP : %f"), Damage, CurrentHP);
if (CurrentHP <= 0)
{
if (HasAuthority())
{
MulticastCharacterDeath();
}
else if (IsLocallyControlled())
{
ServerRPCCharacterDeath();
}
return;
}
Alive();
}
데미지를 실제로 적용한 후, 만약 현재 HP가 0보다 작거나 같다면 Death RPC를 호출해준다.
void ATrapperPlayer::Alive()
{
IsDamaged = true;
GetCharacterMovement()->SetMovementMode(MOVE_None);
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DamagedAnimationMontage)
AnimInstance->Montage_Play(DamagedAnimationMontage, 1.0);
}
void ATrapperPlayer::DamagedAnimEnd()
{
IsDamaged = false;
GetCharacterMovement()->SetMovementMode(MOVE_Walking);
}
조건에 해당되지 않는다면 Alive()
함수를 호출한다. 맞은동안 무브먼트를 None으로 바꾸어 움직이지 못하게 했고, 애니메이션 몽타주가 끝나야 다시 움직일 수 있게 된다.
void ATrapperPlayer::ServerRPCCharacterDeath_Implementation()
{
MulticastCharacterDeath();
}
void ATrapperPlayer::MulticastCharacterDeath_Implementation()
{
Death();
}
void ATrapperPlayer::Death()
{
IsDead = true;
GetCharacterMovement()->SetMovementMode(MOVE_None);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if(AnimInstance && DeathAnimationMontage)
AnimInstance->Montage_Play(DeathAnimationMontage, 1.0);
ATrapperPlayerController* PlayerController = Cast<ATrapperPlayerController>(GetController());
if (PlayerController && IsLocallyControlled())
{
PlayerHud->SetVisibility(ESlateVisibility::Hidden);
PlayerController->PlayerDeath(this);
}
}
플레이어가 Death()
상태가 되면, 무브먼트 모드를 None으로 변경, Collision Enabled 상태를 NoCollision으로 바꿔주고 죽음 몽타주 애니메이션을 실행시켜 주었다.
플레이어 컨트롤러가 존재하고 현재 로컬 플레이어일 경우, 플레이어 Hud에 접근해 숨겨주고 플레이어 컨트롤러의 캐릭터 사망 함수를 호출해준다.
void ATrapperPlayerController::PlayerDeath(ATrapperPlayer* PlayerRef)
{
if (!IsLocalController()) return;
ControlledPlayer = PlayerRef;
ControlledPlayer->DisableInput(this);
if (IsValid(RespawnScreen))
{
RespawnScreen->SetVisibility(ESlateVisibility::Visible);
}
GetWorldTimerManager().SetTimer(RespawnTimerHandle, FTimerDelegate::CreateLambda([&]
{
RespawnScreen->SetVisibility(ESlateVisibility::Hidden);
ControlledPlayer->EnableInput(this);
if (ControlledPlayer->HasAuthority())
{
ControlledPlayer->MulticastCharacterRespawn();
}
else if (ControlledPlayer->IsLocallyControlled())
{
ControlledPlayer->ServerRPCCharacterRespawn();
}
}
), 1.0f, false, RestartDelay);
UE_LOG(LogTemp, Warning, TEXT("Player Death!"));
}
플레이어의 Input을 해제해주고, Respawn HUD를 띄워주고, 타이머를 설정해 플레이어를 리스폰해주었다. Respawn HUD를 띄우는 과정에서, 엔진이 진짜 많이 터졌었다.. 로컬 플레이어가 아닐경우 리턴, Respawn Screen과 ControlledPlayer 모두 Null 처리를 해줬음에도 터지는 이상한 현상이 계속 발생했었는데, 결론적으로는 RespawnScreen 위젯 생성을 BeginPlay()
에서 해주는걸로 해결했다.
원래는 리스폰 UI를 띄울 때 이 함수 내부에서 위젯을 생성한 후 AddToViewport()
함수를 통해 위젯을 띄우려고 했는데, 람다에서 변수를 캡쳐할 때 문제가 생기는건지 무슨 이유인지는 모르겠지만 계속 뭐 하나가 없다고 터져대서 결국 초반에 생성해주고 껐다켰다 하는 식으로 해결했다.
UI 내의 리스폰 숫자가 초기화되지 않는 문제가 있긴 하지만, 조만간 해결하기로 하고 우선은 정상적으로 잘 작동한다.
void ATrapperPlayer::ServerRPCCharacterRespawn_Implementation()
{
MulticastCharacterRespawn();
}
void ATrapperPlayer::MulticastCharacterRespawn_Implementation()
{
Respawn();
}
무한 RPC 생성... 이쯤되면 내가 알고 쓰는게 맞는건지 싶어진다... 그치만 클라이언트에서 다른데로 정보를 보내려면 이거밖에 없지 않나 ㅠ_ㅠ
void ATrapperPlayer::Respawn()
{
if (IsLocallyControlled())
{
PlayerHud->SetVisibility(ESlateVisibility::Visible);
}
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathAnimationMontage)
AnimInstance->Montage_Stop(0.f, DeathAnimationMontage);
GetCharacterMovement()->SetMovementMode(MOVE_Walking);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
TeleportTo(RespawnPoint, GetActorRotation());
IsDamaged = false;
IsDead = false;
CurrentHP = MaxHP;
}
로컬일 경우 HUD를 활성화해주고, 죽음 몽타주를 멈춰준 뒤 무브먼트 모드와 콜리전을 다시 활성화해줬다. 설정한 리스폰 위치로 돌아감과 동시에, 중요한 값들을 초기화 해준다.