Custom PhysicsCollisionHandler never receives Collision Notifies

Hello!
I am attempting to implement physics sounds, but don’t want to do the rather labor-intensive process of adding an OnHit event to every physics object we might potentially have.
Instead, I’d really like to check Physical Materials and play their respective sounds based on impact strength. I gather the best way to do so is by overriding UPhysicsCollisionHandler…!

I’ve managed to do so, and I’ve set a default sound for it to use. Nothing else has been changed.

Here is the default code inside UPhysicsCollisionHandler:

void UPhysicsCollisionHandler::DefaultHandleCollision_AssumesLocked(const FRigidBodyCollisionInfo& MyInfo, const FRigidBodyCollisionInfo& OtherInfo, const FCollisionImpactData& RigidCollisionData)
{
	FRigidBodyContactInfo ContactInfo = RigidCollisionData.ContactInfos[0];

	FBodyInstance* BodyInst0 = MyInfo.GetBodyInstance();
	FBodyInstance* BodyInst1 = OtherInfo.GetBodyInstance();

	if(BodyInst0 && BodyInst1)
	{
		// Find relative velocity.
		FVector Velocity0 = BodyInst0->GetUnrealWorldVelocityAtPoint_AssumesLocked(ContactInfo.ContactPosition);
		FVector AngularVel0 = BodyInst0->GetUnrealWorldAngularVelocity_AssumesLocked();

		FVector Velocity1 = BodyInst1->GetUnrealWorldVelocityAtPoint_AssumesLocked(ContactInfo.ContactPosition);
		FVector AngularVel1 = BodyInst1->GetUnrealWorldAngularVelocity_AssumesLocked();

		const FVector RelVel = Velocity1 - Velocity0;

		// Then project along contact normal, and take magnitude.
		float ImpactVelMag = FMath::Abs(RelVel | ContactInfo.ContactNormal);

		// Difference in angular velocity between contacting bodies.
		float AngularVelMag = (AngularVel1 - AngularVel0).Size() * 70.f;

		// If bodies collide and are rotating quickly, even if relative linear velocity is not that high, use the value from the angular velocity instead.
		if (ImpactVelMag < AngularVelMag)
		{
			ImpactVelMag = AngularVelMag;
		}

		UWorld* World = GetWorld();

		if( (World != NULL) && (DefaultImpactSound != NULL) && (ImpactVelMag > ImpactThreshold) )
		{
			UGameplayStatics::PlaySoundAtLocation(World, DefaultImpactSound, ContactInfo.ContactPosition);

			LastImpactSoundTime = World->GetTimeSeconds();
		}
	}
}

void UPhysicsCollisionHandler::HandlePhysicsCollisions_AssumesLocked(TArray<FCollisionNotifyInfo>& PendingCollisionNotifies)
{
	// Fire any collision notifies in the queue.
	for(int32 i=0; i<PendingCollisionNotifies.Num(); i++)
	{
		// If it hasn't been long enough since our last sound, just bail out
		const float TimeSinceLastImpact = GetWorld()->GetTimeSeconds() - LastImpactSoundTime;
		if(TimeSinceLastImpact < ImpactReFireDelay)
		{
			break;
		}

		// See if this impact is between 2 valid actors
		const FCollisionNotifyInfo& NotifyInfo = PendingCollisionNotifies[i];
		if( NotifyInfo.IsValidForNotify() &&
			NotifyInfo.RigidCollisionData.ContactInfos.Num() > 0 )
		{
			DefaultHandleCollision_AssumesLocked(NotifyInfo.Info0, NotifyInfo.Info1, NotifyInfo.RigidCollisionData);
		}
	}
}

I have set the physics collision handler appropriately in the project settings. Some debug print reveals it IS running HandlePhysicsCollision_AssumeLocked but never receives any notifications of physics collisions, so the for loop never runs.

Can anyone provide any insight?
Thank you!

1 Like

I am having the same issue. An update to this thread if you figured it out would be great!

I have not figured this out, I’m afraid. I ended up just implementing the feature I needed in each actor.

Sorry. If you find a solution, let me know!

Jeez, just came across this same issue 6 years later. Has anyone found a fix for this?

Update: As of Unreal Engine 5.1, this issue appears to be fixed? It’s working fine for me now, at least.

This is one of the few search results discussing PhysicsCollisionHandler, which is itself undocumented, so I’ll include my notes here for posterity:

  • Using 5.3.1, I have successfully replaced PhysicsCollisionHandler with another class.
  • I found that the project setting to replace the default PhysicsCollisionHandler did not do anything; I had to replace it in the World Settings.
  • The engine expects any substitute for PhysicsCollisionHandler to subclass UPhysicsCollisionHandler
  • The probable cause of @WinterDraws’ original issue is that DefaultHandleCollision_AssumesLocked is not a virtual method, and thus overriding it in a subclass does not work as written because the superclass HandlePhysicsCollisions_AssumesLocked will invoke UPhysicsCollisionHandler::DefaultHandleCollision_AssumesLocked rather than the subclass::DefaultHandleCollision_AssumesLocked.

The solution would be to override HandlePhysicsCollisions_AssumesLocked, which is virtual, rather than DefaultHandleCollision_AssumesLocked.