# 3. Character Audio Effects Chain Part of [[Audio Mixers and Character Audio]]. Previous: [[2. Audio Mixer Snapshots and Exposed Parameters]]. Next: [[4. Footsteps and Surface Audio]]. > [!note] Verification status > `AudioLowPassFilter.cutoffFrequency` / `lowpassResonanceQ`, `AudioReverbFilter`, `AudioSource.spatialBlend`, `pitch`, `priority`, `loop` checked in the 6000.5 reference (2026-11-07). Uses `SnapshotDirector` and `MixerGroups` from Parts 1-2. **Code not compiled in Unity.** ## 3.1 Two places to put an effect | | Mixer group effect | Filter component on the GameObject (`AudioLowPassFilter`, `AudioHighPassFilter`, `AudioEchoFilter`, `AudioDistortionFilter`, `AudioReverbFilter`, `AudioChorusFilter`) | |---|---|---| | Applies to | every source routed to the group, *after* summing | that one source (or listener), before the mixer | | Cost | one instance per group | one instance per source | | Snapshot-able | **yes** | no (script it) | | Best for | category moods (underwater, pause, low health) | per-emitter state: occlusion, a robot enemy's distortion, a radio voice | Rule: if *all player sounds* should change, use the `SFX/Player` group. If *this one enemy behind a wall* should change, use a filter on its source. ## 3.2 The player audio rig Under the player prefab (works with either controller from [[Character Controllers]]): ``` Player |- Audio_Feet AudioSource -> SFX/Player/Footsteps (at feet) |- Audio_Voice AudioSource -> SFX/Player/Voice (at head) |- Audio_Foley AudioSource -> SFX/Player/Foley (at chest: cloth, gear rattle) \- Audio_Heart AudioSource -> SFX/Player/Heartbeat (loop, 2D) ``` Spatial blend guide: - **First person:** player sources 2D (`spatialBlend = 0`) - the listener is in your head. - **Third person:** ~0.5-0.8 so steps sit in the world but stay readable when the camera orbits away. - **VR (Quest):** your own footsteps are usually omitted or very subtle - real feet don't match virtual ones; spatial-audio specifics are a later tutorial in the [[Audio]] series. Priority: player voice/feet `priority` 64; world props 128+ (0 = highest, 256 = lowest). Per the 6000.5 reference, when active sources exceed the project's max **real voices**, Unity *virtualizes* the lowest-priority sources first (muted but still tracking position, so they can resume), and among equal priorities the quieter one. ## 3.3 Character audio state component One component listens to gameplay state and drives the mixer - gameplay code never touches the mixer directly (Observer + a single owner of mix state). ```csharp using UnityEngine; using UnityEngine.Audio; /// Drives player-related mix state: low-health blend + heartbeat, underwater, power-up pitch. public class PlayerAudioState : MonoBehaviour { [Header("Refs")] [SerializeField] SnapshotDirector snapshots; [SerializeField] AudioMixer mixer; [SerializeField] AudioSource heartbeat; // looping clip on SFX/Player/Heartbeat [Header("Heartbeat")] [SerializeField] float heartbeatMinPitch = 0.9f; [SerializeField] float heartbeatMaxPitch = 1.5f; [Header("Power-up voice")] [Tooltip("Exposed Pitch parameter on SFX/Player/Voice (e.g. a Pitch Shifter's Pitch).")] [SerializeField] string voicePitchParam = "PlayerVoicePitch"; float _health01 = 1f; bool _underwater; // ----- inputs from gameplay (call these from your health/stat system) ----- public void OnHealthChanged(float current, float max) { _health01 = max > 0f ? Mathf.Clamp01(current / max) : 0f; if (!_underwater) snapshots.BlendHealth(_health01); UpdateHeartbeat(); } public void SetUnderwater(bool value) { if (_underwater == value) return; _underwater = value; if (_underwater) snapshots.ToUnderwater(); else snapshots.BlendHealth(_health01); // return to health-appropriate mix } public void SetPoweredUp(bool on, float pitch = 1.25f) { if (on) mixer.SetFloat(voicePitchParam, pitch); else mixer.ClearFloat(voicePitchParam); // hand control back to snapshots } void UpdateHeartbeat() { float danger = Mathf.InverseLerp(0.4f, 0.1f, _health01); if (danger > 0f && !heartbeat.isPlaying) heartbeat.Play(); if (danger <= 0f && heartbeat.isPlaying) heartbeat.Stop(); heartbeat.pitch = Mathf.Lerp(heartbeatMinPitch, heartbeatMaxPitch, danger); // Level comes from the LowHealth snapshot (Heartbeat group fader), so no volume code here. } } ``` > [!warning] Code not compiled in Unity > Not compiled. Hook `OnHealthChanged` to your health stat - e.g. the derived stats in [[1.Stat Definitions and Base vs Derived Stats]] or a timed buff from [[2.Modifiers and Timed Buffs]] for `SetPoweredUp`. The pitch shifter's exposed parameter range/units depend on the effect - check the Pitch Shifter page in the effects reference. ## 3.4 Underwater volume ```csharp using UnityEngine; /// Put on a trigger volume covering the water. Tracks the camera/head, not the body, /// so the mix changes when the *ears* go under. [RequireComponent(typeof(Collider))] public class UnderwaterAudioVolume : MonoBehaviour { [SerializeField] PlayerAudioState playerAudio; [SerializeField] Transform listenerTransform; // usually the camera with the AudioListener Collider _volume; void Awake() { _volume = GetComponent<Collider>(); _volume.isTrigger = true; } void Update() { // Polling the listener point is more robust than trigger callbacks on a camera with no Rigidbody. bool inside = _volume.bounds.Contains(listenerTransform.position) && (_volume.ClosestPoint(listenerTransform.position) - listenerTransform.position).sqrMagnitude < 0.0001f; playerAudio.SetUnderwater(inside); } } ``` > [!warning] Code not compiled in Unity > Not compiled. `Collider.ClosestPoint` returns the point itself when the point is inside a convex collider - that's the inside test; for a concave MeshCollider use primitive colliders instead. With several water volumes, aggregate them (count) before calling `SetUnderwater`. ## 3.5 Per-source occlusion on an enemy voice A character effect that **must** be per-source: muffle an enemy (or NPC) when a wall is between them and the listener. Directly supports the sensing game in [[Organic Enemy Sensing]] - the player hears the guard muffled through a wall *before* seeing it. ```csharp using UnityEngine; [RequireComponent(typeof(AudioSource), typeof(AudioLowPassFilter))] public class SimpleOcclusion : MonoBehaviour { [SerializeField] Transform listener; [SerializeField] LayerMask occluders; // walls, not props [SerializeField] float openCutoff = 22000f; [SerializeField] float occludedCutoff = 1200f; [SerializeField] float occludedVolume = 0.6f; [SerializeField] float smoothing = 8f; [SerializeField] float checkInterval = 0.1f; AudioSource _src; AudioLowPassFilter _lp; float _baseVolume, _targetCutoff, _targetVol, _nextCheck; void Awake() { _src = GetComponent<AudioSource>(); _lp = GetComponent<AudioLowPassFilter>(); _baseVolume = _src.volume; _targetCutoff = openCutoff; _targetVol = _baseVolume; if (listener == null && Camera.main) listener = Camera.main.transform; } void Update() { if (Time.time >= _nextCheck && _src.isPlaying) { _nextCheck = Time.time + checkInterval; bool blocked = Physics.Linecast(transform.position, listener.position, occluders, QueryTriggerInteraction.Ignore); _targetCutoff = blocked ? occludedCutoff : openCutoff; _targetVol = blocked ? _baseVolume * occludedVolume : _baseVolume; } float k = 1f - Mathf.Exp(-smoothing * Time.deltaTime); _lp.cutoffFrequency = Mathf.Lerp(_lp.cutoffFrequency, _targetCutoff, k); _src.volume = Mathf.Lerp(_src.volume, _targetVol, k); } } ``` > [!warning] Code not compiled in Unity > Not compiled. Linear-interpolating Hz sounds uneven; for a nicer sweep lerp in log space (`Mathf.Exp(Mathf.Lerp(Mathf.Log(a), Mathf.Log(b), k))`). For many emitters, move the linecasts to a manager that staggers checks (budget idea from [[5.Quest 2 Performance and Rendering]]). ## 3.6 Teaching exercise (Level 16 + Level 8) Remove the health bar from the HUD. Can players still tell they're about to die from audio alone (heartbeat + muffled mix)? Then put the bar back and ask which version felt more tense. Clean screen vs information - Rogers' Level 8 trade-off, solved with sound. ## Docs - Audio effects (mixer): https://docs.unity.com/en-us/engine/6000.5/manual/audio/reference/effects - Audio filters (components): https://docs.unity.com/en-us/engine/6000.5/manual/audio/reference/filters - `AudioLowPassFilter`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/AudioLowPassFilter.html - `AudioReverbFilter`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/AudioReverbFilter.html - `AudioSource.spatialBlend`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/AudioSource-spatialBlend.html - `AudioSource.priority`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/AudioSource-priority.html - `AudioMixer.ClearFloat`: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Audio.AudioMixer.ClearFloat.html