How the Undertale Changer Template Audio System Manages Sound Effects and Music
The Undertale Changer Template implements a dual-layer audio architecture that separates background music handling through a persistent singleton AudioSource from short sound-effect playback via an object-pooled component system, all routed through Unity's AudioMixer for dynamic group control.
The arch-aik/undertale-changer-template repository provides a purpose-built audio subsystem designed for RPG-style games. This system distinguishes between long-running background tracks and transient sound effects while maintaining clean separation of concerns through ScriptableObject configuration and singleton pattern management.
Core Audio Architecture Components
The architecture relies on five interconnected components that separate configuration data from runtime execution.
AudioControl ScriptableObject
In Assets/Scripts/UCT/Control/AudioControl.cs, the AudioControl class stores the global AudioMixer asset and organizes sound effects into three functional lists. The important members include globalAudioMixer, fxClipUI, fxClipBattle, and fxClipWalk. This ScriptableObject acts as the central configuration hub that runtime controllers reference for mixer groups and clip libraries.
AudioController Singleton
Assets/Scripts/UCT/Audio/AudioController.cs implements the primary runtime coordinator as a singleton pattern. It owns the main BGM AudioSource attached to its own GameObject and manages an object pool of AudioPlayer components for FX playback. Key methods include PlayFx(...), PlayFxInternal(...), and GetClipFromCharacterSpriteManager(...). The singleton instance is accessible via AudioController.Instance.
AudioPlayer Pool Objects
Assets/Scripts/UCT/Audio/AudioPlayer.cs defines a lightweight MonoBehaviour that attaches to pooled GameObjects. Each instance contains its own AudioSource and exposes a Playing(AudioClip) method to start playback. The Update() method monitors clip completion and automatically returns the object to the controller's pool via AudioController.Instance.ReturnPool(gameObject, this).
MusicData Assets
Assets/Scripts/UCT/Control/MusicData.cs defines the ScriptableObject structure for music tracks. Each asset contains an AudioClip (clip) alongside metadata fields: musicDataName, authorDataName, informationDataName, and cover. These assets populate the music library loaded by the UI controller.
MusicRoomController UI Manager
Assets/Scripts/UCT/Scene/MusicRoomController.cs handles user-facing music selection and playback control. It exposes methods like SpawnMusicData() to load resources from Resources/Audios, SetMusic(bool) to toggle playback state, and SetMusicProgressUI to synchronize visual feedback with playback position.
How Sound Effects Are Managed
The FX pipeline uses object pooling to minimize garbage collection during gameplay.
Request and Validation
Any script initiates playback by calling AudioController.Instance.PlayFx(index, clipList). The method validates the index against the provided List<AudioClip> bounds and ensures the clip exists before proceeding.
Mixer Group Routing
If the caller does not specify an AudioMixerGroup, PlayFxInternal inspects the calling context against the three FX lists defined in AudioControl. It determines the appropriate channel—"FX/UI", "FX/Walk", or "FX/Battle"—and looks up the matching group in globalAudioMixer.FindMatchingGroups(groupName)[0].
Pool Lifecycle Execution
The controller retrieves a pooled AudioPlayer via GetFromPool<AudioPlayer>(), configures its AudioSource with the selected mixer group, volume, and pitch, then invokes Playing(clip). The AudioPlayer component monitors playback in its Update() loop and self-recycles when audioSource.isPlaying becomes false.
Example: Playing a UI click sound
// Play the first UI FX stored in the AudioControl asset
AudioController.Instance.PlayFx(0, MainControl.Instance.AudioControl.fxClipUI);
Example: Random footstep sound
int idx = UnityEngine.Random.Range(0, MainControl.Instance.AudioControl.fxClipWalk.Count);
AudioController.Instance.PlayFx(idx, MainControl.Instance.AudioControl.fxClipWalk);
How Background Music Is Managed
BGM handling differs fundamentally from FX by using a single persistent source rather than pooled objects.
BGM Source Initialization
In AudioController.Start(), the script creates a dedicated AudioSource on the same GameObject and forces it to the "BGM" mixer group:
audioSource.outputAudioMixerGroup =
MainControl.Instance.AudioControl.globalAudioMixer.FindMatchingGroups("BGM")[0];
Asset Loading and Selection
MusicRoomController.SpawnMusicData() loads all MusicData assets from Resources/Audios into the musicData list. When the player selects a track via UI input, the controller updates currentMusicDataIndex and prepares the clip for playback.
Playback Control
The controller assigns the selected clip to the singleton source and manages transport state:
var audioSrc = AudioController.Instance.audioSource;
audioSrc.clip = MusicRoomController.Instance.musicData[musicDataIndex].clip;
audioSrc.Play(); // Begins BGM playback
For pausing and resuming, scripts can check audioSrc.isPlaying and call Pause() or Play() accordingly. Seeking is achieved by modifying audioSrc.time within the bounds of audioSrc.clip.length.
UI Synchronization
MusicRoomController.SetMusicProgressUI reads audioSource.time and audioSource.clip.length each frame to calculate normalized progress. It updates a material property (_progressBar.material.SetFloat(Crop, normalized)) and repositions a point sprite to reflect the current playback position.
Summary
- Configuration Layer:
AudioControlstores the global mixer and categorizes FX into UI, battle, and walk lists. - FX Pipeline:
AudioController.PlayFx()routes requests through object-pooledAudioPlayercomponents with automatic mixer group selection and recycle-on-completion behavior. - BGM Pipeline: A single persistent
AudioSourcemanaged by theAudioControllersingleton handles music playback, driven byMusicDataassets and controlled throughMusicRoomController. - Mixer Routing: All audio routes through Unity's AudioMixer using named groups ("BGM", "FX/UI", "FX/Walk", "FX/Battle") for independent volume and effect control.
- Resource Management: Sound effects use object pooling to prevent runtime allocation, while music tracks load as
MusicDataScriptableObjects from theResources/Audiosfolder.
Frequently Asked Questions
How do I add a new sound effect category beyond UI, battle, and walk?
Create a new List<AudioClip> field in AudioControl.cs following the pattern of fxClipUI. Then modify AudioController.PlayFxInternal() to check against your new list and route to a corresponding mixer group (e.g., "FX/Ambient") looked up via globalAudioMixer.FindMatchingGroups().
Why does the audio system use object pooling for sound effects instead of creating AudioSources on demand?
The AudioPlayer pool prevents runtime garbage collection stutter during combat or rapid UI interactions. By recycling GameObjects with pre-configured AudioSource components, the system maintains consistent performance without allocating new Unity engine objects per sound trigger.
Can I play multiple background music tracks simultaneously?
The current architecture supports only one BGM source via AudioController.Instance.audioSource. To implement layered music (e.g., ambient plus combat layers), you would need to extend AudioController to manage additional persistent AudioSource instances, each routed through the mixer with independent volume control.
How do I programmatically seek to a specific timestamp in a music track?
Access the singleton audio source and set the time property directly, clamping to the clip duration:
var src = AudioController.Instance.audioSource;
src.time = Mathf.Clamp(targetTime, 0, src.clip.length);
The MusicRoomController demonstrates this pattern in its input handling for fast-forward and rewind functionality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →