Hi, I’ve been trying to convert my local multiplayer game to an online multiplayer experience using NGO, but I’m having trouble understanding how the scene validation on the NetworkSceneManager works…
To start things off, my game uses a multi-scene structure, so I need to load multiple scenes additively for my game to work. I also have a bootstrap scene that contains a bunch of managers that my game needs (including the NetworkManager) that is always loaded.
From my understanding, scenes that were already loaded before starting the server will be also loaded on the clients when the client connects, and any scene loaded by the server using the NetworkSceneManager after that is also loaded on all clients.
Now, reading through the Scene Validation section of the documentation, one of the examples says you can use Scene Validation to check is a scene is already pre-loaded on the client, thus avoiding duplication.
But here’s my problem: The server works as expected, but on the clients, my pre-loaded scenes are being unloaded! And I don’t want that, I want to keep the pre-loaded scenes!
Here’s a simplified version of the code I’m using:
Code
public class MultiSceneManager : MonoBehavior
{
private NetworkManager NetworkManager => NetworkManager.Singleton;
private void Awake()
{
NetworkManager.OnServerStarted += OnServerStarted;
NetworkManager.OnServerStopped += OnServerStopped;
NetworkManager.OnClientStarted += OnClientStarted;
NetworkManager.OnClientStopped += OnClientStopped;
}
private void OnDestroy()
{
if(GameManager.IsQuitting)
return;
NetworkManager.OnServerStarted -= OnServerStarted;
NetworkManager.OnServerStopped -= OnServerStopped;
NetworkManager.OnClientStarted -= OnClientStarted;
NetworkManager.OnClientStopped -= OnClientStopped;
}
private void OnServerStarted()
{
//The Scene manager only exists when we start a connection
NetworkManager.SceneManager.OnSceneEvent += OnNetworkSceneEvent;
NetworkManager.SceneManager.VerifySceneBeforeLoading = NetworkServerSceneValidation;
}
private void OnServerStopped(bool isHost)
{
if(GameManager.IsQuitting)
return;
NetworkManager.SceneManager.OnSceneEvent -= OnNetworkSceneEvent;
}
private void OnClientStarted()
{
if(NetworkManager.IsServer)
return;
//The Scene manager only exists when we start a connection
NetworkManager.SceneManager.OnSceneEvent += OnNetworkSceneEvent;
NetworkManager.SceneManager.VerifySceneBeforeLoading = NetworkClientSceneValidation;
}
private void OnClientStopped(bool isHost)
{
if(NetworkManager.IsServer)
return;
NetworkManager.SceneManager.OnSceneEvent -= OnNetworkSceneEvent;
}
private bool NetworkServerSceneValidation(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
return true;
}
private bool NetworkClientSceneValidation(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
Debug.Log($"===> [CLIENT] Trying to load scene {sceneName}");
//Don't not load scenes that are already loaded
var scene = SceneManager.GetSceneByBuildIndex(sceneIndex);
if (scene.isLoaded)
{
Debug.LogWarning($"===> [CLIENT] Scene {sceneName} is loaded! Thus invalid");
return false;
}
Debug.Log($"===> [CLIENT] Scene {sceneName} is not loaded, thus valid");
return true;
}
public void LoadScene(int sceneBuildIndex, LoadSceneMode loadSceneMode)
{
//Only the server can load/unload scenes
if(!(NetworkManager.IsServer || NetworkManager.IsHost))
return;
var sceneName = GetSceneNameByBuildIndex(sceneBuildIndex);
var status = NetworkManager.SceneManager.LoadScene(sceneName, loadSceneMode);
if (status != SceneEventProgressStatus.Started)
{
Debug.LogError($"Failed to load {sceneName} with a {nameof(SceneEventProgressStatus)}: {status}");
return;
}
}
public void UnloadSceneUsingSceneManager(int sceneBuildIndex)
{
//Only the server can load/unload scenes
if(!(NetworkManager.IsServer || NetworkManager.IsHost))
return;
var sceneName = GetSceneNameByBuildIndex(sceneBuildIndex);
var scene = GetSceneByName(sceneName);
var status = NetworkManager.SceneManager.UnloadScene(scene);
if (status != SceneEventProgressStatus.Started)
{
Debug.LogError($"Failed to unload {sceneName} with a {nameof(SceneEventProgressStatus)}: {status}");
return;
}
}
public string GetSceneNameByBuildIndex(int buildIndex)
{
var scenePath = SceneUtility.GetScenePathByBuildIndex(buildIndex);
var sceneName = Path.GetFileNameWithoutExtension(scenePath);
return sceneName;
}
public Scene GetSceneByName(string sceneName)
{
return SceneManager.GetSceneByName(sceneName);
}
private void OnNetworkSceneEvent(SceneEvent sceneEvent)
{
switch (sceneEvent.SceneEventType)
{
case SceneEventType.Load:
if (sceneEvent.ClientId == NetworkManager.LocalClientId)
{
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} loading started");
_networkOperation = sceneEvent.AsyncOperation;
}
break;
case SceneEventType.Unload:
if (sceneEvent.ClientId == NetworkManager.LocalClientId)
{
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} unloading started");
_networkOperation = sceneEvent.AsyncOperation;
}
break;
case SceneEventType.LoadEventCompleted:
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} loaded");
break;
case SceneEventType.UnloadEventCompleted:
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} unloaded");
break;
case SceneEventType.LoadComplete:
if (sceneEvent.ClientId == NetworkManager.LocalClientId)
{
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} loading completed");
_networkOperation = null;
}
break;
case SceneEventType.UnloadComplete:
if (sceneEvent.ClientId == NetworkManager.LocalClientId)
{
Debug.Log($"===> NetworkScene {sceneEvent.SceneName} unloading completed");
_networkOperation = null;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
I’ve tried mixing and matching who does the validation, but nothing I did worked:
- If I return true on both validations, existing scenes gets duplicated for a second, and the everything gets unloaded EXCEPT the ones loaded after the server started;
- If I return true on the server but do the validation in the client (like in the code above), Apparently I don’t get the duplication, but all scenes are unloaded except for the ones that were loaded after the server started;
- If I do the validation on the server but return true on the client, nothing get loaded nor unloaded, so basically nothing changes;
- If I do the validation on both sides, again nothing gets loaded nor unloaded.
So, what am I doing wrong?