Without middleware, every sound in a Unity game is an AudioSource and a clip wired up in code, and picking variations, stopping spam, fading and routing are all yours to write. Pepper.SFX moves that work into assets. You build an Audio Event and the containers under it, and your code plays it with one call that returns a handle.
How it works
- Audio Event is what your code posts. It holds a list of actions that run in order, each with an optional Delay: Play, Stop, Pause, Resume, SetSwitch, SetRTPC, SetState, TriggerDucking and CrossFade. One post can start a sound, change a state and duck the music.
- Voice Settings keep an event in check: Priority, Max Instances with a Steal Behavior (Oldest, Quietest, Furthest, Lowest Priority), Cooldown, and the same limits per Audio Emitter. Tick Persistent for sounds that must never be stolen, such as an engine. Time picks game or real time, so pause-menu sounds work at time scale 0.
Postreturns anAudioHandle. Stop it with a fade, pause it, change its volume or pitch, or wait forOnFinished.- Containers decide which clips play, and they nest inside each other:
- Random Container: one clip per post, with weights and Avoid Repeat Last, remembered per Audio Emitter. Footsteps, gunshots, clicks.
- Sequence Container: clips in order (Forward, Reverse, Ping Pong or Random), with Auto Advance, Loop Sequence and a Delay After per entry.
- Switch Container: plays the child that matches a switch value, such as the surface under a foot.
- Blend Container: layers that crossfade along a parameter, such as engine RPM or combat intensity.
- Routing Container: plays all its clips at once, for layered sounds like an explosion’s blast and debris.
- Every container also has volume and pitch randomisation, 3D settings, a Mixer Group and Parameter Curves.
Example
using UnityEngine;
using AudioSystem;
public class Generator : MonoBehaviour
{
[SerializeField] private AudioEvent switchOn; // Random Container: three clunks
[SerializeField] private AudioEvent hum; // Routing Container with Loop ticked
private AudioHandle humHandle;
public void TurnOn()
{
switchOn.Post(gameObject, transform.position); // fire and forget
if (humHandle == null)
humHandle = hum.Post(gameObject, transform.position); // keep the loop's handle
}
public void SetLoad(float load01)
{
humHandle?.SetPitchOffset(load01 * 2f); // up to two semitones higher under load
}
public void TurnOff()
{
humHandle?.Stop(0.5f); // fade out over half a second
humHandle = null;
}
}
The clunk is posted and forgotten. The hum loops, so the script keeps its handle to bend its pitch and fade it out. Posting by name (AudioManager.Instance.PostEvent("Generator_Hum")) needs the event in an Audio Event Registry or Resources/Audio/Events.
Setting it up
- Add the
AudioManagercomponent to a GameObject in your first scene. - Create a container with Assets > Create > Audio System > Random Container (or Sequence, Switch, Blend or Routing Container) and add your clips.
- Create the event with Assets > Create > Audio System > Audio Event. Add a Play action and set its Container and Target Bus.
- Set Priority, Max Instances and Cooldown under Voice Settings.
- Drag the event into an
AudioEventfield on your script and callPost.
Window > Audio System > Sound Builder (Simple) makes a bus, container and event in one step, and Window > Audio System > Audition plays a container outside Play mode.
Good to know
- A Stop action only stops sounds that its own event started. To stop music started by another event, keep the handle from
Postand callStopon it. - Calling
Stop()on a container stops every sound playing from that container asset. To stop one of them, stop its handle. - Each Sequence Container asset keeps a single position, so two objects playing the same one share it. Duplicate the asset if you need two independent sequences.
- Posting a Blend Container event again plays a second copy alongside the first. Set the event’s Max Instances to 1 if you want it to restart instead.
- A post blocked by Cooldown returns a handle that has already finished, so
OnFinishedstill fires.