I’ve been trying to implement a death camera within ShooterGame’s AShooterPlayerController::PawnPendingDestroy and have noticed that sometimes my death camera code runs and sometimes it does not.
In ShooterCharacter::OnDeath a call is made to DetachFromControllerPendingDestroy() that I believe is causing a race condition preventing AShooterPlayerController::PawnPendingDestroy from being called on the client.
I believe the server’s call to DetachFromControllerPendingDestroy() removes the character’s controller (controller = NULL) which is replicated to the client before the client’s own call to DetachFromControllerPendingDestroy(). Moments later when the client passes through DetachFromControllerPendingDestroy(), the controller is NULL and the call to Controller->PawnPendingDestroy(this) is never made.
Is it possible that there is a race condition on controller = null?
Not sure if there is an easy fix for this, any suggestions?
AShooterCharacter::OnDeath()
void AShooterCharacter::OnDeath(float KillingDamage, struct FDamageEvent const& DamageEvent, class APawn* PawnInstigator, class AActor* DamageCauser)
{
if (bIsDying)
{
return;
}
bReplicateMovement = false;
bTearOff = true;
bIsDying = true;
if (Role == ROLE_Authority)
{
ReplicateHit(KillingDamage, DamageEvent, PawnInstigator, DamageCauser, true);
// play the force feedback effect on the client player controller
APlayerController* PC = Cast<APlayerController>(Controller);
if (PC && DamageEvent.DamageTypeClass)
{
UShooterDamageType *DamageType = Cast<UShooterDamageType>(DamageEvent.DamageTypeClass->GetDefaultObject());
if (DamageType && DamageType->KilledForceFeedback)
{
PC->ClientPlayForceFeedback(DamageType->KilledForceFeedback, false, "Damage");
}
}
}
// cannot use IsLocallyControlled here, because even local client's controller may be NULL here
if (GetNetMode() != NM_DedicatedServer && DeathSound && Mesh1P && Mesh1P->IsVisible())
{
UGameplayStatics::PlaySoundAtLocation(this, DeathSound, GetActorLocation());
}
// remove all weapons
DestroyInventory();
// switch back to 3rd person view
UpdatePawnMeshes();
DetachFromControllerPendingDestroy();
StopAllAnimMontages();
if (LowHealthWarningPlayer && LowHealthWarningPlayer->IsPlaying())
{
LowHealthWarningPlayer->Stop();
}
if (RunLoopAC)
{
RunLoopAC->Stop();
}
// disable collisions on capsule
CapsuleComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
CapsuleComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
if (Mesh)
{
static FName CollisionProfileName(TEXT("Ragdoll"));
Mesh->SetCollisionProfileName(CollisionProfileName);
}
SetActorEnableCollision(true);
// Death anim
float DeathAnimDuration = PlayAnimMontage(DeathAnim);
// Ragdoll
if (DeathAnimDuration > 0.f)
{
GetWorldTimerManager().SetTimer(this, &AShooterCharacter::SetRagdollPhysics, FMath::Min(0.1f, DeathAnimDuration), false);
}
else
{
SetRagdollPhysics();
}
}
APawn::DetachFromControllerPendingDestroy()
void APawn::DetachFromControllerPendingDestroy()
{
if ( Controller != NULL && Controller->GetPawn() == this )
{
AController* OldController = Controller;
Controller->PawnPendingDestroy(this);
if (Controller != NULL)
{
Controller->UnPossess();
Controller = NULL;
}
}
}
AShooterPlayerController:PawnPendingDestroy()
void AShooterPlayerController::PawnPendingDestroy(APawn* P)
{
FVector CameraLocation = P->GetActorLocation() + FVector(0, 0, 300.0f);
FRotator CameraRotation(-90.0f, 0.0f, 0.0f);
FindDeathCameraSpot(CameraLocation, CameraRotation);
Super::PawnPendingDestroy(P);
ClientSetSpectatorCamera(CameraLocation, CameraRotation);
}