# How the Undertale Changer Template Audio System Manages Sound Effects and Music

> Discover how Undertale Changer Template's audio system uses a dual-layer architecture with AudioSource and object-pooled components for seamless music and sound effect management via Unity's AudioMixer.

- Repository: [Archived AIk/undertale-changer-template](https://github.com/arch-aik/undertale-changer-template)
- Tags: internals
- Published: 2026-02-25

---

**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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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**

```csharp
// Play the first UI FX stored in the AudioControl asset
AudioController.Instance.PlayFx(0, MainControl.Instance.AudioControl.fxClipUI);

```

**Example: Random footstep sound**

```csharp
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:

```csharp
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:

```csharp
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**: `AudioControl` stores the global mixer and categorizes FX into UI, battle, and walk lists.
- **FX Pipeline**: `AudioController.PlayFx()` routes requests through object-pooled `AudioPlayer` components with automatic mixer group selection and recycle-on-completion behavior.
- **BGM Pipeline**: A single persistent `AudioSource` managed by the `AudioController` singleton handles music playback, driven by `MusicData` assets and controlled through `MusicRoomController`.
- **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 `MusicData` ScriptableObjects from the `Resources/Audios` folder.

## 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`](https://github.com/arch-aik/undertale-changer-template/blob/main/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:

```csharp
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.