Guidance / Manual
System Architecture
2.1 Design Philosophy
Pepper.SFX follows three core principles:
Principle 1: Data-Driven Design
Traditional Approach:
// Hard-coded, tightly coupled
AudioSource.PlayClipAtPoint(explosionClip, transform.position);
Pepper.SFX Approach:
// Data-driven, decoupled
explosionEvent.Post(gameObject, transform.position);
Why It Matters:
- Audio designers work independently in Unity Editor
- No code changes to modify sounds
- ScriptableObjects enable version control and team collaboration
- Iteration is instant (no recompile)
Principle 2: Event-Based Triggering
What: Code triggers events, not direct audio playback.
Benefits:
- Decoupling: Game logic doesn’t know about audio implementation
- Flexibility: Same event can trigger different sounds based on game state
- Organization: Events are named, searchable assets
- Power: Events can execute multiple actions (play, stop, set RTPC, etc.)
Example:
// One event, multiple outcomes based on state
footstepEvent.Post();
// → Plays grass sound if on grass
// → Plays metal sound if on metal
// → Controlled by switch system
Principle 3: Hierarchical Organization
Buses: Tree structure for mixing States: Grouped for mutual exclusivity Containers: Composable playback behaviors
Example Hierarchy:
Master Bus (-3dB)
├── SFX Bus (0dB)
│ ├── Player (0dB)
│ ├── Enemies (-3dB)
│ └── Ambience (-6dB)
├── Music Bus (-6dB)
└── Dialogue Bus (0dB)
2.2 The Audio Pipeline
Understanding the complete flow from code to speaker:
1. Game Code
↓ myEvent.Post(gameObject, position)
2. AudioEvent (ScriptableObject)
↓ Execute Actions (Play, SetRTPC, SetState, etc.)
3. Container Selection
↓ Via Switch/State/Direct reference
4. AudioContainer (ScriptableObject)
↓ container.Play(position, parent)
5. AudioManager
↓ GetVoice() from pool
6. AudioVoiceEnhanced
↓ Configure AudioSource, apply GainStack
7. AudioBus
↓ Route through bus hierarchy
↓ Apply volume, ducking, effects
8. Unity AudioSource
↓ Play AudioClip
9. AudioMixer (optional)
↓ Effects, routing
10. Speaker
↓ Final audio output
Key Insight: No direct AudioSource manipulation. The system manages the entire pipeline automatically.
2.3 Component Hierarchy
ScriptableObject Assets (Design-Time):
- AudioEvent
- AudioContainer (5 types)
- AudioBus
- AudioState
Runtime Components:
- AudioManager (MonoBehaviour singleton)
- AudioVoiceEnhanced (pooled instances)
- AudioHandle / AudioMultiHandle (playback control)
Static Utilities:
- AudioExtensions (helper methods)
- ListenerUtil (the listener’s position: the registered listening point, or a scene AudioListener)
2.4 Memory & Lifecycle
Startup (Scene Load):
- AudioManager.Awake()
- Singleton registration (a second manager removes itself: its whole GameObject with Persist Across Scenes on, only its component with it off)
- DontDestroyOnLoad, with Persist Across Scenes on (the default; the manager must then be a root object)
- Voice pool creation: Max Real + Max Virtual + Steal Headroom AudioSources (32 + 64 + 4 by default), under “AudioVoices”, a root object of Pepper’s own
- Bus initialization
- Load events and states from the Audio Event Registry, else from Resources/Audio/Events and Resources/Audio/States
- Start coroutines (voice management, occlusion), on “AudioVoices”
The manager’s own GameObject switched off (or a parent of it): nothing changes. The voices and every coroutine
(fades, delays, ducks, the voice pass) live on “AudioVoices”, so they carry on; Unity would otherwise end the coroutines
and stop the voices for good. To silence the game, use PauseAll, pause buses (§6.7) or StopAllSounds.
A dedicated server: set AudioManager.Inert = true at boot, before any AudioManager starts. Then every post returns
one shared, already-finished handle without allocating or logging, with an AudioManager or without one, and an
AudioManager that starts builds no voices, coroutines or per-frame step. See §17.11.
Runtime (Playing):
- Event.Post() → Allocate voice from pool
- Configure voice (clip, bus, position, gain stack)
- Play AudioSource
- Monitor (voice management coroutine)
- Return to pool when finished
Cleanup:
- Voices return to pool automatically
- No runtime allocation/deallocation
- Predictable garbage collection
From the Pepper.SFX 3.1 guides (USER_GUIDE.md). Something wrong or unclear? Tell me.