Character SFX
Breathing loops, effort one-shots, sprint transitions, and attribute-driven body audio
Character SFX
Event-driven body audio — breathing loops that ramp with exertion, effort grunts on jump and land, sprint-recovery wheezes, low-health heartbeat. Zero-tick design where it counts: subscribed to footstep events, attribute change delegates, or AnimInstance polling — your choice per trigger.
Authoring custom Blueprint Events or Triggers? After this overview, jump to BP Authoring — Events & Triggers for the heartbeat case study and the seven gotchas that ship with the territory.
The Three-Layer Architecture
CharacterSFX intentionally separates what plays, when it plays, and how it plays into three classes:
┌──────────────────────────────────────────────────────────────┐
│ UAgenticCharacterSFXConfig (DA_CharacterSFX_*) │
│ │
│ EventConfigs[] ← WHAT plays per tag │
│ ├─ CharacterSFX.Breathing.Walk → Loop (Breathing) │
│ ├─ CharacterSFX.Breathing.Run → Loop (Breathing) │
│ ├─ CharacterSFX.Effort.Jump → OneShot (Grunt) │
│ └─ ... │
│ │
│ EventTriggers[] ← WHEN to raise each tag │
│ ├─ Foley Event (Breathing) → footstep state machine │
│ ├─ Locomotion State → AnimInstance tag poll │
│ └─ Attribute Threshold → GAS attribute delegate │
└──────────────────────────────────────────────────────────────┘
↓ executes via
┌──────────────────────────────────────────────────────────────┐
│ UAgenticFoleyComponent::TriggerCharacterSFX(Tag, Context) │
│ │
│ 1. Look up EventConfigs for Tag │
│ 2. For each event → Event->ExecuteEvent(Component,Tag,Ctx) │
│ 3. Events manage AudioComponent pool + loop state │
└──────────────────────────────────────────────────────────────┘The polymorphism (Abstract, Blueprintable, EditInlineNew, Instanced on both base classes) is what makes the system extensible. Authors write new Event types and Trigger types in C++ or Blueprint without touching the config DataAsset or the foley component itself. The DataAsset holds arrays of base-class instances; UE's Instanced reflection serializes them by their runtime subclass; the Details panel's class-picker dropdown lets you pick a subclass when adding an array entry.
A Configured DataAsset
A DA_CharacterSFX_* with event configs and triggers populated:

Top-level fields on the DataAsset:
| Field | Type | Notes |
|---|---|---|
EventConfigs | TArray<FAgenticCharacterSFXConfigEntry> | Tag → events map. Multiple events can layer under the same tag (Grunt + BreathHold under Effort.Jump). |
EventTriggers | TArray<UAgenticCharacterSFXTrigger*> | Auto-fire triggers. Initialize runs at component BeginPlay. |
AudioPoolSize | int32 (2–8) | Pre-allocated UAudioComponent slots. Default 4 covers concurrent breathing loop + effort one-shot + spare. Bump to 6–8 if you stack more layers. |
GlobalVolumeScale | float (0–2) | Master scalar across every CharacterSFX event from this config. Separate from the surface-foley layer. |
Shipped Event Types — What Plays
Two concrete UAgenticCharacterSFXEvent subclasses ship with the plugin.
Loop (Breathing)
Looping sounds with crossfade, fade-in, fade-out, and timeout-driven auto-stop. Use for:
- Breathing loops that ramp with locomotion (walk → run → sprint)
- Sustained body audio (heavy armor clank loop while sprinting)
- Status SFX (on-fire hiss, bleeding gurgle, low-health heartbeat)
| Field | Range | Notes |
|---|---|---|
Sound | USoundBase* | Looping USoundCue or UMetaSoundSource. |
CrossfadeDuration | 0.05–5 s | Crossfade when the active tag changes (walk → run). 0.5 is a good breathing default. |
FadeInDuration | 0.05–5 s | First-start ramp from silence. 0.15 = barely audible. |
FadeOutDuration | 0.05–5 s | Stop ramp. 0.15 for snappy, 0.5 for soft. |
TimeoutDuration | 0–30 s | Seconds without PulseCharacterSFX calls before auto-stop. 3.0 for responsive fade, 0.0 = persistent. |
BaseVolume | 0–2 | 0.1–0.3 so breathing sits well under footsteps. |
bScaleVolumeByMagnitude + Min/Max | — | Volume lerps with Context.Magnitude. Use for breathing that gets louder as the character sprints harder. |
bScalePitchByExertion + Min/Max | — | Pitch lerps with Context.ExertionLevel. Higher exertion = higher pitch = gasping quality. |
One-Shot (Grunt/Effort)
Discrete sounds with random pool selection, cooldown, and pitch variation. Use for:
- Effort grunts (jump pushoff, heavy landing)
- Pain cries on damage
- Battle cries
- Any one-shot character vocalization
| Field | Range | Notes |
|---|---|---|
Sounds | TArray<USoundBase*> | Pool — random pick per fire, avoiding immediate repeats. 4+ entries gives natural variety. |
bPlay2D | bool | Plays 2D (no spatialization). Useful for first-person breathing right in the player's ear. |
Cooldown | 0–10 s | Minimum seconds between fires — prevents spam. 0.3 works for jump. |
BaseVolume / scale-by-magnitude | — | Same lerp pattern as Loop. |
BasePitch / PitchVariation | 0.5–2 / 0–0.5 | PitchVariation = 0.05 = BasePitch ± 5% per fire. |
Shipped Trigger Types — When to Raise Tags
Three concrete UAgenticCharacterSFXTrigger subclasses ship. Each one subscribes to a different condition source — pick the right one per use case.
1. Foley Event (Breathing)
UAgenticCharacterSFXTrigger_FoleyEvent. Subscribes to the foley component's OnFoleyEventResolved delegate — fires on every footstep. Classifies steps into Walk / Run / Sprint by the Foley.Event.* tag, accumulates an ExertionScore, and raises the matching breathing tag automatically. On stop it picks StopShort (low exertion) or StopLong (high exertion wheeze). On Jump/Land it fires the matching Effort tag.
Use it when you want breathing that follows footstep gait. No AnimBP dependency, no polling — drop it into EventTriggers and the breathing state machine runs itself. This is the trigger every character should have.
2. Locomotion State
UAgenticCharacterSFXTrigger_LocomotionState. Polls a named FGameplayTag property on the pawn's UAnimInstance every frame via reflection. Configurable match-tag set, optional speed-based magnitude.
Use it when you want breathing tied to a specific AnimBP state rather than footstep classification — e.g., a dedicated "stamina drain" state that drives breathing whether or not footsteps are firing. Works with any AnimInstance that exposes a FGameplayTag property — zero compile-time dependency on AgenticLocomotion.
3. Attribute Threshold
UAgenticCharacterSFXTrigger_Attribute. Binds to GAS's OnGameplayAttributeValueChange — fires only when the attribute changes, no tick. Configurable comparison (<, <=, >, >=), absolute or percentage threshold, hysteresis to prevent flicker, optional magnitude derived from the attribute value.
Use it when SFX should react to gameplay state. Canonical use cases:
| Situation | Attribute | Comparison | Threshold | Hysteresis | EventTagToRaise |
|---|---|---|---|---|---|
| Heartbeat (low HP panic) | Health | < | 100 (abs) or 30% | 10 / 5 | CharacterSFX.Breathing.Heartbeat |
| Injured breathing | Health | < | 25% | 5 | CharacterSFX.Breathing.Injured |
| Exhausted wheezing | Stamina | < | 10% | 3 | CharacterSFX.Breathing.Exhausted |
| Battle cry on rage spike | Rage | >= | 100 (abs) | 5 | CharacterSFX.BattleCry |
| Mana surge whisper | Mana | >= | 90% | 5 | CharacterSFX.ManaSurge |
| Bleed-out gurgle | Health | < | 5 (abs) | 1 | CharacterSFX.Breathing.Dying |
The pattern stacks — multiple Attribute triggers in one DataAsset, each raising a different tag, with EventConfigs playing the right Loop or OneShot per tag. No C++ touch.
Hysteresis is mandatory. Attribute values bounce near boundaries — without
Hysteresis > 0you get rapid on/off spam.
Requires GAS on the pawn. The trigger walks the owner for an
UAbilitySystemComponentatInitialize. If none is found, it logs a warning and sits dormant.
Configuration
Open DA_CharacterSFX_Default (or duplicate it for a per-character variant) and:
- Swap sounds — open each
EventConfigsentry, drill intoEvents[0], replace theSound/Soundsfield. - Tune volumes + timeouts —
BaseVolume = 0.1–0.3so breathing sits below the surface layer.TimeoutDuration = 3.0for responsive fade-out. - Configure triggers —
Foley Event (Breathing)for footstep-driven breathing,Attribute Thresholdfor health/stamina-driven SFX,Locomotion Statefor AnimBP-driven breathing. - Assign to character — on the foley component, set
CharacterSFXConfig.
PIE test: walk, run, sprint, jump, stop. Take damage past your heartbeat threshold. Listen.
Verbose logging:
Log LogAgenticFoley VerboseEach trigger fire and stop logs a line — the pipeline tells you exactly which tag fired, which event matched, and the resolved volume / pitch.
Sound Assets
Two complete sound sets ship with the plugin:
| DataAsset | Source | Notes |
|---|---|---|
DA_CharacterSFX_Default | Soldier Sound System pack | 9 EventConfigs (Breathing × 6 + Effort × 2 + Heartbeat) |
DA_CharacterSFX_Custom | ElevenLabs SFX (our-origin, redistributable) | 8 EventConfigs |
Switch by setting CharacterSFXConfig on the foley component. Both ship with a Foley Event (Breathing) trigger preconfigured; Default adds an Attribute Threshold trigger gating heartbeat at Health < 100.
MetaSound presets shipped
| Preset | Pattern | Waves |
|---|---|---|
MSS_Breathing_SprintStart | One-shot, RandomGet | 4 |
MSS_Breathing_StopLong | One-shot, RandomGet | 5 |
MSS_Breathing_StopShort | One-shot, RandomGet | 5 |
MSS_Breathing_Jump | One-shot, RandomGet | 6 |
MSS_Breathing_Land | One-shot, RandomGet | 6 |
MSS_Breathing_WalkLoop | Loop | 1 |
MSS_Breathing_RunLoop | Loop | 1 |
Next Steps
- BP Authoring — Events & Triggers — write your own Loop / OneShot / Trigger subclasses in Blueprint. Heartbeat case study with full lifecycle (idempotent spawn, fade-out, destroy, no stacking) plus the seven gotchas that bit us shipping it.
- Multiplayer — CharacterSFX replicates through the same GAS GameplayCue pipeline as surface foley. No extra wiring.
- Data Assets reference — full field list for
UAgenticFoleyDataAssetand the broader DataAsset family.