using System; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(AudioSource))] public class FootstepSound : MonoBehaviour { [Serializable] public struct MeshSurface { public string tag; public AudioClip clip; } [Header("Foot Transforms (final IK)")] public Transform leftFoot; public Transform rightFoot; [Header("Raycast / Detection")] public float raycastDistance = 0.15f; public float downwardSpeedThreshold = -0.03f; public float footstepCooldown = 0.2f; [Header("Terrain → AudioClips (index = Terrain Layer)")] public AudioClip[] terrainLayerClips; [Header("Mesh → AudioClips (by Tag)")] public List meshSurfaces = new List(); [Header("Physics Mask for Raycast")] public LayerMask terrainAndMeshMask = -1; private AudioSource _src; private Vector3 _prevLeft; private Vector3 _prevRight; private bool _wasLeftGrounded; private bool _wasRightGrounded; private float _nextLeftTime; private float _nextRightTime; private void Awake() { _src = GetComponent(); _src.playOnAwake = false; _src.spatialBlend = 1f; if ((bool)leftFoot) { _prevLeft = leftFoot.position; } if ((bool)rightFoot) { _prevRight = rightFoot.position; } } private void LateUpdate() { if ((bool)leftFoot) { HandleFoot(leftFoot, ref _prevLeft, ref _wasLeftGrounded, ref _nextLeftTime); } if ((bool)rightFoot) { HandleFoot(rightFoot, ref _prevRight, ref _wasRightGrounded, ref _nextRightTime); } } private void HandleFoot(Transform foot, ref Vector3 prevPos, ref bool wasGrounded, ref float nextAllowedTime) { Vector3 position = foot.position; Vector3 vector = (position - prevPos) / Time.deltaTime; RaycastHit hitInfo; bool flag = Physics.Raycast(position + Vector3.up * 0.02f, Vector3.down, out hitInfo, raycastDistance, terrainAndMeshMask, QueryTriggerInteraction.Ignore); if (!wasGrounded && flag && vector.y < downwardSpeedThreshold && Time.time >= nextAllowedTime) { PlayFootstep(in hitInfo); nextAllowedTime = Time.time + footstepCooldown; } wasGrounded = flag; prevPos = position; } private void PlayFootstep(in RaycastHit hit) { AudioClip audioClip = null; audioClip = ((Physics.OverlapSphere(hit.point, 0.1f, LayerMask.GetMask("Water")).Length != 0) ? GetMeshClip("Water") : ((!hit.collider.TryGetComponent(out var component)) ? GetMeshClip(hit.collider.tag) : GetTerrainClip(component, hit.point))); if (!(audioClip == null)) { _src.pitch = UnityEngine.Random.Range(0.93f, 1.07f); _src.PlayOneShot(audioClip); } } private AudioClip GetTerrainClip(Terrain terrain, Vector3 worldPoint) { TerrainData terrainData = terrain.terrainData; if (terrainData == null || terrainData.alphamapLayers == 0) { return null; } Vector3 vector = worldPoint - terrain.transform.position; float num = Mathf.Clamp01(vector.x / terrainData.size.x); float num2 = Mathf.Clamp01(vector.z / terrainData.size.z); int x = Mathf.Clamp(Mathf.RoundToInt(num * (float)(terrainData.alphamapWidth - 1)), 0, terrainData.alphamapWidth - 1); int y = Mathf.Clamp(Mathf.RoundToInt(num2 * (float)(terrainData.alphamapHeight - 1)), 0, terrainData.alphamapHeight - 1); float[,,] alphamaps = terrainData.GetAlphamaps(x, y, 1, 1); int num3 = 0; float num4 = 0f; for (int i = 0; i < alphamaps.GetLength(2); i++) { float num5 = alphamaps[0, 0, i]; if (num5 > num4) { num4 = num5; num3 = i; } } if (num3 >= terrainLayerClips.Length) { return null; } return terrainLayerClips[num3]; } private AudioClip GetMeshClip(string tag) { foreach (MeshSurface meshSurface in meshSurfaces) { if (meshSurface.tag == tag) { return meshSurface.clip; } } return null; } }