using BaseGames.Core; using UnityEngine; namespace BaseGames.Audio { /// /// 材质感知脚步声播放器。挂在玩家 GameObject 上,由 PlayerFeedback.PlayFootstep() 驱动。 /// 每次触发时向下 OverlapCircle 探测脚下碰撞体,读取 FootstepMaterialMarker 决定材质, /// 再从 FootstepAudioConfigSO 随机选取 Clip 并叠加 Pitch 扰动后播放。 /// 未探测到 Marker 时回落到 FootstepMaterial.Stone。 /// public class FootstepSoundPlayer : MonoBehaviour { [Header("音效配置")] [SerializeField] private FootstepAudioConfigSO _config; [Header("地面探测")] [Tooltip("通常复用 PlayerMovement 下的 GroundCheck 子对象")] [SerializeField] private Transform _groundProbe; [SerializeField] private LayerMask _groundLayer; [SerializeField] private float _probeRadius = 0.15f; private AudioSource _audioSource; private readonly Collider2D[] _probeBuffer = new Collider2D[4]; private void Awake() { // 优先使用同对象已有的 AudioSource,否则自动添加 _audioSource = GetComponent(); if (_audioSource == null) _audioSource = gameObject.AddComponent(); _audioSource.spatialBlend = 0f; // 始终 2D _audioSource.playOnAwake = false; } /// /// 由 PlayerFeedback.PlayFootstep() 调用。 /// public void Play() { if (_config == null) return; var material = DetectGroundMaterial(); var entry = _config.GetEntry(material); if (entry == null) return; var e = entry.Value; if (e.clips == null || e.clips.Length == 0) return; var clip = e.clips[Random.Range(0, e.clips.Length)]; if (clip == null) return; // pitchVariance 为 [0.8, 1.2],以 1.0 为中心扩展偏移区间 float half = e.pitchVariance - 1f; _audioSource.pitch = Random.Range(1f - half, 1f + half); _audioSource.PlayOneShot(clip, e.volume); } // ── 地面材质探测 ────────────────────────────────────────────────────── private FootstepMaterial DetectGroundMaterial() { Vector2 origin = _groundProbe != null ? (Vector2)_groundProbe.position : (Vector2)transform.position + Vector2.down * 0.5f; int count = Physics2D.OverlapCircleNonAlloc(origin, _probeRadius, _probeBuffer, _groundLayer); for (int i = 0; i < count; i++) { var marker = _probeBuffer[i].GetComponent(); if (marker != null) return marker.material; } return FootstepMaterial.Stone; // 默认回落 } #if UNITY_EDITOR private void OnDrawGizmosSelected() { Gizmos.color = new Color(0.6f, 1f, 0.6f, 0.5f); Vector3 origin = _groundProbe != null ? _groundProbe.position : transform.position + Vector3.down * 0.5f; Gizmos.DrawWireSphere(origin, _probeRadius); } #endif } }