Skip to content

LoL Engine Components Cheat Sheet

Initial Project Setup

Required Assets Creation

  1. Create Service Configuration
  2. Right-click in Project → Create → LoLEngine → Service Configuration
  3. Name it (e.g., "MyGame_ServiceConfig")

  4. Create Resource Path Config

  5. Right-click → Create → LoLEngine → Resource Path Config
  6. Name it (e.g., "MyGame_ResourcePaths")

  7. Setup Scene

  8. Create empty GameObject "EngineInitializer"
  9. Add ImprovedGameInitializer component
  10. Assign ServiceConfiguration and ResourcePathConfig assets

  11. ServiceConfiguration Options

    Core Services:
    [ ] Enable Event Manager - Required for all event handling
    [ ] Enable Resource Service - Required for asset loading
    [ ] Enable Object Pool - Enables GameObject pooling
    [ ] Enable Time Service - Time manipulation
    [ ] Enable Rng Service - Deterministic named RNG streams
    
    Game Services:
    [ ] Enable Audio Service - Sound/music playback
    [ ] Enable Music Playlist Service - Playlist orchestration over audio
    [ ] Enable Audio Orchestrator - Advanced audio routing (default off)
    [ ] Enable Data Persistence Service - Game saves/loads
    [ ] Enable Auto Save Service - Automatic saving at intervals
    [ ] Enable Quick Save Service - Quick save/load functionality (default off)
    [ ] Enable Localization Service - Multi-language support
    [ ] Enable Notification Service - In-game notifications
    [ ] Enable Game State Manager - Game state machine
    [ ] Enable Asset Updater Service - Remote catalog updates (default off)
    
    Support Services:
    [ ] Enable Serialization Service - JSON serialization (required for saves)
    [ ] Enable Compression Service - Data compression (optional for saves)
    [ ] Enable Encryption Service - Data encryption (optional for saves)
    

Quick Setup Pattern

Base Game Initializer

using LoLEngine.Core.GameInitializer;
using LoLEngine.Runtime;
using LoLEngine.Runtime.Logger;

[DefaultExecutionOrder(-100)]
public class MyGameInitializer : ImprovedGameInitializer
{
    void Start()
    {
        if (ServiceAwaiter.AreServicesReady())
            OnServicesReady();
        else
            ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
    }

    private void OnServicesReady()
    {
        // Your initialization code here
    }

    private void OnServicesTimeout()
    {
        LoLLogger.Error("Timed out waiting for services");
    }
}

MonoBehaviour Service Usage Pattern

public class MyComponent : MonoBehaviour
{
    private IMyService _myService;
    private bool _isInitialized = false;

    void Start()
    {
        if (ServiceAwaiter.AreServicesReady())
            OnServicesReady();
        else
            ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
    }

    private void OnServicesReady()
    {
        _myService = ServiceLocator.Instance.Get<IMyService>();
        _isInitialized = true;
    }

    void Update()
    {
        if (!_isInitialized || _myService == null) return;
        // Use service
    }

    void OnDestroy()
    {
        // Cleanup
    }
}

Audio System

Required Assets

  1. Create AudioConfig: Right-click → Create → LoLEngine → Audio → Audio Config
  2. Assign to ServiceConfiguration: In AudioConfig field

AudioConfig Options

Audio Settings:
├─ Tracks (List)
│  └─ Track Entry:
│     ├─ Name: "Music"/"SFX"/"UI"/"Voice" - Track identifier
│     ├─ Volume: 0-1 - Default volume for track
│     ├─ Mute: true/false - Start muted
│     ├─ Max Concurrent Sounds: -1 = unlimited, >0 = limit
│     └─ Mixer Group: Optional AudioMixerGroup assignment
│
├─ Pool Settings:
│  ├─ Use Object Pooling: true/false - Recycle AudioSources
│  ├─ Initial Pool Size: 10 - Pre-create this many AudioSources
│  └─ Max Pool Size: 50 - Maximum AudioSources in pool
│
├─ Default Settings:
│  ├─ Master Volume: 0-1 - Global volume multiplier
│  ├─ Default Fade Duration: 0.5f - Default fade time
│  └─ Spatial Blend: 0 = 2D, 1 = 3D - Default spatial setting
│
└─ Audio Mixer: Optional - Assign main AudioMixer asset

AudioMixer Setup (if using)

  1. Create AudioMixer asset
  2. Expose these parameters (right-click parameter → Expose):
  3. MasterVolume - Controls all audio
  4. MusicVolume - Music track volume
  5. SFXVolume - Sound effects volume
  6. UIVolume - UI sounds volume
  7. VoiceVolume - Voice/dialogue volume
  8. Assign to AudioConfig

Setup

private IAudioService _audioService;

// In OnServicesReady:
_audioService = ServiceLocator.Instance.Get<IAudioService>();

Basic Usage

// Play 2D sound
this.PlaySound("Audio/UIClick");
var handle = _audioService.PlaySound("Audio/UIClick");

// Play 3D sound
this.PlaySoundAtPosition("Audio/Explosion", transform.position);

// With options
var options = new PlayOptions
{
    volume = 0.8f,
    pitch = 1.2f,
    loop = true,
    fadeIn = true,
    track = _audioService.GetTrack("SFX")
};
var handle = _audioService.PlaySound("Audio/Music", options);

// Control playback
handle.SetVolume(0.5f);
handle.Pause();
handle.Resume();
handle.Stop();

// Track control
_audioService.SetTrackVolume(_audioService.GetTrack("Music"), 0.7f);
_audioService.SetTrackMute(_audioService.GetTrack("SFX"), true);

Randomized Playback (pitch/volume jitter + variant pools)

Use when the same action fires repeatedly (UI clicks, footsteps) and identical-sounding playback feels robotic. Types live in LoLEngine.Core.Audio.Data; entry points are extension methods in LoLEngine.Core.Audio.Extensions.

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

// Hold the pool as a field so no-immediate-repeat state persists across calls
private RandomAudioPool _clickPool = new RandomAudioPool(new[]
{
    "sfx_ui_click_a", "sfx_ui_click_b", "sfx_ui_click_c"
});

// Per-click:
var baseOptions = PlayOptions.Default;
baseOptions.track = _audioService.GetTrack("UI");

// Pool overload — picks a variant + applies jitter
await _audioService.PlayRandomAsync(_clickPool, baseOptions, PlayRandomization.Default);

// Single-id overload — just jitter, no pool
await _audioService.PlayRandomAsync("sfx_ui_click", baseOptions, PlayRandomization.Default);

// Presets: PlayRandomization.Default (light jitter) or .None (no jitter)
// Or build your own:
var rand = new PlayRandomization { minPitch = 0.9f, maxPitch = 1.1f, minVolume = 0.95f, maxVolume = 1f, avoidImmediateRepeat = true };

// Fire-and-forget: observe faults via the ObserveFault extension
// (zero-alloc static ContinueWith on OnlyOnFaulted, logs LogChannel.Custom1)
_audioService.PlayRandomAsync(_clickPool, baseOptions, PlayRandomization.Default)
             .ObserveFault("[MyComponent]");

Notes: - IAudioService/AudioService are unchanged — these are extension methods that compose existing PlaySoundAsync. - Returns Task<IAudioHandle>. Don't use _ = task — call .ObserveFault("[Tag]") (from LoLEngine.Core.Audio.Extensions) or await. - Build RandomAudioPool once and reuse; do NOT construct per-click (_lastIndex state is lost). - No cooldown/debounce included — add at the call site if needed (see below).

Rate limiting for continuous-input actions (hold-to-rotate, etc.):

Randomization makes rapid playback sound less repetitive but does not throttle anything. If the action can fire at frame rate, throttle at the call site — three lines, Time.unscaledTime, anchor the window on attempts:

[SerializeField] private float sfxCooldownSeconds = 0f;   // 0 = disabled; try ~0.05f for held-input rotation
private float _lastSfxTime = float.NegativeInfinity;

public void PlayRotate()
{
    if (sfxCooldownSeconds > 0f)
    {
        var now = Time.unscaledTime;
        if (now - _lastSfxTime < sfxCooldownSeconds) return;
        _lastSfxTime = now;
    }
    _audioService.PlayRandomAsync(_pool, baseOptions, rotateRandomization)
                 .ObserveFault("[RotateSfx]");
}

Only apply to continuous-input actions. Discrete events (pick/release, button click, snap, solve) don't need throttling.

Play Audio From Your UI

Use your UI controller or button handlers to play music or a stinger through IAudioService.

Steps: - Resolve IAudioService after engine initialization. - Call PlaySound from button click handlers. - Call PlayMusic or Play with PlayOptions when a menu/panel becomes visible. - Stop or fade music when leaving the menu if your game flow requires it.

Addressables setup: - Mark your AudioClip as addressable with key matching Audio Id (e.g., Audio/Music/MainMenuTheme). - Or place it under Resources/Audio/Music/MainMenuTheme and use that path.

Notes: - LoL Engine does not ship a UI service wrapper; use Unity uGUI, UI Toolkit, or your own UI framework directly. - Ensure IAudioService is enabled in your service configuration.


Music Playlist System

Orchestration layer on top of IAudioService for back-to-back, themed, or shuffled music playback with gapless crossfade, per-track loudness normalization, and Now-Playing metadata. Enable enableMusicPlaylistService in ServiceConfiguration (requires Audio Service).

Required Assets

  1. Create a MusicPlaylist: Right-click → Create → LoLEngine → Audio → Music Playlist
  2. Fill in playlistId and the tracks list — each entry has addressableId (required), gainDb, displayTitle, artist, coverArt. Legacy addressableIds list (plain strings) auto-migrates to tracks when the asset is opened in the Inspector.
  3. AudioConfig music-playlist fields:
  4. defaultCrossfadeDuration — seconds between tracks (default 2)
  5. defaultPreloadLookaheadSeconds — seconds before end to begin loading next clip (default 3)
  6. defaultPlaylistCrossfadeCurveEqualPower (default) or Linear
  7. defaultPlaybackMode, globalCrossfadeEnabledDefault, defaultMuteMode

Basic Usage

private IMusicPlaylistService _playlist;

private void OnServicesReady()
{
    _playlist = ServiceLocator.Instance.Get<IMusicPlaylistService>();
    _playlist.OnTrackChanged      += id    => Debug.Log($"Now playing: {id}");
    _playlist.OnTrackEntryChanged += entry => UpdateNowPlayingUI(entry);
    _playlist.OnPlaylistCompleted += pl    => Debug.Log("Finished");
}

// Authored playlist (MusicPlaylist asset)
_playlist.Play(forestThemePlaylist);

// Runtime string list (no metadata)
_playlist.Play(new[] { "music_a", "music_b", "music_c" });

// Runtime list with per-track metadata and gain
_playlist.Play(new[]
{
    new PlaylistTrackEntry { addressableId = "music_a", gainDb = -3f, displayTitle = "Forest Dawn", artist = "Composer" },
    new PlaylistTrackEntry { addressableId = "music_b", gainDb =  0f, displayTitle = "Deep Grove" },
});

// Controls
_playlist.Next();
_playlist.Previous();
_playlist.Pause();       // position-preserving
_playlist.Resume();
_playlist.Stop(1.5f);    // fade-out seconds; -1 = service default

// Theme swap (crossfades between playlists)
_playlist.SwitchTo(cityThemePlaylist);

// Shuffle — plays every track exactly once per cycle, then reshuffles
_playlist.Mode = PlaylistPlaybackMode.Shuffle;

// Now-Playing (polling)
var entry = _playlist.CurrentTrackEntry;

Per-Track Gain (loudness normalisation)

Set gainDb on each PlaylistTrackEntry. 0 dB = neutral. Recommended range: −12 to +6 dB. The linear gain (10 ^ (gainDb / 20)) multiplies into PlayOptions.volume alongside the Music track volume and master volume — no AudioMixer required.

Now-Playing UI

Subscribe to OnTrackEntryChanged or use the ready-made NowPlayingSample MonoBehaviour (Samples~/Scripts/11_NowPlayingSample.cs). Assign TMP_Text fields for title/artist and an Image for the cover sprite — the component handles updates automatically.

Gapless Crossfade

The service creates a hidden MusicPlaylistPump MonoBehaviour on DontDestroyOnLoad during LateInitialize(). Each frame it checks remaining time: - At remaining ≤ preloadLookaheadSeconds → loads the next clip into the resource cache. - At remaining ≤ crossfadeDuration (and cache is ready) → starts the next track early so both clips genuinely overlap. - Fallback: if the preload hasn't finished, OnComplete fires the advance as usual (same gap as before — no regression).

Equal-Power Crossfade

AudioConfig.defaultPlaylistCrossfadeCurve = EqualPower (default) uses sin/cos curves so the sum of the two fading signals stays near constant perceived loudness. Switch to Linear per-config to restore the old character.

Settings UI Pattern

_playlist.MasterMusicVolume        = 0.8f;
_playlist.GlobalCrossfadeEnabled   = false;     // kill-switch → instant cuts
_playlist.MuteMode                 = MuteMode.PauseOnMute; // or SilenceOnly
_playlist.IsMuted                  = true;
  • MuteMode.PauseOnMute — pauses the Music track; unmute resumes from the exact position.
  • MuteMode.SilenceOnly — silences but keeps playback advancing; unmute is in sync with wall-clock.

AudioMixer (optional)

Simple path: leave AudioTrackConfig.mixerGroup unset; per-track gainDb handles relative loudness. Bus-processing path: create an AudioMixer asset, add a Music group with: - Compressor: ratio ≈ 3:1, threshold ≈ −18 dB, knee 6 dB - Limiter: ceiling −1 dBFS

Assign the group to the Music entry's mixerGroup in AudioConfig. No engine code change required (AudioTrackConfig.mixerGroup is already an AudioMixerGroup field).

WAV Source Files

Unity re-encodes at build time based on each clip's Import Settings → Compression Format. Leave source files as WAV. Recommended for music beds: - Load Type: Streaming - Compression Format: Vorbis - Quality: 70–100 %

Trim any trailing silence in a DAW before import — author padding prevents true gapless transitions.

Crossfade Requirements

Crossfading requires the Music track's maxConcurrentSounds >= 2 (the service logs a warning at init and falls back to instant cuts if misconfigured).

Events

C# events: OnTrackChanged(string), OnTrackEntryChanged(PlaylistTrackEntry), OnPlaylistCompleted(MusicPlaylist). Also fires PlaylistEvents.{PlaylistStarted, PlaylistTrackChanged, PlaylistStopped, PlaylistSwitched, PlaylistCompleted} on IEventManager.


Input (Unity Input System)

LoL Engine has no IInputService (removed before 1.0 — see CHANGELOG.md). Install Unity's Input System package and use InputActionAsset / PlayerInput in game code. Optional starter: Runtime/Resources/Input/DefaultInputSystem_Actions.inputactions.

Full guide: Input.md.


Resource Management

Required Assets

  1. Create ResourceManagementConfig: Right-click → Create → LoLEngine → Config → Resource Management Config
  2. Assign to ServiceConfiguration: In ResourceManagementConfig field

ResourceManagementConfig Options

Loading Settings:
├─ Enable Addressables: true/false - Use Addressables system
├─ Preferred Source: Addressables/Resources/AssetBundles - Primary loading method
├─ Fallback To Resources: true/false - Try Resources if Addressables fails
│
Memory Management:
├─ Max Memory Budget MB: 512 - Maximum cached assets in memory
├─ Enable Reference Counting: true/false - Track asset usage
├─ Auto Unload Unused: true/false - Clean up unreferenced assets
├─ Unload Interval: 60 - Seconds between cleanup checks
│
Performance:
├─ Preload On Start: true/false - Load common assets at startup
├─ Async Loading Priority: High/Normal/Low - Default load priority
├─ Max Concurrent Loads: 4 - Simultaneous async operations
│
Addressables Settings (if enabled):
├─ Remote Catalog URL: "" - URL for remote content catalog
├─ Asset Download Path: "" - Where to download remote assets
├─ Check For Updates: true/false - Check for catalog updates
└─ Auto Release Handles: true/false - Automatic memory cleanup
  1. Window → Asset Management → Addressables → Groups
  2. Click "Create Addressables Settings"
  3. Mark assets as Addressable:
  4. Select asset → Inspector → Check "Addressable"
  5. Set meaningful address (e.g., "Audio/BackgroundMusic01")

Setup

private IResourceService _resourceService;

// In OnServicesReady:
_resourceService = ServiceLocator.Instance.Get<IResourceService>();

Loading Assets

// Async loading (recommended)
async void LoadAssets()
{
    // String-based loading
    var texture = await _resourceService.LoadAsync<Texture2D>("UI/ButtonTexture");
    var prefab = await _resourceService.LoadAsync<GameObject>("Prefabs/Enemy");

    // With options
    var options = new ResourceLoadOptions
    {
        Priority = ResourceLoadPriority.High,
        KeepInMemory = true
    };
    var audio = await _resourceService.LoadAsync<AudioClip>("Audio/Music", options);
}

// AssetReference loading (if using Addressables)
#if UNITY_ADDRESSABLES
[SerializeField] private AssetReference enemyPrefabRef;

async void LoadWithAssetReference()
{
    if (enemyPrefabRef != null && enemyPrefabRef.RuntimeKeyIsValid())
    {
        var prefab = await _resourceService.LoadAsync<GameObject>(enemyPrefabRef);
    }
}
#endif

// Cleanup
void OnDestroy()
{
    _resourceService.Release("path/to/asset");
    #if UNITY_ADDRESSABLES
    _resourceService.Release(enemyPrefabRef);
    #endif
}

Object Pooling

Required Assets

No specific config required - service works automatically. Pool settings can be configured per-prefab when spawning.

Basic Usage

// Spawn from pool
GameObject enemy = this.Spawn(enemyPrefab, spawnPosition, Quaternion.identity);
BulletComponent bullet = this.Spawn<BulletComponent>(bulletPrefab, position, rotation);

// With pool configuration
var bullet = this.SpawnWithConfig<BulletComponent>(bulletPrefab,
    capacity: 20,
    maxSize: 50,
    position: spawnPosition);

// Return to pool
this.Despawn(enemy);
this.Despawn(bullet.gameObject);

// Preload pool
void Start()
{
    this.PreloadPool(bulletPrefab, 20);
}

Event System

Required Assets

No specific config required - service initializes automatically.

Define Event

public class PlayerHealthChangedEvent : GameEvent
{
    public int CurrentHealth { get; set; }
    public int MaxHealth { get; set; }
}

Listen to Events

public class HealthUI : MonoBehaviour, IEventListener<PlayerHealthChangedEvent>
{
    void OnEnable() => this.EventStartListening<PlayerHealthChangedEvent>();
    void OnDisable() => this.EventStopListening<PlayerHealthChangedEvent>();

    public void OnGameEvent(PlayerHealthChangedEvent eventData)
    {
        // Update UI
    }
}

Trigger Events

// Get pooled event
var healthEvent = GameEvent.GetEvent<PlayerHealthChangedEvent>();
healthEvent.CurrentHealth = 75;
healthEvent.MaxHealth = 100;
GameEvent.Trigger(healthEvent);

Data Persistence

Required Assets

  1. Create LoLEngineConfig (engine-wide): Assets/Resources/Configs/DefaultLoLEngineConfig.asset — compression, encryption, extensions, and paths live here. Do not commit production keys; inject via environment variables/CI.
  2. Create SaveConfig (game behavior): Right-click → Create → LoLEngine → Data Persistence → Save Configuration
  3. Place SaveConfig at: Assets/Resources/Configs/DefaultSaveConfig.asset (auto-discovered by AutoSave)

SaveConfig Options

Game Behavior:
├─ Default Save Slot: "save1" - Default slot name
├─ Enable Auto Save: true/false - Enable automatic saving
├─ Auto Save Interval: 300 - Seconds between auto-saves
├─ Auto Save Slot: "autosave" - Dedicated auto-save slot
├─ Restrict Auto Save To Gameplay: true/false - Skip MainMenu/Boot/Loading scenes
├─ Enable Quick Save: true/false - Enable quick save/load
└─ Save Thumbnails: true/false - For UI usage (optional)

Note: the number of quick saves to keep is a runtime property on IQuickSaveService (MaxQuickSaves, default 5), not a SaveConfig field.

Note: Engine-wide save directory, file extensions, compression, obfuscation, and encryption live in LoLEngineConfig (Resources/Configs/DefaultLoLEngineConfig).

Required Service Dependencies

Enable these in ServiceConfiguration: - [x] EnableDataPersistenceService (required) - [x] EnableSerializationService (required) - [ ] EnableCompressionService (if using compression) - [ ] EnableEncryptionService (if using encryption) - [ ] EnableAutoSaveService (if using auto-save)

Setup Data Classes

[Serializable]
public class PlayerData : PersistableData
{
    public override string DataId => "player";

    [SerializeField] private int level;
    [SerializeField] private Vector3 position;

    public int Level { get => level; set => level = value; }
    public Vector3 Position { get => position; set => position = value; }
}

Save/Load

private ISaveSystem _saveSystem;

// In OnServicesReady:
_saveSystem = ServiceLocator.Instance.Get<ISaveSystem>();

// Get/Create data
var playerData = _saveSystem.GetData<PlayerData>();
playerData.Level = 10;
playerData.Position = transform.position;

// Save
_saveSystem.SaveGame("slot1");

// Load
if (_saveSystem.HasSaveFile("slot1"))
{
    _saveSystem.LoadGame("slot1");
    var loadedData = _saveSystem.GetData<PlayerData>();
    transform.position = loadedData.Position;
}

// Async operations
await _saveSystem.SaveGameAsync("slot1");
await _saveSystem.LoadGameAsync("slot1");

// List / delete / latest / any
var files = await _saveSystem.ListSavesAsync();
await _saveSystem.DeleteAsync("slot1");
var latest = await _saveSystem.GetLatestAsync();
bool any = await _saveSystem.HasAnySavesAsync();

Auto-Save

var autoSaveService = ServiceLocator.Instance.Get<IAutoSaveService>();
autoSaveService.StartAutoSave();
autoSaveService.StopAutoSave();
autoSaveService.SaveNow();

Paths and Extensions

var settings = new SaveSettings();
string path = settings.GetSaveFilePath("slot1");
string ext = settings.GetFileExtension(); // do not hardcode .sav/.osav/.esave

Security Notes

  • Configure encryption/compression only in LoLEngineConfig.
  • Inject production keys via environment variables or CI; never commit real keys.

Localization

Required Assets

  1. Create LocalizationConfig: Right-click → Create → LoLEngine → Localization → Config
  2. Assign to ServiceConfiguration: In LocalizationConfig field

LocalizationConfig Options

Language Settings:
├─ Default Language: English - Fallback language
├─ Use System Language: true/false - Auto-detect from OS
├─ Supported Languages: List<SystemLanguage> - Available languages
│  └─ Add languages your game supports
│
Table Settings:
├─ Localization Tables Path: "Localization/" - Path in Resources
├─ Table Format: CSV/JSON - File format for translations
├─ Auto Load Tables: true/false - Load all tables on init
├─ Missing Key Behavior: ShowKey/ShowError/UseDefault
│  ├─ ShowKey - Display the key itself
│  ├─ ShowError - Show error message
│  └─ UseDefault - Use default language
│
Text Processing:
├─ Enable Text Formatting: true/false - Support {0} placeholders
├─ Enable Rich Text: true/false - Support TextMeshPro tags
├─ RTL Language Support: true/false - Right-to-left languages
│
Performance:
├─ Cache Translations: true/false - Keep in memory
├─ Lazy Load Languages: true/false - Load only when needed
├─ Max Cache Size: 1000 - Maximum cached strings
│
Debug:
├─ Show Language Debug: true/false - Display language codes
├─ Highlight Missing: true/false - Highlight untranslated text
└─ Export Missing Keys: true/false - Log missing translations

Create Localization Tables

  1. Create CSV files in Resources/Localization/
  2. Format:
    Key,Context,English,French,German,Japanese
    UI_PLAY_BUTTON,Main Menu,Play,Jouer,Spielen,プレイ
    UI_SETTINGS,Main Menu,Settings,Paramètres,Einstellungen,設定
    ITEM_SWORD_DESC,Items,A sharp blade,Une lame tranchante,Eine scharfe Klinge,鋭い刃
    

Setup

private ILocalizationService _localizationService;

// In OnServicesReady:
_localizationService = ServiceLocator.Instance.Get<ILocalizationService>();

Usage

// Get text
// using LoLEngine.Core.Localization.LocString;
string text = _localizationService.Format(new LocString(LocTable.UI, "UI_WELCOME"));
string formatted = _localizationService.Format(
    new LocString(LocTable.UI, "SCORE_TEXT", new DynamicVar("score", playerScore)));

// Change language
_localizationService.SetLanguage(SystemLanguage.French);

// Get available languages
var languages = _localizationService.AvailableLanguages;

UI

LoL Engine does not provide an IUIService, UIScreen, or UIConfig API in this release. Use Unity uGUI, UI Toolkit, or your project's UI framework directly, and call LoL Engine services such as IAudioService, ILocalizationService, and IEventManager from your UI controllers.


Time Management

Service Configuration

No config asset required - enable in ServiceConfiguration: - [x] Enable Time Service

Default Time Channels (auto-created)

Global Channel:
├─ Controls: Overall game time
├─ Default Scale: 1.0
└─ Use For: General gameplay

UI Channel:
├─ Controls: UI animations/transitions
├─ Inherits Global: false - Independent from game pause
└─ Use For: Menus, HUD animations

Audio Channel:
├─ Controls: Audio playback speed
├─ Inherits Global: true - Pauses with game
└─ Use For: Music, sound effects

Custom Channels:
└─ Create via: timeService.CreateChannel("name", inheritsGlobal)

Time Service Features

Global Controls:
├─ Pause/Resume - Affects all inheriting channels
├─ Time Scale - 0.5 = half speed, 2.0 = double speed
├─ Smooth Transitions - Lerp between time scales
│
Timer Types:
├─ Countdown - Fires callback after duration
├─ Stopwatch - Tracks elapsed time
├─ Periodic - Repeats at intervals
└─ Scheduled - Execute at specific time

Usage

// Global time control
_timeService.SetTimeScale(0.5f, 1.0f, EasingType.EaseOut);
_timeService.PauseGlobal();
_timeService.ResumeGlobal();

// Channel-specific time
var gameplayChannel = _timeService.GetChannel("Gameplay");
gameplayChannel.SetTimeScale(0.25f);
float deltaTime = gameplayChannel.DeltaTime;

// Timers
var timer = _timeService.CreateTimer(5.0f, () => Debug.Log("Timer done!"));
timer.Start();

// Scheduling
_timeService.Scheduler.Schedule(() => Debug.Log("Delayed!"), 2.0f);

// MonoBehaviour extensions
this.DelayedCall(() => Debug.Log("After 3 seconds"), 3.0f);

Notifications

Service Configuration

No config asset required - enable in ServiceConfiguration: - [x] Enable Notification Service

Default Categories

NotificationCategory enum:
├─ UI - User interface messages
├─ System - System/engine messages
├─ Gameplay - Game events/achievements
├─ Level - Level-specific notifications
├─ Combat - Combat-related notifications
├─ Achievement - Achievement unlocks
└─ Error - Error messages

NotificationPriority enum:
├─ Low - Background info
├─ Normal - Standard messages
├─ High - Important alerts
└─ Critical - Must-see messages

Notification Features

Display Options:
├─ Duration: 0 = manual dismiss, >0 = auto-dismiss
├─ Persistent: Survives scene changes
├─ Queue: Wait for previous to finish
└─ Data: Custom payload Dictionary<string, object>

Management:
├─ History: Keep last N notifications
├─ Categories: Enable/disable by type
├─ Active List: Currently showing
└─ Filtering: By category/priority

Send Notifications

// Simple notification
this.SendNotification("Title", "Message", NotificationCategory.Gameplay);

// With options
this.SendNotification(
    "Quest Complete",
    "You earned 100 gold!",
    NotificationCategory.Gameplay,
    NotificationPriority.High,
    duration: 5f,
    data: new Dictionary<string, object> { { "gold", 100 } }
);

Subscribe to Notifications

void Start()
{
    this.SubscribeToNotifications(NotificationCategory.Gameplay, HandleNotification);
}

void OnDestroy()
{
    this.UnsubscribeFromNotifications(NotificationCategory.Gameplay, HandleNotification);
}

private void HandleNotification(INotification notification)
{
    Debug.Log($"{notification.Title}: {notification.Message}");
}

Game State Management

Service Configuration

No config asset required - enable in ServiceConfiguration: - [x] Enable Game State Manager Service

Engine vs Game State IDs

Engine GameStateType (0–99):
├─ None (0) - Uninitialized / no active state
├─ Bootstrap (1) - Engine startup
└─ Loading (2) - Scene/asset transition

Game-defined (100+ in YOUR project):
public static class MyGameStateType {
    public const GameStateType MainMenu = (GameStateType)100;
    public const GameStateType Gameplay = (GameStateType)101;
    public const GameStateType Paused   = (GameStateType)102;
}
See Samples~/GameState/SampleGameStateType.cs

State Features

State Lifecycle (IGameState / BaseGameState):
├─ Enter() - Setup when entering state
├─ Exit() - Cleanup when leaving state
├─ Update() - Per-frame logic (if needed)
└─ FixedUpdate() - Physics-tick logic (if needed)

State Events:
├─ BeforeStateChange - Pre-transition
├─ AfterStateChange - Post-transition
└─ Via EventManager (IEventListener + EventStartListening)

Usage

// Bootstrap: register before ChangeState
_gameStateManager.RegisterState(new MainMenuState());
_gameStateManager.RegisterState(new GameplayState());
_gameStateManager.RegisterState(new PausedState());

// Change state
_gameStateManager.ChangeState(MyGameStateType.Gameplay);

// Check current state
if (_gameStateManager.IsInState(MyGameStateType.Paused))
{
    // Handle paused state
}

// Listen (MonoBehaviour implementing IEventListener<T>)
void OnEnable()  => this.EventStartListening<GameStateEvents.AfterStateChangeEvent>();
void OnDisable() => this.EventStopListening<GameStateEvents.AfterStateChangeEvent>();

public void OnGameEvent(GameStateEvents.AfterStateChangeEvent evt)
{
    if (evt.NewCurrentStateType == MyGameStateType.Paused) { /* ... */ }
}

// Create custom state
public class MyGameState : BaseGameState
{
    public override GameStateType StateType => MyGameStateType.Gameplay;

    public override void Enter() { /* Setup */ }
    public override void Exit() { /* Cleanup */ }
    public override void Update() { /* Update logic */ }
}

Thread Safety & Performance

Enhanced Thread Safety

All core services now provide production-ready thread safety:

// Thread-safe service access from any thread
private async Task BackgroundProcessing()
{
    await Task.Run(() => {
        // Safe to access services from background threads
        var resourceService = ServiceLocator.Instance.Get<IResourceService>();
        var audioService = ServiceLocator.Instance.Get<IAudioService>();

        // All operations are thread-safe
        resourceService.LoadAsync<Texture2D>("background_texture");
        audioService.PlaySound("notification_sound");
    });
}

Handle Leak Prevention

Addressables handles are automatically tracked and cleaned up:

// Monitor handle usage (development builds)
#if DEVELOPMENT_BUILD || UNITY_EDITOR
void CheckHandleLeaks()
{
    // This logs active handle counts
    _resourceService.GetResourceStats();
    // Output: "Active AssetReference handles: 5"
    // Output: "Active AddressableResourceLoader handles: 12"
}
#endif

Memory Management Best Practices

// Proper cleanup on scene transition
void OnDestroy()
{
    // Release all AssetReference resources
    if (_resourceService != null)
    {
        _resourceService.Release(myAssetReference);
    }

    // Service cleanup is automatic during shutdown
}

Common Patterns

Default Config Locations

If configs aren't assigned in ServiceConfiguration, the system looks for defaults at: - Resources/Configs/DefaultLoLEngineConfig - Resources/Configs/DefaultLocalizationConfig - Resources/Configs/DefaultSaveConfig - Resources/Configs/DefaultAudioConfig - Resources/Configs/DefaultResourceManagementConfig

Service Dependencies

Some services require others to be enabled: - SaveSystem → SerializationService, CompressionService (optional), EncryptionService (optional)

Service Cleanup

void OnDestroy()
{
    if (_eventManager != null)
    {
        this.EventStopListening<MyEvent>();
    }

    // Release resources
    _resourceService?.Release("assetPath");
}

Error Handling

try
{
    var service = ServiceLocator.Instance.Get<IMyService>();
    // Use service
}
catch (ServiceNotFoundException ex)
{
    LoLLogger.Error($"Service not found: {ex.Message}");
}

Null Checking Pattern

void Update()
{
    if (!_isInitialized || _myService == null) return;
    // Safe to use service
}