Playing sound/particle at impact location & diff. surfaces

Hey I’m currently getting the hang of unreal and trying to figure out the basics.

What’s the correct way to play a sound and spawn particles when an object collides with different phys materials?
(For example running into a wall or a box falling and hitting the floor)
Do you use a trace or would you take the velocity/velocity length? I’ve tried out multiple approaches but it all feels somewhat off and I don’t want to learn it badly from the start.

Could someone give an example of what would be a solid way/“best practice”? I could find surprisingly little on this subject when I thought this must be a very common thing people need to do.

Any help would be greatly appreciated.

The answer would be : it depends

Most of the time you want to spawn effects from a collision or a line trace that would already have a Hit generated, in which case you should simply add a listener or override the appropriate OnHit, and then getting the phys material from the Hit, (although you should probably try to get it from the material of the HitComp as a fallback)
This is my utility function in C++ to get a HitPhysMat with 2 different fallbacks

UPhysicalMaterial* HitPhysMat = Hit.PhysMaterial.Get();
if (! HitPhysMat && IsValid(Hit.GetComponent()))
{
	int32 SectionIndex;
	// we try to get the correct material
	UMaterialInterface* Material = Hit.GetComponent()->
	GetMaterialFromCollisionFaceIndex(Hit.FaceIndex, SectionIndex);

	// fallback if we couldn't get it from FaceIndex
	if (!Material)
	{
		Material = Hit.GetComponent()->GetMaterial(0);
	}

	if (Material)
	{
		HitPhysMat = Material->GetPhysicalMaterial();
	}
}
return HitPhysMat;

You might have to enable Generate Hit to make sure a hit is generated

If however you don’t have a hit I think a line trace of the collision as close as possible from impact would also be able to get you a good HitResult to get a PhysMat.

In the case of a character landing since you mentioned it you should probably override OnLanded and use it’s hit.

Also, you might want to use PhysMaterials themselves or the Surface value on them, depending on your needs

More importantly however, you will need to store the data containing the reaction of your hit somewhere and for that I would recommend using a DataAsset with TMaps and default values inside it, and then create one for every different reaction, you might want to use the same reaction for 2 similar bullets for instance but you’d need a different one for your character landing, and another one for an arrow getting stuck in a wall as an example.

DataAssets most importantly allow you to not have to copy this (potentially very heavy) data on every single instance that reacts to surface types, imagine if every single one of your projectiles had 3 copies of the same TMap and you have a weapon that shoots that projectile 30 times on every shot, and fires 10 times per second, that’s 900 copies of TMaps with the exact same values as their parent for no reason !

You could technically have alleviated that by storing the reaction data on the weapon instead but then if the weapon gets thrown or deleted before it’s projectile hits a surface you could have nasty nullpointer errors (or not have the data anymore at best)

This was my implementation of the DataAsset :

USTRUCT(BlueprintType)
struct FDecalData
{
	GENERATED_BODY()

	/** material */
	UPROPERTY(EditDefaultsOnly, Category=Decal)
	UMaterialInterface* DecalMaterial = nullptr;

	/** quad size (width & height) */
	UPROPERTY(EditDefaultsOnly, Category=Decal)
	float DecalSize = 256.f;

	/** lifespan */
	UPROPERTY(EditDefaultsOnly, Category=Decal)
	float LifeSpan = 10.f;

	/** fade out */
	UPROPERTY(EditDefaultsOnly, Category=Decal)
	float FadeOutDuration = 2.f;
};

UCLASS()
class YMENOK_API UPhysMaterialReaction : public UDataAsset
{
	GENERATED_BODY()

protected:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Sound)
	USoundBase* DefaultSound;
	
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Sound)
	TMap<UPhysicalMaterial*, USoundBase*> PhysMaterialToSound;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Decal)
	FDecalData DefaultDecal;
	
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Decal)
	TMap<UPhysicalMaterial*, FDecalData> PhysMaterialToDecal;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Particles)
	UFXSystemAsset* DefaultParticles;
	
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Particles)
	TMap<UPhysicalMaterial*, UFXSystemAsset*> PhysMaterialToParticles;

public:
	UFUNCTION(BlueprintPure, Category=Sound)
	USoundBase* GetSoundFor(const UPhysicalMaterial *const PhysMaterial) const;

	UFUNCTION(BlueprintPure, Category=Decal)
	FDecalData GetDecalFor(const UPhysicalMaterial *const PhysMaterial) const;
	
	UFUNCTION(BlueprintPure, Category=Particles)
	UFXSystemAsset* GetParticleSystemFor(const UPhysicalMaterial *const PhysMaterial) const;

	UFUNCTION(BlueprintCallable, Category=Spawn)
	void SpawnEffects(const UObject* const WorldContextObject, FVector const& Location, FRotator const& Rotation, FHitResult const& SurfaceHit) const;
};

You can get physics materials from hit results. For physics objects you can use normal impulse to see how hard it hit, but for characters you’ll have to take their velocity at the moment of hit and calculate based on that, since with character movement the normal impulse will always be 0.