Skip to content

LoL Engine Audio System Documentation

Overview

The LoL Engine Audio System provides a comprehensive, AAA-quality audio solution for Unity games. It features two tiers: a foundational Core Audio System for basic needs, and an advanced AAA Audio Orchestrator for professional-grade audio management with voice prioritization, ducking, and preloading.

5-Minute Quick Start

For Scene Background Music (Most Common)

  1. Create AudioMixer:
  2. Right-click → Create → Audio → Audio Mixer → Name: "MainAudioMixer"
  3. Create groups: Master → Music, SFX, UI, Voice
  4. Expose parameters: MasterVolume, MusicVolume, SFXVolume, UIVolume, VoiceVolume
  5. CRITICAL: Mark as Addressable with address LoLEngine/Audio/MainAudioMixer

  6. Enable Audio Services in your ServiceConfiguration.asset:

  7. Enable Audio Service
  8. Enable Audio Orchestrator

  9. Add SimpleAudioPlayer to your scene:

  10. Create empty GameObject → Add Component → LoLEngine → Helpers → Simple Audio Player
  11. Configure:

    • Audio Id: "Audio/Music/MainMenuTheme"
    • Track Name: "Music"
    • Volume: 0.7
    • Loop:
    • Play On Start:
    • Fade In Duration: 1.5
  12. Test: Press Play - your scene should play the background music!

Notes: - You do NOT need to create AudioConfig - the engine handles it automatically - AudioMixer MUST be Addressable or in Resources folder for the engine to find it


Getting Started - Complete Setup Guide

Prerequisites

  • LoL Engine package installed in your Unity project
  • Unity 6000.0+ (recommended)
  • Basic familiarity with Unity's Inspector and Project window

Step 1: Initial Project Setup

1.1 Verify LoLEngine Installation

Ensure you have LoLEngine properly set up: 1. Check that ServiceLocator and basic services are initialized 2. Verify you have a ServiceConfiguration asset in your project 3. Confirm LoLEngine assemblies are compiled without errors

1.2 Create Audio Directory Structure

Create the following folder structure for organization:

Assets/
├── Audio/
│   ├── Music/
│   ├── SFX/
│   ├── UI/
│   └── Voice/
└── Resources/
    └── Configs/
        └── Audio/

Step 2: Audio Asset Preparation

2.1 Import Audio Files

  1. Import your audio files into the appropriate folders:
  2. Music: Background music, ambient tracks
  3. SFX: Sound effects, impact sounds
  4. UI: Button clicks, notifications
  5. Voice: Dialog, narration

For better performance and memory management:

  1. Enable Addressables:
  2. Window → Asset Management → Addressables → Groups
  3. Click "Create Addressables Settings" if needed

  4. Mark Audio as Addressable:

  5. Select audio files in Project window
  6. Check "Addressable" in Inspector
  7. Set addresses following pattern: Audio/Category/FileName
  8. Example: Audio/Music/MainMenuTheme, Audio/SFX/ButtonClick

  9. Create Audio Groups:

  10. In Addressables Groups window, create groups:
    • Audio_Music (for background music)
    • Audio_SFX (for sound effects)
    • Audio_Critical (for UI and essential sounds)

Step 3: AudioMixer Setup

3.1 Create AudioMixer Asset

  1. Right-click in Project → Create → Audio → Audio Mixer
  2. Name it MainAudioMixer
  3. Create groups structure:
    Master
    ├── Music
    ├── SFX
    ├── UI
    └── Voice
    

3.2 Expose Parameters

For each group, expose volume parameters: 1. Right-click group → "Expose... to script" 2. Rename exposed parameters in Audio Mixer window: - MasterVolume - MusicVolume - SFXVolume - UIVolume - VoiceVolume

3.3 Make AudioMixer Addressable (CRITICAL)

IMPORTANT: The AudioMixer MUST be Addressable for the engine to load it.

  1. Select MainAudioMixer asset in Project window
  2. Check "Addressable" checkbox in Inspector (top of window)
  3. Set address to: LoLEngine/Audio/MainAudioMixer
  4. Save the changes

Alternative (Resources fallback): If not using Addressables, place the AudioMixer at: - Path: Assets/Resources/LoLEngine/Audio/MainAudioMixer.mixer - The engine will automatically fallback to Resources loading

Step 4: Service Configuration

4.1 Enable Audio Services

  1. Locate your ServiceConfiguration asset (usually in Assets/Resources/Configs/)
  2. In the Inspector, ensure these are enabled:
  3. Enable Audio Service (required foundation)
  4. Enable Audio Orchestrator (AAA features)

4.2 Understanding Audio Configuration Architecture

IMPORTANT: Volume Control Hierarchy

The LoLEngine audio system has a clear hierarchy for volume control:

  1. AudioMixer (Unity built-in) - PRIMARY RUNTIME VOLUME CONTROL
  2. Exposed parameters control actual playback volumes
  3. Set design-time defaults directly in the mixer asset
  4. Runtime changes via audioService.SetTrackVolume() write to mixer parameters
  5. Player preferences are persisted to PlayerPrefs and restored on startup

  6. AudioPriorityConfig (AAA Orchestrator) - VOICE MANAGEMENT & DUCKING

  7. Controls max concurrent sounds per category
  8. Voice stealing policies (Oldest, Quietest, Lowest Priority)
  9. Audio ducking rules and duck amounts
  10. Platform-specific performance overrides

  11. ~~AudioConfig~~ (Legacy, NOT NEEDED for AAA setup)

  12. Do NOT create this for AAA AudioOrchestratorService
  13. Only used by basic AudioService if you're not using the orchestrator
  14. Engine creates sensible defaults automatically if needed
  15. Volume settings in AudioConfig are overridden by AudioMixer anyway

What You Actually Need for AAA Setup: - AudioMixer (Step 3) - Volume control and routing - AudioPriorityConfig (below) - Voice limits and ducking - AudioPreloadProfile (below) - Critical audio preloading - MusicMapConfig asset (optional, below) - Scene/context music mappings - AudioConfig - NOT needed, engine provides defaults

4.3 Create AAA Audio Configuration Assets

Create AudioPriorityConfig (REQUIRED for AAA features): 1. Right-click in Project → Create → LoLEngine → Audio → Audio Priority Config 2. Name: AudioPriorityConfig 3. Location: Assets/LoLEngine/Runtime/Resources/Configs/Audio/AudioPriorityConfig.asset 4. Configure category rules:

Category Priority Max Voices Steal Policy Allow Ducking Duck Amount
Music 64 2 Oldest Yes 0.3
SFX 128 16 Lowest Priority No 0.7
UI 255 4 None No 1.0
Voice 200 3 Oldest No 1.0
Ambient 32 8 Quietest Yes 0.2

Field Explanations: - Priority: Higher = more important (0-255, where 255 = critical, won't be stolen) - Max Voices: Maximum concurrent sounds in this category (-1 = unlimited, not recommended) - Steal Policy: What to stop when limit reached (Oldest/Quietest/Lowest Priority/None) - Allow Ducking: Can this category's volume be reduced when high-priority audio plays? - Duck Amount: Volume multiplier when ducked (0.3 = reduce to 30% volume)

Create AudioPreloadProfile (RECOMMENDED for performance): 1. Right-click in Project → Create → LoLEngine → Audio → Audio Preload Profile 2. Name: DefaultPreloadProfile 3. Location: Assets/LoLEngine/Runtime/Resources/Configs/Audio/AudioPreloadProfile.asset 4. Add critical audio IDs by priority: - Emergency Priority: UI sounds, error alerts (must load instantly) - Important Priority: Menu music, player action sounds - Normal Priority: Ambient sounds, optional audio 5. Set memory budget and loading constraints: - Memory Budget MB: 50-100 MB for mobile, 200+ MB for PC/console - Max Concurrent Loads: 4-8 depending on platform - Loading Timeout: 5-10 seconds

Create MusicMapConfig (OPTIONAL for scene/context music mappings): 1. Right-click in Project → Create → LoLEngine → Audio → Music Map Config 2. Name: GameMusicMapConfig 3. Location: Assets/Resources/Configs/GameMusicMapConfig.asset (in your game project, not LoLEngine) 4. Add scene mappings:

Scene Name Audio ID Crossfade Duration Play On Load
MainMenu Audio/Music/MainMenuTheme 2.0 Yes
GameLevel Audio/Music/GameplayTheme 3.0 Yes
Combat Audio/Music/CombatTheme 1.5 Yes
  1. Add context mappings for dynamic music (optional):
  2. Context ID: "Combat", Audio ID: "Audio/Music/BattleIntense"
  3. Context ID: "Exploration", Audio ID: "Audio/Music/AmbientCalm"
  4. Context ID: "BossFight", Audio ID: "Audio/Music/BossTheme"

Note: MusicMapConfig requires custom code to use. See "Automatic scene-based music" in Troubleshooting section.

Step 5: Scene Implementation

5.1 Add Audio to Your Scene

For Background Music: 1. Create an empty GameObject named "AudioManager" 2. Add Component → LoLEngine → Helpers → Simple Audio Player 3. Configure in Inspector: - Audio Id: Audio/Music/MainMenuTheme - Track Name: Music - Volume: 0.7 - Loop: - Fade In Duration: 1.5 - Play On Start:

For Interactive Audio: 1. Select UI buttons or interactive objects 2. Add Component → LoLEngine → Helpers → Simple Audio Player 3. Configure for UI sounds: - Audio Id: Audio/UI/ButtonClick - Track Name: UI - Play On Start: (trigger manually)

5.2 Test Basic Setup

  1. Press Play in Unity Editor
  2. Verify you see in Console: "AudioOrchestratorService initialized successfully"
  3. Music should play without "Synchronous loading" warnings
  4. Test UI interactions trigger appropriate sounds

Step 6: Advanced Features

6.1 Loading Screen Preloading

For better performance during gameplay:

public class GameLoader : MonoBehaviour
{
    [SerializeField] private AudioPreloadProfile gameplayProfile;

    private async void Start()
    {
        var orchestrator = ServiceLocator.Instance.Get<IAudioOrchestratorService>();

        if (orchestrator != null && gameplayProfile != null)
        {
            await orchestrator.PreloadProfile(gameplayProfile);
            Debug.Log("Critical audio preloaded for instant playback");
        }

        // Continue with scene loading...
    }
}

6.2 Dynamic Music Context

For context-sensitive music:

public class GameStateManager : MonoBehaviour
{
    private IAudioOrchestratorService orchestrator;

    void Start()
    {
        orchestrator = ServiceLocator.Instance.Get<IAudioOrchestratorService>();
    }

    public void OnCombatStart()
    {
        orchestrator?.SetMusicContext("Combat", SceneManager.GetActiveScene().name);
    }

    public void OnCombatEnd()
    {
        orchestrator?.SetMusicContext("Exploration", SceneManager.GetActiveScene().name);
    }
}

Architecture Overview

graph TD
    A[AudioOrchestratorService] --> B[IAudioService - Core]
    A --> C[Voice Management]
    A --> D[Audio Ducking]
    A --> E[Preloading System]
    A --> F[Configuration System]

    B --> G[Track Management]
    B --> H[AudioMixer Integration]
    B --> I[Audio Source Pooling]
    B --> J[Resource Loading]

    K[SimpleAudioPlayer] --> A
    L[AdvancedAudioPlayer] --> A
    M[AudioPreloadManager] --> E

    F --> N[AudioPriorityConfig]
    F --> O[MusicMapConfig]
    F --> P[AudioPreloadProfile]

Core Components

AAA Audio Orchestrator

AudioOrchestratorService

Advanced audio service implementing ILoLEngineService with: - Priority-based voice management with intelligent stealing policies - Automatic audio ducking and mixer snapshot transitions - Context-aware music system with seamless crossfading - Smart preloading with memory management for instant critical audio playback - Platform-specific optimizations for mobile, console, and PC builds

Audio Components

SimpleAudioPlayer - Simple drop-in component for audio playback - Inspector-friendly configuration for one audio ID - Supports volume, looping, fade in/out, track name, play-on-start, and stop-on-disable - Good for scene background music, ambience, and simple UI sounds - Uses IAudioService through the service locator

AdvancedAudioPlayer - Full-featured component - Smart loading strategies (Always Async/Sync, PreloadThenSync, Smart) - AAA performance settings with priority levels and loading timeouts - Memory management integration with AudioPreloadManager

AudioPreloadManager - Singleton preloading manager - Batch preloading with progress reporting for loading screens - Automatic cleanup of unused preloaded assets - Memory usage tracking and optimization

Core Audio System (Foundation)

AudioService

The foundational audio service (IAudioService) that handles: - Track-based organization (Music, SFX, UI, Voice) - Unity AudioMixer integration with parameter validation - Audio source pooling for performance optimization - Resource loading via ResourceService (Addressables/Resources) - Settings persistence using PlayerPrefs

Key Interfaces

  • IAudioService - Core audio operations interface
  • IAudioHandle - Active sound instance control interface
  • AudioTrack - Sound category representation
  • PlayOptions - Detailed playback parameter specification

Usage Examples

Basic Audio Operations

// Get audio service
var audioService = ServiceLocator.Instance.Get<IAudioService>();

// Simple sound effect
var handle = audioService.PlaySound("Audio/SFX/Explosion");

// 3D positioned sound
var handle3D = audioService.PlaySoundAtPosition("Audio/SFX/Footstep", transform.position);

// Advanced playback with options
var options = new PlayOptions
{
    volume = 0.8f,
    pitch = 1.2f,
    loop = false,
    track = audioService.GetTrack("SFX"),
    fadeIn = true
};
var advancedHandle = audioService.PlaySound("Audio/Ambient/Wind", options);

AAA Orchestrator Usage

// Get orchestrator service
var orchestrator = ServiceLocator.Instance.Get<IAudioOrchestratorService>();

// Play music with crossfading
var musicOptions = new MusicOptions
{
    Volume = 0.8f,
    Loop = true,
    CrossfadeDuration = 2f
};
await orchestrator.PlayMusic("Audio/Music/CombatTheme", musicOptions);

// Play SFX with priority
var sfxOptions = new SfxOptions
{
    Category = AudioCategory.SFX,
    Priority = AudioPriority.Critical,
    Volume = 1f
};
await orchestrator.PlaySfx("Audio/Weapons/Gunshot", sfxOptions);

// Context-aware music switching
await orchestrator.SetMusicContext("Combat", SceneManager.GetActiveScene().name);

AdvancedAudioPlayer and AudioPreloadManager

Optional MonoBehaviour helpers in LoLEngine.Helpers.Audio for priority-based loading and batch preloading. Enable Audio Orchestrator in ServiceConfiguration for full voice-management features; these helpers work with IAudioService directly.

Components:

Component Purpose
AdvancedAudioPlayer Per-GameObject playback with priority, loading strategy, preload-on-start
AudioPreloadManager Scene singleton for batch preloading during loading screens
AudioPriority / AudioLoadingStrategy Priority levels and sync/async loading modes

Quick setup:

  1. Add AudioPreloadManager to your boot or loading scene (singleton, DontDestroyOnLoad).
  2. Configure preload groups in the Inspector (weapon SFX, UI clicks, etc.) with appropriate priority.
  3. Add AdvancedAudioPlayer to GameObjects; set audioId, priority, and loading strategy in the Inspector.

Loading screen batch preload:

public class GameLoadingScreen : MonoBehaviour
{
    private async void Start()
    {
        var criticalAudio = new List<string>
        {
            "Audio/Weapons/PrimaryWeapon",
            "Audio/Weapons/SecondaryWeapon",
            "Audio/Player/Footsteps",
            "Audio/UI/CriticalAlerts"
        };

        await AudioPreloadManager.Instance.PreloadAudioBatchAsync(
            criticalAudio,
            AudioPriority.Critical,
            progress => UpdateProgressBar(progress));

        SceneManager.LoadScene("GameLevel");
    }
}

Combat SFX (configure priority/strategy in Inspector, call from code):

public class WeaponController : MonoBehaviour
{
    [SerializeField] private AdvancedAudioPlayer weaponFirePlayer;
    [SerializeField] private AdvancedAudioPlayer reloadPlayer;

    public void FireWeapon()
    {
        weaponFirePlayer.PlaySync(); // instant when preloaded
    }

    public void StartReload()
    {
        reloadPlayer.Play(); // smart async loading
    }
}

Dynamic music via SetAudioIdAsync:

public async void ChangeGameState(GameState newState)
{
    string musicId = newState switch
    {
        GameState.Exploration => "Audio/Music/Exploration",
        GameState.Combat => "Audio/Music/Combat",
        GameState.Boss => "Audio/Music/BossFight",
        _ => "Audio/Music/Ambient"
    };

    await musicPlayer.SetAudioIdAsync(musicId);
}

Priority and loading strategy:

Priority Use case Loading strategy Examples
Emergency Must play instantly PreloadThenSync Death sounds, critical alerts
Critical Near-instant playback PreloadThenSync or Smart Weapon fire, UI feedback
Important Quick but not urgent Smart Combat music, key dialogue
Normal Standard game audio Smart or AlwaysAsync Ambient SFX
Background Can wait or skip AlwaysAsync Distant sounds

Memory management:

// Inspector: autoUnloadUnused + unloadDelay on AudioPreloadManager
AudioPreloadManager.Instance.UnloadAudio("Audio/Level1/SpecificSound");
var stats = AudioPreloadManager.Instance.GetStats();
// stats.preloaded, stats.loading, stats.failed

Migrating from SimpleAudioPlayer:

var oldPlayer = GetComponent<SimpleAudioPlayer>();
var newPlayer = gameObject.AddComponent<AdvancedAudioPlayer>();
newPlayer.SetAudioId(oldPlayer.CurrentAudioId);
// Set priority, loadingStrategy, preloadOnStart in Inspector
Destroy(oldPlayer);

For basic drop-in playback without priority/preloading, see AudioHelpers.md (SimpleAudioPlayer).

Randomized Playback (pitch/volume jitter + variant pools)

For actions that fire repeatedly — UI clicks, footsteps, low-impact SFX — playing the exact same clip at the exact same pitch sounds robotic. LoLEngine ships two composable primitives for this:

  • PlayRandomization — a serializable struct (min/max pitch, min/max volume, avoidImmediateRepeat) with Default (light jitter) and None (no jitter) presets.
  • RandomAudioPool — a small stateful picker over a string[] of audio IDs. Remembers its last pick so successive Pick calls can avoid immediate repeats.

Both are in LoLEngine.Core.Audio.Data. The entry points are extension methods on IAudioService in LoLEngine.Core.Audio.Extensions.AudioExtensions:

using LoLEngine.Core.Audio.Data;
using LoLEngine.Core.Audio.Extensions;

// --- Single id with jitter ---
var baseOptions = PlayOptions.Default;
baseOptions.track = audioService.GetTrack("UI");
await audioService.PlayRandomAsync("sfx_ui_click", baseOptions, PlayRandomization.Default);

// --- Variant pool (no-immediate-repeat) ---
// Build the pool ONCE (it holds the _lastIndex state across calls)
var clickPool = new RandomAudioPool(new[] { "sfx_ui_click_a", "sfx_ui_click_b", "sfx_ui_click_c" });

// Then each click — pool is reused, jitter re-rolled every call
await audioService.PlayRandomAsync(clickPool, baseOptions, PlayRandomization.Default);

// --- Pure helper: just roll new pitch/volume without playing ---
var jittered = baseOptions.WithRandomization(PlayRandomization.Default);
audioService.PlaySound("some_id", jittered);

When to use which overload: - PlayRandomAsync(string, ...) — single clip, just add pitch/volume variance. No pool state needed. - PlayRandomAsync(RandomAudioPool, ...) — you have 2+ variant clips and want no-immediate-repeat selection. Hold the RandomAudioPool as a field so the repeat-avoidance state persists across calls. - PlayOptions.WithRandomization(in PlayRandomization) — escape hatch when you want the jitter applied to options but plan to play through a different path (e.g. PlaySoundAtPosition).

Important design notes: - Neither IAudioService nor AudioService were modified. These are extension methods that compose PlaySoundAsync — you can still use every existing audio API without change. - The extensions return Task<IAudioHandle>, same as PlaySoundAsync. Fire-and-forget with _ = task will lose exceptions silently. Observe the task (see below) or await it. - PlayRandomization is a struct with sensible defaults; it's safe to call PlayRandomization.Default or PlayRandomization.None inline rather than caching one. - No cooldown/debounce is built in. If you need rate limiting (e.g., max 1 click sound per 50 ms), add it at the call site — it's unrelated to audio randomization.

Observing fire-and-forget tasks:

Because extensions return Task, any fault (missing audio ID, resource load failure) surfaces as an unobserved task exception unless you handle it. Use the ObserveFault extension in AudioExtensions — it attaches a zero-allocation fault observer that logs on LogChannel.Custom1:

private void OnButtonClicked()
{
    var baseOptions = PlayOptions.Default;
    baseOptions.track = _audioService.GetTrack("UI");

    var task = _audioService.PlayRandomAsync(_clickPool, baseOptions, _randomization);
    task.ObserveFault("[ButtonClickSfx]");  // logs as "[ButtonClickSfx] async audio failed: ..."
}

ObserveFault is safe to call with a null or already-completed task, handles the sync-completion path without allocating, and uses a static ContinueWith continuation (state-parameter overload) so there's no closure allocation per call — cheap enough for rapid UI input or held-key rotation.

A complete consumer-side example (wiring the API to a Unity Button with task observation) lives in the Tangrams project at Assets/Tangrams/Features/UI/Common/Scripts/ButtonClickSfx.cs.

Rate limiting at the call site:

Randomization makes rapid playback sound less repetitive, but it does not throttle anything — if rotation input fires at frame rate, you'll get 60 jittered clicks per second. That's the caller's problem, not the engine's: the right cooldown value depends entirely on how the action is driven (held-key input vs. discrete step events vs. touch taps). Don't try to push this into the engine — three lines at the call site is correct:

[SerializeField, Tooltip("Minimum unscaled seconds between plays. 0 disables throttling.")]
private float rotateSfxCooldownSeconds = 0f;

private float _lastRotateSfxTime = float.NegativeInfinity;

public void PlayRotate()
{
    if (rotateSfxCooldownSeconds > 0f)
    {
        var now = Time.unscaledTime;
        if (now - _lastRotateSfxTime < rotateSfxCooldownSeconds) return;
        _lastRotateSfxTime = now;   // anchor on attempts, not successes
    }

    var baseOptions = PlayOptions.Default;
    baseOptions.track = _audioService.GetTrack("SFX");
    _audioService.PlayRandomAsync(_rotatePool, baseOptions, _rotateRandomization)
                 .ObserveFault("[PuzzleAudio]");
}

Why these specific choices matter: - Time.unscaledTime, not Time.time — so the throttle still fires during pause/slow-mo animations. - Default 0f = disabled — safe to ship with the field added; behavior unchanged until a designer tunes it per action. - Timestamp updated inside the guard, not after the play call — anchors the window on attempts, so a caller that fires faster than the cooldown can't starve the window by perpetually resetting it from "just-missed" calls. - No throttle on Pick / Snap / Solved — picks are discrete grab-and-release events, snaps and solves are rare. Only continuous-input actions need this.

Starting value for tuning: ~50 ms (20 plays/sec max) is a reasonable anchor for rotation-style continuous input. Below ~30 ms it still sounds mashy even with pitch jitter; above ~100 ms you can hear the throttle kick in as "skipped" feedback. Adjust by ear. A live example of this pattern is in the Tangrams project at Assets/Tangrams/Features/Puzzle/Scripts/PuzzleAudio.cs (PlayRotate).

Runtime Sound Control

// Control individual sounds
handle.SetVolume(0.5f);
handle.SetPitch(0.8f);
handle.Pause();
handle.Resume();
handle.Stop();

// Subscribe to events
handle.OnComplete += () => Debug.Log("Sound finished!");
handle.OnDestroyed += () => Debug.Log("Handle cleaned up");

Track Management & Volume Control

Understanding Volume Control:

The audio system controls volume through AudioMixer exposed parameters. All volume changes write to the mixer, ensuring consistent audio routing and DSP processing.

// Get audio service
var audioService = ServiceLocator.Instance.Get<IAudioService>();

// Resolve tracks by name first — the volume/mute APIs take an AudioTrack
var music = audioService.GetTrack("Music");
var sfx   = audioService.GetTrack("SFX");
var ui    = audioService.GetTrack("UI");

// Set track volumes (writes to AudioMixer parameters)
audioService.SetTrackVolume(music, 0.7f);      // 70% volume
audioService.SetTrackVolume(sfx, 0.8f);        // 80% volume
audioService.SetTrackVolume(ui, 1.0f);         // 100% volume

// Read current volume / mute state from the resolved track
float musicVolume = music.Volume;

// Mute/unmute tracks
audioService.SetTrackMute(sfx, true);          // Mute sound effects
audioService.SetTrackMute(sfx, false);         // Unmute sound effects

// Solo: play one sound exclusively on a track, then restore normal playback
audioService.PlaySolo("Audio/Music/CombatTheme", music);
audioService.EndSolo();

// Check if a track is muted
bool isMuted = music.Muted;

Volume Persistence:

Volume settings are automatically saved to PlayerPrefs and restored on startup:

// Player changes music volume in settings menu
audioService.SetTrackVolume(audioService.GetTrack("Music"), 0.5f);

// Restart game
// Volume is automatically restored to 0.5f

Creating a Settings Menu:

public class AudioSettingsUI : MonoBehaviour
{
    [SerializeField] private Slider masterVolumeSlider;
    [SerializeField] private Slider musicVolumeSlider;
    [SerializeField] private Slider sfxVolumeSlider;

    private IAudioService audioService;

    void Start()
    {
        audioService = ServiceLocator.Instance.Get<IAudioService>();

        // Load current volumes
        masterVolumeSlider.value = audioService.GetTrack("Master").Volume;
        musicVolumeSlider.value = audioService.GetTrack("Music").Volume;
        sfxVolumeSlider.value = audioService.GetTrack("SFX").Volume;

        // Hook up slider events
        masterVolumeSlider.onValueChanged.AddListener(OnMasterVolumeChanged);
        musicVolumeSlider.onValueChanged.AddListener(OnMusicVolumeChanged);
        sfxVolumeSlider.onValueChanged.AddListener(OnSFXVolumeChanged);
    }

    void OnMasterVolumeChanged(float value)
    {
        audioService.SetTrackVolume(audioService.GetTrack("Master"), value);
    }

    void OnMusicVolumeChanged(float value)
    {
        audioService.SetTrackVolume(audioService.GetTrack("Music"), value);
    }

    void OnSFXVolumeChanged(float value)
    {
        audioService.SetTrackVolume(audioService.GetTrack("SFX"), value);
    }
}

Performance Considerations

Sound Limits

  • Configure max concurrent sounds per track to prevent audio overload
  • Use audio source pooling to minimize allocation overhead
  • Monitor active handle counts in development builds

Memory Management

  • Addressables automatically handles memory management for audio assets when enabled
  • Large audio files benefit from Addressables' reference counting and streaming
  • Proper cleanup of persistent handles
  • Event subscription management to prevent memory leaks

CPU Performance

  • Cleanup operations run at configurable intervals
  • Coroutine-based monitoring scales better than frame-based polling
  • Event-driven architecture minimizes unnecessary updates

Troubleshooting

"AudioOrchestratorService not available"

Solutions: 1. Check ServiceConfiguration asset has "Enable Audio Orchestrator" 2. Ensure "Enable Audio Service" is also (orchestrator depends on it) 3. Verify ServiceConfiguration is assigned in your scene's GameInitializer 4. Check Console for initialization errors

"Config not found, using defaults"

Solutions: 1. Create ScriptableObject assets using Unity's Create menu (not manual .asset files) 2. Place in exact path: Assets/LoLEngine/Runtime/Resources/Configs/Audio/ 3. File names must match exactly: AudioPriorityConfig.asset, MusicMapConfig.asset 4. Ensure .meta files are not corrupted

"Sync loading warnings still appear"

Solutions: 1. Verify orchestrator is enabled AND initialized successfully 2. Use SimpleAudioPlayer or AdvancedAudioPlayer instead of hand-written playback code 3. For critical audio, add to AudioPreloadProfile and preload during loading screens 4. Check audio files are properly marked as Addressable

"No audio playing at all"

Solutions: 1. Verify AudioMixer is configured with required tracks and exposed parameters 2. Check AudioMixer volume levels are not muted or set to -80dB 3. Verify audio asset exists at specified audioId path 4. Test with a simple audioId like "Audio/Music/TestClip" 5. Check Addressables Groups window - ensure audio is marked as Addressable 6. Verify speakers/headphones are connected and Unity audio is not muted

"Automatic scene-based music not working"

Note: There is no automatic scene-based music component. To use MusicMapConfig for automatic scene music, you must call AudioOrchestratorService.SetMusicContext() programmatically.

For Automatic Scene Music (Advanced): Create a script that loads music based on the scene:

public class SceneMusicManager : MonoBehaviour
{
    private IAudioOrchestratorService orchestrator;

    private async void Start()
    {
        orchestrator = ServiceLocator.Instance.Get<IAudioOrchestratorService>();

        if (orchestrator != null)
        {
            string sceneName = SceneManager.GetActiveScene().name;
            var handle = await orchestrator.SetMusicContext("", sceneName);

            if (handle == null)
            {
                Debug.LogWarning($"Failed to load music for scene: {sceneName}");
            }
        }
    }
}

For Simple Scene Music (Recommended): Use SimpleAudioPlayer with manual Audio ID configuration per scene, or write a small scene script that calls SetMusicContext().

"InvalidKeyException: No Location found for Key=LoLEngine/Audio/MainAudioMixer"

This error means the AudioMixer is not Addressable or not at the correct address:

Solutions: 1. Select your AudioMixer asset in Project window 2. Check "Addressable" checkbox in Inspector 3. Set address to: LoLEngine/Audio/MainAudioMixer (exactly) 4. Alternative: Move AudioMixer to Assets/Resources/LoLEngine/Audio/MainAudioMixer.mixer

Common mistake: Creating the AudioMixer but forgetting to mark it as Addressable. The engine requires either Addressables OR Resources folder placement.

"InvalidKeyException: No Location found for Key" (Audio Assets)

This error indicates invalid Addressable keys for audio files: Solutions: 1. Check audio asset addresses match your code exactly (case-sensitive) 2. Verify audio files are marked as Addressable in the Addressables Groups window 3. For music files, ensure MusicMapConfig audio IDs match Addressable addresses 4. Test with a simple path like "Audio/Music/TestClip" to verify Addressables is working 5. Check Addressables Groups window → Default Local Group → ensure assets are listed

"Audio components don't show in Component menu"

Solutions: 1. Check compilation errors in Console window 2. Verify all scripts compiled successfully 3. Try Unity → Assets → Refresh 4. Restart Unity Editor if needed

Migration Guide

SetMusicContext Return Value

AudioOrchestratorService.SetMusicContext returns Task<IAudioHandle>, so callers can detect whether music actually loaded.

var handle = await orchestrator.SetMusicContext("Combat", sceneName);
if (handle != null)
{
    Debug.Log("Music loaded successfully");
}
else
{
    Debug.LogWarning("Music failed to load - implement fallback");
}

Benefits: - Proper error detection and handling - Enables intelligent fallback systems - Better debugging and monitoring capabilities

From Legacy Audio Players

Replace existing audio components with: - SimpleAudioPlayer - For scene background music and simple audio playback - AdvancedAudioPlayer - For complex audio scenarios requiring advanced loading strategies and memory management - Custom Script - For MusicMapConfig-based automatic scene music (see "Automatic scene-based music" in Troubleshooting section)

From Core AudioService Only

  1. Enable AudioOrchestrator in ServiceConfiguration
  2. Create configuration ScriptableObjects
  3. Optionally replace direct AudioService calls with orchestrator components
  4. Add preloading profiles for critical audio

Debug Features

  • Comprehensive logging through LoLLogger system
  • Parameter validation during initialization
  • Runtime handle monitoring and cleanup reporting
  • Performance statistics via GetPerformanceStats()

This Audio System provides a comprehensive, robust, and performant solution for managing all aspects of game audio within the LoL Engine, designed with scalability, maintainability, and ease of use in mind.