Skip to main content

Using NetworkSceneManager

Netcode for GameObjects Integrated Scene Management#

The NetworkSceneManager Provides You With:#

  • An existing framework that supports both the bootstrap and scene transitioning scene management usage patterns.
  • Automated client synchronization that occurs when a client is connected and approved.
    • All scenes loaded via NetworkSceneManager will be synchronized with clients during client synchronization.
      • Later in this document, you will learn more about using scene validation to control what scenes are synchronized on the client, server, or both side(s).
    • All spawned Netcode objects are synchronized with clients during client synchronization.
  • A comprehensive scene event notification system that provides you with a plethora of options.
    • It provides scene loading, unloading, and client synchronization events ("Scene Events") that can be tracked on both the client and server.
      • The server is notified of all scene events (including the clients')
        • The server tracks each client's scene event progress (scene event progress tracking)
      • Clients receive scene event messages from the server, trigger local notifications as it progresses through a Scene Event's, and send local notifications to the server.
        • clients are not aware of other clients' scene events
  • In-Scene placed NetworkObject synchronization
info

In-Scene placed NetworkObjects can be used in many ways and are treated uniquely from that of dynamically spawned NetworkObjects. An in-scene placed NetworkObject is a GameObject with a NetworkObject and typically at least one NetworkBehaviour component attached to a child of or the same GameObject. It is recommended to read through all integrated scene management materials (this document, Scene Events, and Timing Considerations) before learning about more advanced In-Scene (placed) NetworkObjects topics.

All of these scene management features (and more) are handled by the NetworkSceneManager.

Accessing NetworkSceneManager#

The NetworkSceneManager lives within the NetworkManager and is instantiated when the NetworkManager is started. NetworkSceneManager can be accessed in the following ways:

  • From within a NetworkBehaviour derived component, you can access it by using NetworkManager.SceneManager
  • From anything else that does not already have a reference to NetworkManager, you can access it using NetworkManager.Singleton.SceneManager.

Here are some genral rules about accessing and using NetworkSceneManager:

  • Do not try to access the NetworkSceneManager when the NetworkManager is shutdown (it won't exist).
    • The NetworkSceneManager is only instantiated when a NetworkManager is started.
  • As a server:
    • Any scene you want synchronized with currently connected or late joining clients must be loaded via the NetworkSceneManager
    • If you use the UnityEngine.SceneManagement.SceneManager during a netcode enabled game session, then those scenes will not be synchronized with currently connected or late joining clients.
      • A "late joining client" is a client that joins a game session that has already been started and there are already one or more clients connected
        • All netcode aware objects spawned prior to a late joining client need to be synchronized with the late joining client.
    • The server does not require any clients to be currently connected before it starts loading scenes via the NetworkSceneManager.
    • Any scene loaded via NetworkSceneManager will always default to being synchronized with clients.
      • Scene validation is explained later in this document.
warning

Do not try to access the NetworkSceneManager when the NetworkManager is shutdown. The NetworkSceneManager is only instantiated when a NetworkManager is started. This same rule is true for all Netcode systems that reside within the NetworkManager.

Loading a Scene#

In order to load a scene, there are four requirements:

  1. The NetworkManager instance loading the scene must be a host or server.
  2. The NetworkSceneManager.Load method is used to load the scene.
  3. The scene being loaded must be registered with your project's build settings scenes in build list
  4. A Scene Event cannot already be in progress.

Basic Scene Loading Example#

Imagine that you have an in-scene placed NetworkObject, let's call it "ProjectSceneManager", that handles your project's scene management using the NetworkSceneManager and you wanted your server to load a scene when the ProjectSceneManager is spawned.
In its simplest form, it could look something like:

public class ProjectSceneManager : NetworkBehaviour
{
/// INFO: You could remove the #if UNITY_EDITOR code segment and make SceneName public,
/// but this code assures if the scene name changes you won't have to remember to
/// manually update it.
#if UNITY_EDITOR
public UnityEditor.SceneAsset SceneAsset;
private void OnValidate()
{
if (SceneAsset != null)
{
m_SceneName = SceneAsset.name;
}
}
#endif
[SerializeField]
private string m_SceneName;
public override void OnNetworkSpawn()
{
if (IsServer && !string.IsNullOrEmpty(m_SceneName))
{
var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive);
if (status != SceneEventProgressStatus.Started)
{
Debug.LogWarning($"Failed to load {m_SceneName} " +
$"with a {nameof(SceneEventProgressStatus)}: {status}");
}
}
}
}

In the above code snippet, we have a NetworkBehaviour derived class, ProjectSceneManager, that has a public SceneAsset property (editor only property) that specifies the scene to load. In the OnNetworkSpawn method, we make sure that only the server loads the scene and we compare the SceneEventProgressStatus returned by the NetworkSceneManager.Load method to the SceneEventProgressStatus.Started status. If we received any other status, then typically the name of the status provides you with a reasonable clue as to what the issue might be.

Scene Events and Scene Event Progress#

The term "Scene Event" refers to all subsequent scene events that transpire over time after a server has started a SceneEventType.Load ("load") or SceneEventType.Unload ("unload") Scene Event. For example, a server might load a scene additively while clients are connected. The following high-level overview provides you with a glimpse into the events that transpire during a load Scene Event(it assumes both the server and clients have registered for scene event notifications):

  • Server starts a SceneEventType.Load event
    • The server begins asynchronously loading the scene additively
      • The server will receive a local notification that the scene load event has started
    • Once the scene is loaded, the server spawns any in-scene placed NetworkObjects locally
      • After spawning, the server will send the SceneEventType.Load message to all connected clients along with information about the newly instantiated and spawned NetworkObjects.
      • The server will receive a local SceneEventType.LoadComplete event
        • This only means the server is done loading and spawning NetworkObjects instantiated by the scene being loaded.
  • The clients receive the scene event message for the SceneEventType.Load event and begin processing it.
    • Upon starting the scene event, the clients will receive a local notification for the SceneEventType.Load event.
    • As each client finishes loading the scene, they will generate a SceneEventType.LoadComplete event
      • The client sends this event message to the server.
      • The client is notified locally of this scene event.
        • This notification only means that it has finished loading and synchronizing the scene, but does not mean all other clients have!
  • The server receives the SceneEventType.LoadComplete events from the clients
    • When all SceneEventType.LoadComplete events have been received, the server will generate a SceneEventType.LoadEventCompleted notification
      • This notification is triggered locally on the server
      • The server broadcasts this scene event to the clients
  • Each client receives the SceneEventType.LoadEventCompleted event
    • At this point all clients have completed the SceneEventType.Load event and are synchronized with all newly instantiated and spawned NetworkObjects.

The purpose behind the above outline is to demonstrate that a Scene Event can lead to other scene event types being generated and that the entire sequence of events that transpire occur over a longer period of time than if you were loading a scene in a single player game.

tip

When you receive the SceneEventType.LoadEvenetCompleted or the SceneEventType.SynchronizeComplete you know that the server or clients can start invoking netcode specific code (i.e. sending Rpcs, updating NetworkVariables, etc.). Alternately, NetworkManager.OnClientConnectedCallback is triggered when a client finishes synchronizing and could be used in a similar fashion. However, that only works for the initial client synchronization and not for scene loading or unloading events.

warning

Because the NetworkSceneManager still has additional tasks to complete once a scene is loaded, it is not recommended to use UnityEngine.SceneManagement.SceneManager.sceneLoaded or UnityEngine.SceneManagement.SceneManager.sceneUnloaded as a way to know when a scene event has completed. These two callbacks will occur before NetworkSceneManager has finished the final processing after a scene is loaded or unloaded and you could run into timing related issues. If you are using the netcode integrated scene management, then it is highly recommended to subscribe to the NetworkSceneManager's scene event notifications.

Scene Event Notifications#

You can be notified of scene events by registering in one of two ways:

  1. Receive all scene event notification types: NetworkSceneManager.OnSceneEvent
  2. Receive only a specific scene event notification type: NetworkSceneManager has one for each SceneEventType
    info

    Receiving (via subscribing to the associated event callback) only specific scene event notification types does not change how a server or client receives and processes notifications.

Receiving All Scene Event Notifications Typically, this is used on the server side in order to receive notifications for every scene event notification type for both the server and clients. You can receive all scene event type notifications by subscribing to the NetworkSceneManager.OnSceneEvent callback handler.

Receiving A Specific Scene Event Notification Typically, this is used with clients or components that might only need to be notified of a specific scene event type. There are 9 scene event types and each one has a corresponding "single event type" callback handler in NetworkSceneManager.

As an example: You might want to register for the SceneEventType.LoadEventCompleted scene event type to know, from a client perspective, that the server and all other clients have finished loading a scene. This notification lets you know when you can start performing other netcode related actions on the newly loaded and spawned NetworkObjects.

Scene Event Progress Status#

As we discussed in the earlier code example, it is important to check the status returned by NetworkSceneManager.Load to make sure your scene loading event has started. The following is a list of all SceneEventProgressStatus enum values with some additional helpful information:

  • Started
    • The scene event has started (success)
  • SceneNotLoaded
    • This is returned when you attempt to unload a scene that is not loaded (or no longer is loaded)
  • SceneEventInProgress
    • This is returned if you attempt to start a new scene event (loading or unloading) while one is still in progress
  • InvalidSceneName
    • This can happen for one of the two reasons:
      • The scene name is not spelled correctly or the name of the scene changed
      • The scene name is not in your project's scenes in build list
  • SceneFailedVerification
    • This is returned if you fail scene verification on the server side (more about scene verification later)
  • InternalNetcodeError
    • Currently, this error will only happen if the scene you are trying to unload was never loaded by the NetworkSceneManager.

Unloading a Scene#

Now that we understand the loading process, scene events, and can load a scene additively, the next step is understanding the integrated scene management scene unloading process. In order to unload a scene, here are the requirements:

  1. The NetworkManager instance unloading the scene should have already been started in host or server mode and already loaded least one scene additively.
    1.1 Only additively loaded scenes can be unloaded. 1.2 You cannot unload the currently active scene (see info dialog below)
  2. The NetworkSceneManager.Unload method is used to unload the scene
  3. A Scene Event cannot already be in progress
info

Unloading the currently active scene, in Netcode, is commonly referred to as "scene switching" or loading another scene in LoadSceneMode.Single mode. This will unload all additively loaded scenes and upon the new scene being loaded in LoadSceneMode.Single mode it is set as the active scene and the previous active scene is unloaded.

Basic Scene Unloading Example#

Below we are taking the previous scene loading example, the ProjectSceneManager class, and modifying it to handle unloading. This includes keeping a reference to the SceneEvent.Scene locally in our class because NetworkSceneManager.Unload requires the Scene to be unloaded.

Below is an example of how to:

  • Subscribe the server to NetworkSceneManager.OnSceneEvent notifications.
  • Save a reference to the Scene that was loaded.
  • Unload a scene loaded additively by NetworkSceneManager.
  • Detect the scene event types associated with loading and unloading.
public class ProjectSceneManager : NetworkBehaviour
{
/// INFO: You could remove the #if UNITY_EDITOR code segment and make SceneName public,
/// but this code assures if the scene name changes you won't have to remember to
/// manually update it.
#if UNITY_EDITOR
public UnityEditor.SceneAsset SceneAsset;
private void OnValidate()
{
if (SceneAsset != null)
{
m_SceneName = SceneAsset.name;
}
}
#endif
[SerializeField]
private string m_SceneName;
private Scene m_LoadedScene;
public bool SceneIsLoaded
{
get
{
if (m_LoadedScene.IsValid() && m_LoadedScene.isLoaded)
{
return true;
}
return false;
}
}
public override void OnNetworkSpawn()
{
if (IsServer && !string.IsNullOrEmpty(m_SceneName))
{
NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive);
CheckStatus(status);
}
base.OnNetworkSpawn();
}
private void CheckStatus(SceneEventProgressStatus status, bool isLoading = true)
{
var sceneEventAction = isLoading ? "load" : "unload";
if (status != SceneEventProgressStatus.Started)
{
Debug.LogWarning($"Failed to {sceneEventAction} {m_SceneName} with" +
$" a {nameof(SceneEventProgressStatus)}: {status}");
}
}
/// <summary>
/// Handles processing notifications when subscribed to OnSceneEvent
/// </summary>
/// <param name="sceneEvent">class that contains information about the scene event</param>
private void SceneManager_OnSceneEvent(SceneEvent sceneEvent)
{
var clientOrServer = sceneEvent.ClientId == NetworkManager.ServerClientId ? "server" : "client";
switch (sceneEvent.SceneEventType)
{
case SceneEventType.LoadComplete:
{
// We want to handle this for only the server-side
if (sceneEvent.ClientId == NetworkManager.ServerClientId)
{
// *** IMPORTANT ***
// Keep track of the loaded scene, you need this to unload it
m_LoadedScene = sceneEvent.Scene;
}
Debug.Log($"Loaded the {sceneEvent.SceneName} scene on " +
$"{clientOrServer}-({sceneEvent.ClientId}).");
break;
}
case SceneEventType.UnloadComplete:
{
Debug.Log($"Unloaded the {sceneEvent.SceneName} scene on " +
$"{clientOrServer}-({sceneEvent.ClientId}).");
break;
}
case SceneEventType.LoadEventCompleted:
case SceneEventType.UnloadEventCompleted:
{
var loadUnload = sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted ? "Load" : "Unload";
Debug.Log($"{loadUnload} event completed for the following client " +
$"identifiers:({sceneEvent.ClientsThatCompleted})");
if (sceneEvent.ClientsThatTimedOut.Count > 0)
{
Debug.LogWarning($"{loadUnload} event timed out for the following client " +
$"identifiers:({sceneEvent.ClientsThatTimedOut})");
}
break;
}
}
}
public void UnloadScene()
{
// Assure only the server calls this when the NetworkObject is
// spawned and the scene is loaded.
if (!IsServer || !IsSpawned || !m_LoadedScene.IsValid() || !m_LoadedScene.isLoaded)
{
return;
}
// Unload the scene
var status = NetworkManager.SceneManager.UnloadScene(m_LoadedScene);
CheckStatus(status, false);
}
}

Really, if you take away the debug logging code the major differences are:

  • We store the Scene loaded when the server receives its local SceneEventType.SceneEventType.LoadComplete notification
  • When the ProjectSceneManager.UnloadScene method is invoked (assuming this occurs outside of this class) the ProjectSceneManager.m_LoadedScene is checked to make sure it is a valid scene and that it is indeed loaded before we invoke the NetworkSceneManager.Unload method.

Scene Validation#

Sometimes you might need to prevent the server or client from loading a scene under certain conditions. Here are a few examples of when you might do this:

  • One or more game states determine if a scene is loaded additively
    • Typically, this is done on the server-side.
  • The scene is already pre-loaded on the client
    • Typically, this is done on the client-side.
  • Security purposes
    • As we mentioned earlier, NetworkSceneManager automatically considers all scenes in the build settings scenes in build list valid scenes to be loaded. You might use scene validation to prevent certain scenes from being loaded that could cause some form of security and/or game play issue.
      • It is common to do this on either the server or client side depending upon your requirements/needs.

Usage Example The below example builds upon the previous example's code, and adds some psuedo-code for server-side scene validation:

private bool ServerSideSceneValidation(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
// Comparing against the name or sceneIndex
if (sceneName == "SomeSceneName" || sceneIndex == 3)
{
return false;
}
// Don't allow single mode scene loading (i.e. bootstrap usage patterns might implement this)
if (loadSceneMode == LoadSceneMode.Single)
{
return false;
}
return true;
}
public override void OnNetworkSpawn()
{
if (IsServer && !string.IsNullOrEmpty(m_SceneName))
{
NetworkManager.SceneManager.VerifySceneBeforeLoading = ServerSideSceneValidation;
NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive);
CheckStatus(status);
}
base.OnNetworkSpawn();
}

The callback is the first thing invoked on the server-side when invoking the NetworkSceneManager.Load method. If the scene validation fails, NetworkSceneManager.Load will return SceneEventProgressStatus.SceneFailedVerification and the scene event is cancelled.

caution

Client-Side Scene Validation
This is where you need to be cautious with scene validation, because any scene that you do not validate on the client side should not contain Netcode objects that are considered required dependencies for a connecting client to properly synchronize with the current netcode (game) session state.

What Next?#

We have covered how to access the NetworkSceneManager, how to load and unload a scene, provided a basic overview on scene events and notifications, and even briefly discussed in-scene placed NetworkObjects. You now have the fundamental building-blocks one needs to learn more advanced integrated scene management topics.
We recommend proceeding to the next integrated scene management topic, "Scene Events", in the link below.