Scene Loading
Overview
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 toCurrentScenes
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 inCurrentScenes
orInterestedInScenes
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 theSceneRef
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