docs
Systems
Presentation
AudioSystem
Architecture and Design

Design patterns in use

  • Singleton: AudioManager exposes a static Instance. In Awake, the first instance is kept with DontDestroyOnLoad, and duplicates are destroyed.
  • Object pool: AudioSourcePool creates a fixed set of AudioSource GameObjects at construction. It hands out inactive sources and reactivates them when needed.
  • Template method: AudioManager defines the fixed steps, and hands project-specific parts to abstract hooks (such as HandleButtonClicked and GetVolumePreset), the injected IAudioTweener, and the injected ILayeredMusicPlayer.
  • Strategy through injection: AudioManager accepts an IAudioTweener through SetAudioTweener and an ILayeredMusicPlayer through SetLayeredMusicPlayer. Backends and fading implementations can be swapped without changing AudioManager.
  • Static factory: AudioCommandFactory builds Audio-domain commands with the correct verb, scope, subject, and parameters, then sends them through BusUtility.
  • Two-tier lookup: AudioClipLookup maps clip keys to holders. Each holder maps clip keys to AudioClipData. Localized holders add a third level, language.
  • Publish and subscribe: AudioFlowController registers with the event bus for the UI domain and forwards received events to AudioManager.ProcessEvent.
  • Service locator: FlowController and AudioFlowController get the bus registry through ServiceLocator instead of having it injected.

Connections to dependency systems

  • Contracts: Domain routes commands and events to AudioFlowController. CoreParamKeys.ObjectValue carries the float volume value. Command.Create is used by the factory. These types are defined outside this folder.
  • Params: Each command carries typed values in a Params container, read and written with ParamKey<T> keys defined in AudioParamKeys. Get throws when a key is missing, so the system uses Has and TryGet for optional values such as the clip key or callback.
  • Flow: AudioFlowController extends FlowController. FlowController.Init registers for the Audio domain on the command bus. AudioFlowController also calls base.Init() and then registers for UI events. HandleCommand is declared abstract in FlowController, so a project subclass must implement it and forward commands to AudioManager.HandleCommand.
  • Bus: AudioCommandFactory sends commands through BusUtility.Send, which uses the CommandBus. The event bus delivers UI events to AudioFlowController.OnEvent.
  • Initialization: InitializationManager runs FlowController subclasses through IInitializable. AudioFlowController is classified as a sync initializable event receiver, because it implements IEventReceiver. Its Init runs before ordinary sync systems.
  • Application: ServiceLocator.GetBusRegistry supplies the buses to FlowController and AudioFlowController.

Key concepts

  • Audio command: A command in the Audio domain. Its verb is an AudioCommandVerb. Its scope is an AudioScope, used for volume and progress. Its subject is an AudioSubject, such as Music, SFX, Preset, or Master.
  • Clip key: A string that identifies a clip. Clip keys are hashed with GetHashCode for AudioState lookups.
  • Audio state: An AudioState records the playback status, linked AudioSource, clip key, and reserved flag for each tracked music clip. AudioManager owns the list of states.
  • Reserved state: A paused music state is marked reserved, so it is kept and can be resumed. A non-reserved state is removed when released.
  • Volume mix: A VolumeMix struct holds SFX, Music, and Master decibel values. A VolumeSource selects whether values come from saved settings, runtime values, or a preset. A VolumeChannel selects which mixer groups to apply.
  • Volume context: A VolumeContext is stored by SetAudioContext.
  • Layered music: Music played through an ILayeredMusicPlayer backend. Progress is set as a value, and the backend interprets it. ILayeredMusicDataStore saves and loads progress.
  • Tweener: An IAudioTweener fades AudioSource volume over time and can cancel a fade.

Event and data flow

Commands:

  1. A caller builds a command with AudioCommandFactory. For example, PlayMusic sets the clip key and sets the subject to Music.
  2. BusUtility.Send places the command on the CommandBus, routed by Domain.Audio.
  3. The project's AudioFlowController subclass receives the command in HandleCommand, and calls AudioManager.HandleCommand.
  4. HandleCommand passes the command to HandleStandardAudioCommand. That method reads the verb, scope, and subject, then calls the matching AudioManager method.
  5. For sound effects, AudioManager gets a pooled source and calls AudioPlaybackUtility.Play. That method applies the clip data, fades in through the tweener, and, for non-looping clips, waits until playback ends before deactivating the source and invoking the optional callback.
  6. For music, RequestMusic checks the existing AudioState. It resumes a paused clip, does nothing if the clip is already playing, or starts new playback. When layering is off, it releases other tracked states first.

UI events:

  1. The event bus delivers a Domain.UI event to AudioFlowController.OnEvent.
  2. OnEvent calls AudioManager.ProcessEvent.
  3. ProcessEvent routes UI-domain events to HandleUIEvent. For button clicks on the button subject, it calls the abstract HandleButtonClicked, which the project implements.

Clip resolution:

  1. AudioManager.Awake builds an AudioClipLookup from its holder lists, then calls BuildHolderHashmap.
  2. The lookup builds each holder's internal map, then maps clip keys to holders. The first occurrence of a duplicate key wins, and later duplicates are logged.
  3. GetHashedClip finds the holder for a key, then asks it for the clip. Localized holders use the language key, and fall back to any available language.