using System; using UnityEngine; using UnityEngine.Assertions; namespace Unity.BossRoom.Gameplay.GameplayObjects.AnimationCallbacks { /// /// Plays one of a few sound effects, on a loop, based on a variable in an Animator. /// We use this to play footstep sounds. /// /// /// /// For this project we're using a few looped footstep sounds, choosing between them /// based on the animated speed. This method has good performance versus a more complicated /// approach, but it does have a flaw: it becomes inaccurate when the character's speed is slowed. /// e.g. if a slowness debuff makes you move at 75% speed, the footsteps will be slightly off because /// we only have sound-loops for 50% and 100%. That's not a big deal in this particular game, though. /// In the rare situations where animated speed is faster than 100% (due to speed buffs etc.), we /// currently just don't play any footsteps at all. /// public class AnimatorFootstepSounds : MonoBehaviour { [SerializeField] [Tooltip("The Animator we'll track")] private Animator m_Animator; [SerializeField] [Tooltip("A Float parameter on the Animator, with values between 0 (stationary) and 1 (full movement).")] private string m_AnimatorVariable; [SerializeField] [HideInInspector] // this is maintained via OnValidate() in the editor private int m_AnimatorVariableHash; [SerializeField] [Tooltip("The AudioSource we'll use for looped footstep sounds.")] private AudioSource m_AudioSource; [SerializeField] [Tooltip("Loopable audio of the character's footsteps moving at walking speed")] private AudioClip m_WalkFootstepAudioClip; [SerializeField] [Tooltip("Relative volume to play the clip at")] private float m_WalkFootstepVolume = 1; [SerializeField] [Tooltip("Loopable audio of the character's footsteps moving at running speed")] private AudioClip m_RunFootstepAudioClip; [SerializeField] [Tooltip("Relative volume to play the clip at")] private float m_RunFootstepVolume = 1; [SerializeField] [Tooltip("If the speed variable is this or below, we're moving too slowly for footsteps (no sounds played)")] private float m_TooSlowThreshold = 0.3f; [SerializeField] [Tooltip("If the speed variable is between TooSlowThreshold and this, we're walking")] private float m_WalkSpeedThreshold = 0.6f; [SerializeField] [Tooltip("If the speed variable is between WalkSpeedThreshold and this, we're running. (Higher than this means no sound)")] private float m_RunSpeedThreshold = 1.2f; float m_LastSpeed; void Awake() { if (!m_Animator) { m_Animator = GetComponentInParent(); } } private void Update() { if (!m_Animator) { // we can't actually run since we don't have the stuff we need. So just stop updating enabled = false; return; } var speed = m_Animator.GetFloat(m_AnimatorVariableHash); if (Mathf.Approximately(speed, m_LastSpeed)) { return; } // choose which sound effect to use based on how fast we're walking AudioClip clipToUse = null; float volume = 0; if (speed <= m_TooSlowThreshold) { // we could have a "VERY slow walk" sound... but we don't, so just play nothing } else if (speed <= m_WalkSpeedThreshold) { clipToUse = m_WalkFootstepAudioClip; volume = m_WalkFootstepVolume; } else if (speed <= m_RunSpeedThreshold) { clipToUse = m_RunFootstepAudioClip; volume = m_RunFootstepVolume; } else { // we're animating the character's legs faster than either of our clips can support. // We could play a faster clip here... but we don't have one, so just play nothing } // now actually configure and play the appropriate sound if (clipToUse == null) { m_AudioSource.Stop(); m_AudioSource.clip = null; } else if (m_AudioSource.clip != clipToUse) { m_AudioSource.clip = clipToUse; m_AudioSource.volume = volume; m_AudioSource.loop = true; m_AudioSource.Play(); } m_LastSpeed = speed; } #if UNITY_EDITOR /// /// Precomputes the hashed value for the animator-variable we care about. /// (This way we don't have to call Animator.StringToHash() at runtime.) /// Also auto-initializes variables when possible. /// private void OnValidate() { m_AnimatorVariableHash = Animator.StringToHash(m_AnimatorVariable); Assert.IsTrue(m_AnimatorVariableHash != 0); if (m_AudioSource == null) { m_AudioSource = GetComponent(); } Assert.IsNotNull(m_AudioSource); Assert.IsNotNull(m_WalkFootstepAudioClip); Assert.IsNotNull(m_RunFootstepAudioClip); } #endif } }