LoL Engine Components Cheat Sheet¶
Initial Project Setup¶
Required Assets Creation¶
- Create Service Configuration
- Right-click in Project → Create → LoLEngine → Service Configuration
-
Name it (e.g., "MyGame_ServiceConfig")
-
Create Resource Path Config
- Right-click → Create → LoLEngine → Resource Path Config
-
Name it (e.g., "MyGame_ResourcePaths")
-
Setup Scene
- Create empty GameObject "EngineInitializer"
- Add
ImprovedGameInitializercomponent -
Assign ServiceConfiguration and ResourcePathConfig assets
-
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¶
- Create AudioConfig: Right-click → Create → LoLEngine → Audio → Audio Config
- 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)¶
- Create AudioMixer asset
- Expose these parameters (right-click parameter → Expose):
MasterVolume- Controls all audioMusicVolume- Music track volumeSFXVolume- Sound effects volumeUIVolume- UI sounds volumeVoiceVolume- Voice/dialogue volume- 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¶
- Create a MusicPlaylist: Right-click → Create → LoLEngine → Audio → Music Playlist
- Fill in
playlistIdand thetrackslist — each entry hasaddressableId(required),gainDb,displayTitle,artist,coverArt. LegacyaddressableIdslist (plain strings) auto-migrates totrackswhen the asset is opened in the Inspector. AudioConfigmusic-playlist fields:defaultCrossfadeDuration— seconds between tracks (default 2)defaultPreloadLookaheadSeconds— seconds before end to begin loading next clip (default 3)defaultPlaylistCrossfadeCurve—EqualPower(default) orLineardefaultPlaybackMode,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¶
- Create ResourceManagementConfig: Right-click → Create → LoLEngine → Config → Resource Management Config
- 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
Addressables Setup (Recommended)¶
- Window → Asset Management → Addressables → Groups
- Click "Create Addressables Settings"
- Mark assets as Addressable:
- Select asset → Inspector → Check "Addressable"
- 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¶
- 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. - Create SaveConfig (game behavior): Right-click → Create → LoLEngine → Data Persistence → Save Configuration
- 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¶
- Create LocalizationConfig: Right-click → Create → LoLEngine → Localization → Config
- 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¶
- Create CSV files in Resources/Localization/
- 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
}