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;
};