92 lines
3.4 KiB
C#
92 lines
3.4 KiB
C#
using BaseGames.Core;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Audio
|
||
{
|
||
/// <summary>
|
||
/// 材质感知脚步声播放器。挂在玩家 GameObject 上,由 PlayerFeedback.PlayFootstep() 驱动。
|
||
/// 每次触发时向下 OverlapCircle 探测脚下碰撞体,读取 FootstepMaterialMarker 决定材质,
|
||
/// 再从 FootstepAudioConfigSO 随机选取 Clip 并叠加 Pitch 扰动后播放。
|
||
/// 未探测到 Marker 时回落到 FootstepMaterial.Stone。
|
||
/// </summary>
|
||
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<AudioSource>();
|
||
if (_audioSource == null)
|
||
_audioSource = gameObject.AddComponent<AudioSource>();
|
||
|
||
_audioSource.spatialBlend = 0f; // 始终 2D
|
||
_audioSource.playOnAwake = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 由 PlayerFeedback.PlayFootstep() 调用。
|
||
/// </summary>
|
||
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<FootstepMaterialMarker>();
|
||
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
|
||
}
|
||
}
|