So I spent quite a bit of time looking into this issue and made a fix for it in our code that overrides the engine’s code.
Here is what I discovered:
The crash occurs in this function:
void UCharacterMovementComponent::PerformMovement(float DeltaSeconds)
On this line:
checkf(!Velocity.ContainsNaN(), TEXT("UCharacterMovementComponent::PerformMovement: Velocity contains NaN (%s: %s)\n%s"), *GetPathNameSafe(this), *GetPathNameSafe(GetOuter()), *Velocity.ToString());
So I would assume that whoever added that line is probably really interested as to the cause of this issue. I discovered it became NaN after calling this function:
ApplyAccumulatedForces(DeltaSeconds);
In this function I discovered that the PendingImpulseToApply variable contained a NaN in the Z axis. This is only set in the AddImpulse function so I set a breakpoint to find out where / when it occurred and the NaN occurred coming from this function:
void ACharacter::ApplyDamageMomentum(float DamageTaken, FDamageEvent const& DamageEvent, APawn* PawnInstigator, AActor* DamageCauser)
Then digging down deeper it came from:
DamageEvent.GetBestHitInfo(this, PawnInstigator, HitInfo, ImpulseDir);
And ImpulseDir is set by this:
OutImpulseDir = HitInstigator ?
( OutHitInfo.ImpactPoint - HitInstigator->GetActorLocation() ).GetSafeNormal()
: FVector::ZeroVector;
So in case someone wants to fix it, that is where your NaN is coming from and causing crashes. In our code I overrode the Character’s ApplyDamageMomentum function and checked if it was ContainsNaN and if so I set it to 0.0f like this (see the comment (XXX Custom code) guards):
void AXXXSoldier::ApplyDamageMomentum(float DamageTaken, FDamageEvent const& DamageEvent, APawn* PawnInstigator, AActor* DamageCauser)
{
UDamageType const* const DmgTypeCDO = DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>();
float const ImpulseScale = DmgTypeCDO->DamageImpulse;
if ((ImpulseScale > 3.0f) && (CharacterMovement != nullptr))
{
FHitResult HitInfo;
FVector ImpulseDir;
DamageEvent.GetBestHitInfo(this, PawnInstigator, HitInfo, ImpulseDir);
/** XXX Custom code below **/
if (ImpulseDir.ContainsNaN())
{
#if !UE_BUILD_SHIPPING
UE_LOG(LogActor, Error, TEXT("ApplyDamageMomentum ImpulseDir contains NaN (would have caused a crash):%s"), *ImpulseDir.ToCompactString());
#endif
if (FMath::IsNaN(ImpulseDir.X) || !FMath::IsFinite(ImpulseDir.X))
{
ImpulseDir.X = 0.0f;
}
if (FMath::IsNaN(ImpulseDir.Y) || !FMath::IsFinite(ImpulseDir.Y))
{
ImpulseDir.Y = 0.0f;
}
if (FMath::IsNaN(ImpulseDir.Z) || !FMath::IsFinite(ImpulseDir.Z))
{
ImpulseDir.Z = 0.0f;
}
}
/** XXX Custom code above **/
FVector Impulse = ImpulseDir * ImpulseScale;
bool const bMassIndependentImpulse = !DmgTypeCDO->bScaleMomentumByMass;
// limit Z momentum added if already going up faster than jump (to avoid blowing character way up into the sky)
{
FVector MassScaledImpulse = Impulse;
if (!bMassIndependentImpulse && CharacterMovement->Mass > SMALL_NUMBER)
{
MassScaledImpulse = MassScaledImpulse / CharacterMovement->Mass;
}
if ((CharacterMovement->Velocity.Z > GetDefault<UCharacterMovementComponent>(CharacterMovement->GetClass())->JumpZVelocity) && (MassScaledImpulse.Z > 0.0f))
{
Impulse.Z *= 0.5f;
}
}
CharacterMovement->AddImpulse(Impulse, bMassIndependentImpulse);
}
}