This page is a work in progress and could be pending updates.

Scene Loading

Overview

Fusion does not have an inherent concept of scene. Fusion instead provides an INetworkSceneManager interface, which allows developers to define custom handling for scene changes, referred to hereon as the Scene Manager.

NOTE: An example implementation can be found in the Fusion Scene Loading Sample.

A Scene Manager instance may be assigned in the NetworkRunner.StartGame() method via the SceneManager field of StartGameArgs. If no implementation is provided (null), Fusion will create and use a default instance of NetworkSceneManagerDefault.

The options for defining a Scene Manager are:

  • INetworkSceneManager: This interface can be implemented to define a completely custom Scene Manager.
  • NetworkSceneManagerBase: An abstract implementation of INetworkSceneManager with some basic handling. Derive from this class and extend it to define a custom Scene Manager.
  • NetworkSceneManagerDefault: A complete and working prototype implementation of INetworkSceneManager which derives from NetworkSceneManagerBase. This class may be used as is, or extended and overriden as needed. It may also serve as an example on how to create a completely custom implementation. This class is the default Scene Manager used when no SceneManager is supplied to NetworkRunner.StartGame().

Back To Top

INetworkSceneManager

To assign a class as the Scene Manager for a NetworkRunner, that class must implement the INetworkSceneManager interface with the following methods:

  • void Initialize(NetworkRunner runner): Used for any initialization which may require the NetworkRunner instance. Called by Fusion as part of the NetworkRunner.Initialize() handling.
  • void Shutdown(NetworkRunner runner): Called by Fusion as part of the NetworkRunner.Shutdown() process.
  • bool IsReady(NetworkRunner runner): Defines the check for whether the current runner scene has completed loading.

Back To Top

INetworkSceneManagerObjectResolver

Fusion maintains all the network scene object registered to it by default and resolve them when the runner requests them.

It it also possible to handled this process manually by implementing the INetworkSceneManagerObjectResolver interface. The interface provides the TryResolveSceneObject() method which defines how a network scene object matching the passed sceneObjectGuid to it is resolved (i.e. finding the object and returning it).

The full method signature is bool TryResolveSceneObject(NetworkRunner runner, Guid sceneObjectGuid, out NetworkObject instance).

Back To Top

NetworkSceneManagerDefault

The NetworkSceneManagerDefault is the default Scene Manager when no implementation is provided as part of the StartGameArgs to NetworkRunner.StartGame().

NetworkSceneManagerDefault inherits from NetworkSceneManagerBase. It implements the abstract coroutine to switch scenes locally in unity and pools all the NetworkObjects found in that scene. The NetworkSceneManagerDefault class may be also extended by using the available overrides to create a most customized Scene Manager behaviour.

When NetworkSceneManagerDefault is used, Runner.SetActiveScene() will set the scene index used by the scene update detection found in the default implementation.

Back To Top

NetworkSceneManagerBase

NetworkSceneManagerBase is an abstract class which implements the INetworkSceneManager interface, and may be used as a starting point for creating a custom Scene Manager.

NetworkSceneManagerBase handles detecting when a new scene needs to be loaded and manages the asyncronous loading process. It also allocates the simulation objects by passing the pooled NetworkObjects of the scene to NetworkRunner.RegisterSceneObjects().

When implementing a custom Scene Manager which inherits from NetworkSceneManagerBase, an implementation of the abstract IEnumerator SwitchScene() method is required. This method should load the scene, collect all the NetworkObjects found in that scene once it is loaded, and then pass the collection of NetworkObjects to the finished delegate. Beyond that, the developer is free to add any functionality they see fit.

Alternatively, the developer may create an entirely custom class by implementating INetworkSceneManager.

Back To Top

Scene Update Detection

The NetworkSceneManagerBase uses the LateUpdate() function to check if the currently loaded scene is different from the one provided by NetworkRunner.CurrentScene. This check is needed to detect if there is a new scene to be loaded.

private SceneRef _currentScene;
private bool _currentSceneOutdated;

protected virtual void LateUpdate() 
{
    if (!Runner) 
        return;

    if (Runner.CurrentScene != _currentScene) 
        _currentSceneOutdated = true;
}
NetworkRunner.CurrentScene is a simple implementation which only networks one SceneRef value used by clients to detect scene changes. To manage multiple scenes, it is possible to keep a collection instead to manage and make no use of NetworkRunner.CurrentScene.

Back To Top

INetworkSceneManager.IsReady

As the NetworkSceneManagerBase implements INetworkSceneManager, it needs to provide a implementation of IsReady() to tell when the NetworkRunner can try to attach the newly loaded network scene object.

bool INetworkSceneManager.IsReady(NetworkRunner runner) 
{
    Assert.Check(Runner == runner);

    if (_runningCoroutine != null) 
        return false;

    if (_currentSceneOutdated) 
        return false;

    if (runner.CurrentScene != _currentScene) 
        return false;

    return true;
}

If any loading scene coroutine is running or the current scene is outdated, the method returns false.

Back To Top

SwitchSceneWrapper

The SwitchSceneWrapper is the coroutine which will handle the actual scene loading process.

LateUpdate()
{
    // All the previous checks

    var prevScene = _currentScene;
      _currentScene = Runner.CurrentScene;
      _currentSceneOutdated = false;

      _runningCoroutine = SwitchSceneWrapper(prevScene, _currentScene);
      StartCoroutine(_runningCoroutine);
}

The proper execution of the coroutine is the following:

Some control variable and a dictionary to hold the new loaded scene object are created and the abstract SwitchScene() method implemented by the NetworkSceneManagerDefault is called and the resulted list of objects is registered by being passed to the NetworkRunner.RegisterSceneObjects() method.

private IEnumerator SwitchSceneWrapper(SceneRef prevScene, SceneRef newScene)
{
    bool finishCalled = false;
    IEnumerable<NetworkObject> sceneObjects;

    //...
    //Call and execution of abstratc SwitchScene()
    //...

    if (error != null) 
        LogError($"Failed to switch scenes: {error}");
    else if (!finishCalled) 
        LogError($"Failed to switch scenes: SwitchScene implementation did not invoke finished delegate");
    else 
    {
        Runner.RegisterUniqueObjects(sceneObjects.Values);
    }
}

Back To Top

SwitchScene

The SwitchScene() method implemented by the NetworkSceneManagerDefault performs the following operations:

  1. Unload the current scene (if current scene is valid).
  2. Call Unity LoadSceneAsync() method.
  3. Yield further execution until the await completes (scene load completed).
  4. If await completes and the loaded scene is invalid, throws an error and exit the coroutine.
  5. Find and store each NetworkObject in the loaded scene.
    1. Disable all the NetworkObject found in the newly loaded scene (they will be enabled when attached later)
    2. If in PeerMode.Multiple: Register each NetworkObject with the Runner Visibility handling.
  6. Trigger the Finished() delegate and pass it a collection of the NetworkObject found in the scene as an argument.
In PeerMode.Multiple loading a scene requires some special treatment to insert the object into a specific Runner's Physics Scene, and add the `NetworkObject`s to the Runner's Visibility handling.
    //The following code is a simplified overview.

    //The coroutine itself. 
    //It selects the proper handling method based on the peer mode.
    protected override IEnumerator SwitchScene(SceneRef prevScene, SceneRef newScene, FinishedLoadingDelegate finished) {
      if (Runner.Config.PeerMode == NetworkProjectConfig.PeerModes.Single) {
        return SwitchSceneSinglePeer(prevScene, newScene, finished);
      } else {
        return SwitchSceneMultiplePeer(prevScene, newScene, finished);
      }
    }

    //Summary of the single peer implementation.
    protected virtual IEnumerator SwitchSceneSinglePeer(SceneRef prevScene, SceneRef newScene, FinishedLoadingDelegate finished) {

      // Creating and defining needed variables.
      Scene loadedScene;
      Scene activeScene = SceneManager.GetActiveScene();

      LoadSceneParameters loadSceneParameters = new LoadSceneParameters(LoadSceneMode.Single);

      loadedScene = default;

      yield return LoadSceneAsync(newScene, loadSceneParameters, scene => loadedScene = scene);

      if (!loadedScene.IsValid()) {
        throw new InvalidOperationException($"Failed to load scene {newScene}: async op failed");
      }

      //Delay frames for safety
      for (int i = PostLoadDelayFrames; i > 0; --i) {
        yield return null;
      }

      var sceneObjects = FindNetworkObjects(loadedScene, disable: true);
      finished(sceneObjects);
    }


To Document Top