Audio#
AudioManager plays and stops sounds, while VolumeManager stores and saves the six volume settings. Both managers are singletons that initialize on first access.
Playing#
AudioManager.Play takes an AudioResource and AudioConfig, then returns the sound's runtime Guid:
AudioConfig config = new AudioConfig(
timeScale: TimeScale.GameTime,
transition: 0f,
channel: AudioChannel.SoundEffect,
loop: false,
priority: 128,
volume: 1f,
pitch: 1f,
stereoPan: 0f,
spatialBlend: 1f,
reverbZoneMix: 1f,
volumeRolloff: AudioRolloffMode.Logarithmic,
minDistance: 1f,
maxDistance: 500f,
dopplerLevel: 0f,
spread: 0f,
hasTarget: true,
target: this.gameObject,
locationTransform: this.transform
);
Guid audioId = AudioManager.Play(this.m_Resource, config);AudioConfig is a read-only struct configured through its constructor. Use named arguments to keep each value associated with its setting. Only the last three arguments are optional: target, locationTransform, and locationPosition.
These arguments control timing, channel assignment, and the sound's relationship to a game object:
| Argument | Is |
|---|---|
timeScale |
An ITimeScale. TimeScale.GameTime and TimeScale.RealTime are ready-made; a component implementing ITimeScale gives the sound a local clock. |
transition |
Fade-in seconds. 0 starts at full volume. |
channel |
Which channel the sound belongs to, and therefore which volume and mixer group apply. |
hasTarget / target |
Marks the sound as belonging to a game object, so Stop(GameObject, float) and IsPlaying(GameObject) can find it. |
locationTransform |
Followed every frame. Takes precedence over locationPosition. |
locationPosition |
A fixed world point, used when locationTransform is null. |
The rest map one-to-one onto AudioSource and are described in Playing a sound.
Destroying the target doesn't stop its sound
target and locationTransform hold references without parenting the sound to the object. When you destroy the object, the sound keeps playing. A destroyed locationTransform falls back to locationPosition, which for a targeted sound is Vector3.zero.
Stop the sound before you destroy its target.
Stopping#
Choose a stop method based on whether you need one sound, every instance of a resource, or a group of sounds:
| Method | Stops | Returns |
|---|---|---|
Stop(Guid) |
One sound, immediately. | void |
Stop(Guid, float) |
One sound, fading over the given seconds. | Awaitable |
Stop(AudioResource, float) |
Every sound playing that resource. | Awaitable |
Stop(GameObject, float) |
Every sound targeting that object. | Awaitable |
StopChannel(AudioChannel, float) |
Every sound on that channel. | Awaitable |
Await a channel fade before starting the next operation when the sequence depends on silence:
await AudioManager.StopChannel(AudioChannel.Music, 2f);
await SceneManager.LoadSceneAsync(nextScene);StopChannel finishes the music fade before LoadSceneAsync starts. If you don't need to wait for the fade, you can call the method without awaiting its result.
Queries#
Use IsPlaying to check a specific instance, resource, or target:
| Method | Returns |
|---|---|
IsPlaying(Guid) |
Whether that specific sound is still active. |
IsPlaying(AudioResource) |
Whether any sound is playing that resource. |
IsPlaying(GameObject) |
Whether any sound targets that object. |
Changing a sound while it plays#
GetActive returns the live IAudio for an ID, or null after the sound ends:
IAudio audio = AudioManager.GetActive(audioId);
if (audio != null) audio.Volume = 0.4f;Setting Volume changes that active instance without affecting other sounds that use the same resource. Other members expose its configuration and writable playback settings:
| Member | Access |
|---|---|
Resource, Channel, HasTarget, Target |
Read-only — fixed when the sound started. |
Volume, Pitch |
Read/write. Re-applied every frame against the channel volume and time scale. |
Priority, StereoPan, SpatialBlend, ReverbZoneMix |
Read/write. Applied directly to the AudioSource. |
VolumeRolloff, MinDistance, MaxDistance, DopplerLevel, Spread |
Read/write. Applied directly to the AudioSource. |
The ID distinguishes one sound from other instances playing the same resource. Play Instructions discard the returned Guid, so editor-authored Events cannot later address that individual instance.
Hold the ID only as long as the sound
An ID whose sound has finished is not reused, but it no longer resolves. Treat a null from GetActive as it ended, not as an error, and don't keep IDs in long-lived state expecting them to stay valid.
Volumes#
VolumeManager exposes the six volumes as Volume objects and provides channel-based accessors:
VolumeManager.Music.Value = 0.5f;
VolumeManager.Set(AudioChannel.Ambient, 0.2f);
float music = VolumeManager.Music.Value;
float ambient = VolumeManager.Get(AudioChannel.Ambient);Every value is clamped to 0–1 on assignment. Assigning the current value doesn't fire an event or notify subscribers.
Final sound volume equals its own volume multiplied by its channel volume and Master.
AudioVolume includes Master#
Choose the enum based on whether the field needs to include the master volume:
| Enum | Values | Used by |
|---|---|---|
AudioChannel |
The five channels. | Everything that plays, stops or queries a sound, and every VolumeManager method. |
AudioVolume |
The five channels plus Master. |
Serialized fields that pick a volume to change. |
AudioVolume uses the same values as AudioChannel for channel members. Handle Master separately before casting:
float value = this.m_Volume == AudioVolume.Master
? VolumeManager.Master.Value
: VolumeManager.Get((AudioChannel) this.m_Volume);The guard handles the only extra AudioVolume member. Master is a property rather than a channel, so channel-based Get, Set, Subscribe, and Unsubscribe reject it.
Use VolumeManager.Master directly, including VolumeManager.Master.Subscribe(…) for change notifications.
Use AudioVolume for a serialized field a designer picks a volume from, and AudioChannel everywhere else. Casting a Master into an AudioChannel and handing it to VolumeManager throws ArgumentOutOfRangeException.
Reacting to changes#
Use Subscribe when your callback needs a channel's Args context, and keep its returned ID for cleanup:
Guid subscriptionId = VolumeManager.Subscribe(AudioChannel.Music, this.OnMusicChanged, args);
VolumeManager.Unsubscribe(AudioChannel.Music, subscriptionId);The subscription owns its context and disposes it during Unsubscribe. For a callback that recomputes all volume state, use the notification without arguments:
VolumeManager.EventChange += this.RefreshVolume;EventChange fires when any channel or Master changes.
Each Volume also has its own EventChange taking the new value, if you'd rather watch one directly.
Unsubscribe in OnDisable
Both notification mechanisms are static and outlive your components. A subscription that isn't released keeps its callback alive after the object is gone. A channel subscription also retains its Args.
Volumes save themselves#
VolumeManager implements ISaveLoad as shared and persistent data under unity-audio-volume-info. The manager registers itself and follows this lifecycle:
SaveLoadManager.SaveShared()writes the six values.- Loading restores the same values across every profile.
Unloadpreserves them when starting a new game.
The manager subscribes when it initializes. Subscribing applies the values already held by the snapshot, so the stored volumes are available on first audio access.
Apply volume to hand-placed Audio Sources#
The managers govern only sources created by AudioManager. A hand-placed AudioSource ignores channel volume because Visual Script applies that volume on managed sources rather than mixer groups.
In a component with m_AudioSource and m_Channel fields, these methods apply channel and Master volume whenever either changes:
private void OnEnable()
{
VolumeManager.EventChange += this.RefreshVolume;
this.RefreshVolume();
}
private void OnDisable()
{
VolumeManager.EventChange -= this.RefreshVolume;
}
private void RefreshVolume()
{
this.m_AudioSource.volume = VolumeManager.Get(this.m_Channel) * VolumeManager.Master.Value;
}The free Audio Source Volume package provides this behavior with a channel dropdown. Its component assigns volume directly, so write a custom version when another system must also contribute to that field.
Upgrading from the Audio Unity package#
Audio previously lived in a separate package under VisualScript.Runtime.AudioUnity. It now uses the VisualScript.Runtime namespace and VisualScript.Runtime.Core assembly.
Replace using VisualScript.Runtime.AudioUnity; with using VisualScript.Runtime;. Remove the old audio reference from assembly definitions because VisualScript.Runtime.Core now contains these types.
The AudioSourceVolume component remains in the extension package with its original namespace.