Skip to content

Scene Management

ISceneService fills the scene-management hole every game needs: async load/unload with progress, optional CanvasGroup fades, loading from either the Build Settings list or Addressables behind one option, scene-change events, and an optional pre-load auto-save. It is deliberately minimal — load, unload, progress, fade. There is no scene-dependency graph or orchestration DSL; a scene is a name or an Addressables key, and the service loads it.

Enable

The service is optional and off by default. In your ServiceConfiguration asset, tick Enable Scene Service (under Game Services). Resolve it like any engine service:

using OxHeart.Core.SceneManagement.Interfaces;
using OxHeart.Core.ServiceManagement.Service;

var scenes = ServiceLocator.Instance.Get<ISceneService>();

Load a scene

LoadSceneAsync returns a SceneLoadResult you can await. The default options are an instant, no-fade Single (replace) load that resolves its source automatically:

var result = await scenes.LoadSceneAsync("Level1");
if (!result.Success)
    Debug.LogWarning($"Load failed: {result.Error}");

The service is graceful — a missing scene, an empty key, or a load that gets superseded by a newer one returns Success = false with a short Error; it never throws.

Options

SceneLoadOptions is a struct, so default is already a sane instant load. Set only what you need:

var result = await scenes.LoadSceneAsync("Level1", new SceneLoadOptions
{
    Mode = LoadSceneMode.Single,     // or Additive
    Source = SceneSource.Auto,       // Auto | Addressables | BuildSettings
    FadeOutDuration = 0.25f,         // seconds; 0 = no fade out
    FadeInDuration = 0.25f,          // seconds; 0 = no fade in
    AutoSaveBeforeLoad = true        // request a save before leaving the current scene
});

Where scenes load from — SceneSource

Source Behaviour
Auto (default) Try Addressables first (only if the package is present and a scene entry exists for the key), otherwise fall back to the Build Settings list. The Addressables probe is silent — a missing entry produces no error logs.
Addressables Load through Addressables by address/key. Fails gracefully if there is no entry.
BuildSettings Load by name/path from the Build Settings scene list.

Fades

Fades are opt-in per call. When FadeOutDuration/FadeInDuration are set, the service fades the screen to the configured colour before loading and back afterwards, using its own DontDestroyOnLoad overlay canvas — it never touches your camera or game UI, so it works even before your first scene has any UI. The fade runs on unscaled time, so it animates while the game is paused (Time.timeScale = 0).

Config fade durations are opt-in values — they are not auto-applied to default(SceneLoadOptions) (which is an honest instant no-fade load). Copy them in when you want the configured fade:

var cfg = /* your SceneServiceConfig */;
await scenes.LoadSceneAsync("Level1", new SceneLoadOptions
{
    FadeOutDuration = cfg.DefaultFadeOutDuration,
    FadeInDuration  = cfg.DefaultFadeInDuration
});

Progress and events

Progress is reported as a monotonic 0 → 1 (Unity's allowSceneActivation 0.9 quirk is mapped, and the value never moves backward) through a direct C# event — ideal for a bound loading bar:

scenes.OnLoadProgress += p =>
{
    loadingBar.value = p.Progress;              // 0..1
    phaseLabel.text  = p.Phase.ToString();      // FadingOut | Loading | Activating | FadingIn | Complete
};

The same information is published on the event bus as pooled GameEvents for decoupled systems. As with any engine event, subscribe by implementing IEventListener<T> and calling EventStartListening (see the Events guide):

using OxHeart.Core.Events.Interfaces;
using OxHeart.Core.Events.Providers;   // EventStartListening / EventStopListening
using OxHeart.Core.SceneManagement.Events;

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

    public void OnGameEvent(SceneEvents.LoadProgress e)
    {
        // e.SceneKey, e.Progress (0..1), e.Phase
    }
}

SceneEvents.LoadStarted (SceneKey, Mode) and SceneEvents.LoadCompleted (SceneKey, Success) are subscribed the same way.

IsLoading is true while a load is in flight, and ActiveSceneName reports the current active scene. Starting a second load while one is running supersedes the first (the first resolves to Success = false, Error = "superseded") — there is no queue.

Unload

bool unloaded = await scenes.UnloadSceneAsync("Overlay");

Returns true if the scene was unloaded, false if the request was refused (for example, trying to unload the only loaded scene). Like loading, it never throws.

Pre-load auto-save

Set AutoSaveBeforeLoad = true to request an immediate save (through the AutoSave service's SaveNow()) before the load begins — handy for saving progress before leaving a level. If the AutoSave service is not enabled, the request is warned-and-skipped and the load still proceeds. This is separate from AutoSaveService's own post-load save-on-scene-change.

Configuration — SceneServiceConfig

Create via Assets → Create → OxHeart → Scene Service Config and place it at Resources/Configs/DefaultSceneServiceConfig. If no asset is present the service uses sensible code defaults, so this is optional.

Field Purpose
Default Fade Color Colour the overlay fades to during a transition (default black).
Default Fade Out / In Duration Suggested fade durations — opt-in values you copy into SceneLoadOptions.
Fade Canvas Sort Order Sorting order of the overlay canvas (very high, so the fade renders above all game UI).
Auto Save Before Load Default A config-level default for callers that drive AutoSaveBeforeLoad from config.

API summary

Member Purpose
LoadSceneAsync(nameOrKey, options?)SceneLoadResult Load a scene with optional fades/progress/auto-save
UnloadSceneAsync(nameOrKey)bool Unload an additively-loaded scene (false if refused)
OnLoadProgress (event, SceneLoadProgress) Direct per-sample progress (key, 0..1, phase)
ActiveSceneName Name of the active scene
IsLoading True while a load is in flight
SceneEvents.LoadStarted / LoadProgress / LoadCompleted Pooled bus events for decoupled listeners

Sample

Import the Scene Management sample from Package Manager. Open SceneTransitionA.unity and press Play.

The demo shows:

  • Loading the sibling scene via SceneSource.Auto, BuildSettings, and Addressables, with fade-out / fade-in durations set in SceneLoadOptions
  • An on-screen progress bar driven by ISceneService.OnLoadProgress (the service's own DontDestroyOnLoad overlay handles fades — the sample canvas never animates its own fade)
  • Graceful failure hints when a scene is not in Build Settings and not Addressable
  • Editor-only Add / Remove sample scenes to/from Build Settings buttons (.unitypackage cannot ship ProjectSettings, so buyers add the scenes themselves — and can cleanly remove them again when done evaluating)

Setup notes:

  1. Add SceneTransitionA and SceneTransitionB to File → Build Settings (or use the in-sample editor button), or mark them Addressable, depending on which load path you want to exercise. Unity snapshots the Build Settings scene list when Play starts, so an add or remove made during Play takes effect on the next Play. When you are done with the sample, the Remove button restores your project's scene list.

    ⚠️ If you mark them Addressable, edit the address. Ticking Addressable makes Unity default the address to the scene's full asset path (Assets/OxHeart/Samples/SceneManagement/Scenes/SceneTransitionA.unity). The sample — like LoadSceneAsync generally — asks for the key SceneTransitionA, so the load fails until you rename the address in the Addressables Groups window to exactly SceneTransitionA (and SceneTransitionB). This applies to your own scenes too: the key you pass to LoadSceneAsync must match the Addressable address, not the file path. With SceneSource.Auto the mismatch is silent by design (the Addressables probe logs nothing and falls back to Build Settings), so a scene that is Addressable but wrongly addressed will quietly load via Build Settings — or fail, if it is not in that list either.

  2. Both scenes carry their own EngineInitializer. On a Single-mode transition the duplicate-instance guard destroys the second initializer with a warning — expected; each scene remains standalone-runnable.
  3. Keep AutoSaveBeforeLoad off in the demo (the sample does) unless AutoSave is also enabled — otherwise you hit the warn-and-skip path.