Use a Unity Event to relay the message anywhere you want.
using UnityEngine.Events;
public class ControllerEventRelay : MonoBehaviour {
public UnityEvent<ControllerColliderHit> onColliderHit;
void OnControllerColliderHit(ControllerColliderHit hit)
{
onColliderHit?.Invoke(hit);
}
}
Put this script on the parent that has the character controller, then use the event to wire it up to any function accepting a ControllerColliderHit
argument on any component on any object you like. You can even wire up multiple listeners to all respond to each individual collision event.
It looks like this in the Inspector:

Click the "+" button to add a new listener, and select the object that has the function you want to invoke. Then select the component that has that function from the drop-down, and select the function from the "Dynamic ControllerColliderHit" section at the top. Any public
instance method accepting a ControllerColliderHit
as its sole argument will show up as an option to choose here.
You can also add listeners in code:
onColliderHit.AddListener(functionToCall);
OnControllerColliderHit
, you need to distinguish whether the collision is from the ground, the wall or other objects, which makes things complicated. My suggestion is to add a Animation Event named "foot on the ground" to the running animation, which triggers a downward ray detection and plays a sound according to the material. \$\endgroup\$