This document is about: FUSION 1
SWITCH TO

Scene Loading

Level 4

Overview

The Fusion Scene Loading sample is currently showcasing single peer scene loading only.

This Fusion Scene Loading sample explores and showcases a more customizable way of loading scenes in Fusion that the developer can manipulate for what it needs. Instead of a simple single scene loading, this sample uses a max of four scenes which can be loaded at the same time using additive loading, with the ability for each client to control which of the loaded scenes on server are loaded locally.

Highlights

  • Customizable scene loading.
  • Up to 4 simultaneous scenes.
  • Client side scene interest management.

Download

Version Release Date Download
1.1.3 Oct 21, 2022 Fusion Scene Loading 1.1.3 Build 55

Project

Before running the demo, a Fusion App Id for the Photon Cloud needs to be created and copy-pasted into the PhotonAppSettings asset. App Ids can be created from the Photon Dashboard. Make sure to create a Fusion App Id, not just a Realtime Id.

The Photon App Settings asset can be selected from the Unity menu Fusion > Realtime Settings.

Simply paste the generated App Id into the App Id Fusion field.

Folder Structure

The code for the sample is located in the Scripts folder, and is subdivided into:

  • Helpers: Minor classes for convenience.
  • Sample: Logic used by the 2 scenes that the sample provides.
  • CustomSceneLoading: The sample core classes that handle the scene loading.

Sample loop

The MainScene contains a field for entering the session name and a checkbox for enabling client scene management are displayed, along with two buttons to enter each mode available.

The client scene management is only used by the host, and clients will get the update through the networked property from the host of their session. If there is no session with the provided name, a session will be created in host mode.

In the "Buttons sample" four buttons are displayed which represent the four scenes available. The host can toggle which scenes are going to be loaded with these buttons.

If client scene management is enabled, clients may toggle these buttons to indicate which scenes are allowed to load.

In the "Character sample" each player controls a character. Moving the character close to any corner of the map will trigger the loading of the scene which neighbors that area. Leave the area will trigger an unload.

If client scene management is enabled, clients will only load the scenes which are allowed.

Client Scene Management

The client scene management logic restricts clients to only loading scenes which that client explicitly allows; these are specified by the InterestedInScenes collection. This allows each client to load a different subset of the active server scenes. The Host / Server has to loads every scene, as it is the state authority of the session.

This sample handles this with multiple scene lists:

  • CurrentScenes: The networked list of current scenes loaded on the Host/Server.
  • InterestedInScenes: contains the scenes which the local client will load. When scenes added to CurrentScenes on the server, they will only load on clients if that scene in included this collection.
  • LoadedScenes: are the currently loaded scenes locally. It is used to determine if changes in CurrentScenes or InterestedInScenes should trigger a load, an unload or nothing.

If ClientSceneManagement is enabled, clients will load only the CurrentScenes entries which are specifically allowed on the client (InterestedInScenes).

If ClientSceneManagement is disabled, clients will load all scenes listed in the CurrentScenes collection.

CustomSceneLoaderBase

This class is a clone of NetworkSceneManagerBase with a few changes. The most important change is that it declares two abstract methods.

  • bool IsScenesUpdated(): Returns true if there is any scene that needs to be loaded.
  • SceneRef GetDesiredSceneToLoad(): Returns the SceneRef of the scene to be loaded.

CustomSceneLoader

This class inherits from CustomSceneLoaderBase and implements the following abstract methods:

SwitchScene()

In the original NetworkSceneManagerBase class, a coroutine was defined for properly dealing with loading scenes in Unity, and returning all the NetworkObjects found in the loaded scene. The main difference with this variation used in this sample is that it is now a Task and load the scenes additively.

C#

  protected override async Task<IEnumerable<NetworkObject>> SwitchScene(SceneRef prevScene, SceneRef newScene) {
    prevScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1).buildIndex;
    Debug.Log($"Switching Scene from {prevScene} to {newScene}");

    List<NetworkObject> sceneObjects = new List<NetworkObject>();
    if (newScene >= 0) {
      var loadedScene = await LoadSceneAsset(newScene, LoadSceneMode.Additive);
      Debug.Log($"Loaded scene {newScene}: {loadedScene}");
      sceneObjects = FindNetworkObjects(loadedScene, disable: false);
    }

    Debug.Log($"Switched Scene from {prevScene} to {newScene} - loaded {sceneObjects.Count} scene objects");
    return sceneObjects;
  }

IsScenesUpdated()

Responsible for deciding when a new scene needs to be loaded. In this implementation, this calls a function on CustomNetworkSceneManager which implements the proper logic and checks for any scenes which need to be loaded and unloaded.

C#

protected override bool IsScenesUpdated() {
    if (Runner.SceneManager() && Runner.SceneManager().Object) {

      Runner.SceneManager().UnloadOutdatedScenes();

      return Runner.SceneManager().IsSceneUpdated(out _desiredScene);
    }

    return true;
  }

GetDesiredSceneToLoad()

Returns the scene to be loaded, _desiredScene which is assigned on IsScenesUpdated() as explained above.

C#

private SceneRef _desiredScene;

protected override SceneRef GetDesiredSceneToLoad() {
  return _desiredScene;
}

CustomNetworkSceneManager

To have a list with scene indexes to be loaded, a NetworkBehaviour with a networked collection is required. This NetworkBehaviour will be responsible for holding the scenes currently loaded and the scenes desired to be loaded.

C#

public class CustomNetworkSceneManager : NetworkBehaviour 
{
  private const int MAXSCENES = 4;
  [Networked] public NetworkBool ClientSceneManagement { get; set; }
  [Networked, Capacity(MAXSCENES)] public NetworkLinkedList<SceneRef> CurrentScenes => default;
  public List<SceneRef> InterestedInScenes = new List<SceneRef>(MAXSCENES);
  public List<SceneRef> LoadedScenes = new List<SceneRef>(MAXSCENES);

  private SceneRef _sceneToUnload;

  //...
}

Extension method

On Spawned(), the CustomNetworkSceneManager's registers itself with the NetworkRunner with the help of the NetworkRunner.SetSceneManager() custom extension method.

C#

//CustomNetworkSceneManager.cs

public override void Spawned()
{
    if (Runner.SceneManager())
    {
      Runner.Despawn(Object);
      return;
    }

    DontDestroyOnLoad(this);
    Runner.SetSceneManager(this);
}

Adding and removing scenes

The CustomNetworkSceneManager has methods for adding and removing scenes. These methods are responsible for handling the scenes indexes in the [Networked] CurrentScenes linked list on the host and in the InterestedInScenes list on the client side.

Scene update detection and outdated unload

bool IsSceneUpdated(out SceneRef sceneRef) returns true if there is any scene which should be loaded, and assign the out variable to the index of the scene that is needed to be loaded.

C#

public bool IsSceneUpdated( out SceneRef sceneRef )
{
    for (int i = 0; i < CurrentScenes.Count; i++) {
      if (CurrentScenes[i] == default) continue;
      if (LoadedScenes.Contains(CurrentScenes[i])) continue;
      
      if (ClientSceneManagement == false || InterestedInScenes.Contains(CurrentScenes[i])) {
        sceneRef = CurrentScenes[i];
        LoadedScenes.Add(CurrentScenes[i]);
        return false;
      }
    }
    sceneRef = SceneRef.None;
    return true;
}

The UnloadOutdatedScenes() function detects if there is any loaded scene that should unloaded and removes it from the _sceneToUnload list.

C#

public void UnloadOutdatedScenes(Action<AsyncOperation> removeOutdatedObjects)
{
    _sceneToUnload = LoadedScenes.Except(ClientSceneManagement ? InterestedInScenes.Intersect(CurrentScenes) : CurrentScenes).FirstOrDefault();

    // Reload scenes is priority

    // Reloading scenes is basically done by having a stack of
    // scenes to unload and if there's any scene left, pop out and
    // set to _sceneToUnload.

    if (_scenesToReload.Count > 0) {
      _sceneToUnload = _scenesToReload.Pop();

      // The host needs to say the clients to reload
      // only when finished reloading all scenes.
      if (_scenesToReload.Count == 0)
        OnUpToDate += TriggerReloadByte;
    }

    if (_sceneToUnload) {
      Debug.Log("Unloading scene - " + _sceneToUnload);
      SceneManager.UnloadSceneAsync(_sceneToUnload);
      LoadedScenes.Remove(_sceneToUnload);
    }
}
Back to top