diff --git a/Runtime/UX/PlayerFootstepSFX.cs b/Runtime/UX/PlayerFootstepSFX.cs
new file mode 100644
index 0000000..b5901fb
--- /dev/null
+++ b/Runtime/UX/PlayerFootstepSFX.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Reality Collective. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using UnityEngine;
+
+#if RTK_LOCOMOTION
+using RealityCollective.ServiceFramework.Services;
+using RealityToolkit.Locomotion;
+#endif
+
+namespace RealityToolkit.Player.UX
+{
+ ///
+ /// Produces a footstep sound effect when the player rig moves.
+ /// Attach to the .
+ ///
+ public class PlayerFootstepSFX : MonoBehaviour
+#if RTK_LOCOMOTION
+ , ILocomotionServiceHandler
+#endif
+ {
+ [SerializeField]
+ private AudioSource audioSource = null;
+
+ [SerializeField]
+ private float stepSize = 1f;
+
+ private float previousStepTime;
+ private Vector2 previousStepPosition;
+ private const float timeout = .5f;
+
+#if RTK_LOCOMOTION
+ private ILocomotionService locomotionService;
+
+ ///
+ /// See .
+ ///
+ private async void Awake()
+ {
+ await ServiceManager.WaitUntilInitializedAsync();
+
+ locomotionService = ServiceManager.Instance.GetService();
+ locomotionService.Register(gameObject);
+ }
+
+ ///
+ /// See .
+ ///
+ private void OnDestroy()
+ {
+ if (locomotionService != null)
+ {
+ locomotionService.Unregister(gameObject);
+ }
+ }
+
+ ///
+ public void OnMoving(LocomotionEventData eventData) => CheckStep();
+
+ ///
+ public void OnTeleportCanceled(LocomotionEventData eventData) { }
+
+ ///
+ public void OnTeleportCompleted(LocomotionEventData eventData) { }
+
+ ///
+ public void OnTeleportStarted(LocomotionEventData eventData) { }
+
+ ///
+ public void OnTeleportTargetRequested(LocomotionEventData eventData) { }
+#else
+ ///
+ /// See .
+ ///
+ private void LateUpdate() => CheckStep();
+#endif
+
+ private void CheckStep()
+ {
+ var time = Time.time;
+ var position = transform.position;
+ var stepPosition = new Vector2(position.x, position.z);
+
+ if (previousStepTime > 0f && time - previousStepTime > timeout)
+ {
+ previousStepTime = time;
+ previousStepPosition = stepPosition;
+ return;
+ }
+
+ var delta = (previousStepPosition - stepPosition).magnitude;
+ if (delta > stepSize)
+ {
+ previousStepTime = time;
+ previousStepPosition = stepPosition;
+ PlaySFX();
+ }
+ }
+
+ private void PlaySFX() => audioSource.Play();
+ }
+}
diff --git a/Runtime/UX/PlayerFootstepSFX.cs.meta b/Runtime/UX/PlayerFootstepSFX.cs.meta
new file mode 100644
index 0000000..a63b527
--- /dev/null
+++ b/Runtime/UX/PlayerFootstepSFX.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 910a9635a0141f84db9af5d4775b3c3f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 08c0fd91bdca45afa09ec64323cf3848, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant: