Quick answer: Overlap events don’t carry per-face information. Enable complex collision on the mesh, switch to OnComponentHit events or line traces with bReturnPhysicalMaterial true, and read HitResult.PhysMaterial.

Your footstep system casts an overlap to decide whether the player is on stone, wood, or grass. Every overlap comes back with a null PhysicalMaterial. You assigned materials in the static mesh editor, you can see them on the mesh, and the overlap confirms the character is touching the ground. The bug is that Overlap events don’t expose face data.

Why Overlap Misses the Material

An overlap in Unreal is a boolean “these two volumes intersect” query. It doesn’t compute contact points or which face was touched. A PhysicalMaterial is a per-face property. Without a contact point, the engine has nothing to look up, so it returns null.

Hit events, by contrast, compute a contact point during the sweep. The FHitResult that arrives in OnComponentHit includes PhysMaterial, ImpactPoint, ImpactNormal, and FaceIndex.

The Fix

Step 1: Enable complex collision on the mesh. Open the static mesh asset. Set Collision Complexity to Use Complex as Simple, or check bTraceComplexOnMove. This makes the triangle mesh queryable, which is required for per-face material lookups.

Step 2: Use a line trace with PhysMat return.

FCollisionQueryParams params;
params.bReturnPhysicalMaterial = true;

FHitResult hit;
FVector start = GetActorLocation() + FVector(0, 0, 50);
FVector end = start - FVector(0, 0, 200);

if (GetWorld()->LineTraceSingleByChannel(hit, start, end, ECC_Visibility, params))
{
    UPhysicalMaterial* mat = hit.PhysMaterial.Get();
    if (mat) PlayFootstep(mat->SurfaceType);
}

Step 3: Or use OnComponentHit for contact-driven events. For physics objects that land on a surface, bind to the component’s OnHit event. The event’s Hit parameter has PhysMaterial populated automatically.

Surface Types

Define Surface Types in Project Settings → Physics. Each is an enum value (SurfaceType1–63) that you map to a designer-friendly name. Reference them in game code via the enum instead of comparing material assets directly.

Verifying

Add a UE_LOG(LogTemp, Display, TEXT("PhysMat: %s"), *hit.PhysMaterial.Get()->GetName()) in the trace result. Walk around the map and confirm the log reflects the actual surface under each footstep. If every result is the same default material, complex collision isn’t enabled.

“Overlap tells you volumes touch. Hit tells you where and what. Pick the right query for the job and PhysicalMaterial starts working.”

Related Issues

For overlap events that don’t fire at all, see Unreal overlap event not triggering. For per-face logic on landscape, see Unreal landscape material stretching slopes.

For gameplay systems that need surface type, always design around line traces. Overlap is the wrong primitive.