Help adapting my additive Scene Management system to VR Multiplayer Template

Hello there!

I’d like to ear some feedback to better learn what is the right approach to implement what I want to do.
I’ve been working on a XR application for Meta Quest 3 that let the user visit some virtual environments. Recently, I was asked to implement multi-user features in this prototype, so I migrated my project into a new one having VR Multiplayer Template as a base.

The app flow is currently managed by a custom SceneManager I wrote that handles additive scene loading. There is one bootstrap startup scene with mostly permanent GameObjects (and DDoL ones), including this custom SceneManager and the XR Origin. At start the SceneManager loads the first scene additively, which is an AR scene selector menu for the user to choose the environment he wants to visit. When the user enter an environment, the SceneManager unloads the AR scene menu and loads the chosen environment. Same (reversed) when the user exits it.

public class MySceneManager : MonoBehaviourSingleton<MySceneManager>
{
    [SerializeField] private string firstSceneToLoad;
    [SerializeField] private FadeController fadeController;
    [SerializeField] private float fadeDuration;

    private int _currentSceneLoaded;

    public override void Awake()
    {
        base.Awake();

        fadeController.SetAlpha(0);
        LoadScene(firstSceneToLoad, false);
    }

    public void LoadScene(string sceneName, bool fade = true)
    {
        StartCoroutine(LoadSceneCoroutine(sceneName, fade));
    }

    private IEnumerator LoadSceneCoroutine(string newScene, bool fade)
    {
        if(fade)
        {
           yield return fadeController.FadeCoroutine(fadeDuration, FadeController.Direction.Out);
        }

        if (_currentSceneLoaded != 0)
        {
            yield return SceneManager.UnloadSceneAsync(_currentSceneLoaded);
            Debug.Log($"[{GetType().Name}] Scene {_currentSceneLoaded} unloaded");
        }

        yield return SceneManager.LoadSceneAsync(newScene, LoadSceneMode.Additive);
        Debug.Log($"[{GetType().Name}] Scene {newScene} loaded");
        var scene = SceneManager.GetSceneByName(newScene);
        _currentSceneLoaded = scene.buildIndex;
        SceneManager.SetActiveScene(scene);

        if (fade)
        {
            yield return fadeController.FadeCoroutine(fadeDuration, FadeController.Direction.In);
        }
    }
}

Now it comes the multiplayer part. I want that each user can be able to start a new lobby as well as joining one in any time, regardless of what scene has loaded locally. A client who joins a lobby should log in the same scene of the host. If the host leaves the current scene, the lobby gets closed and all clients should be disconnected. If a client leaves the current scene, he gets disconnected by the lobby.

How should I rethink my scene management system? I was trying to refactor my SceneManager in order to use NetworkSceneManager, but NetworkSceneManager is not available until a network session is actually instantiated (so when the host starts a lobby), so I can’t handle scene management when offline.
Is there a way to achieve this result using already provided NGO Scene Management? Or do I need to create a custom system?

Any feedback or help is appreciated! Thanks!

Hey there, thanks for checking out the VR Multiplayer Template!

TL;DR you will most likely need to create a custom scene manager that handles both setting the active multiplayer scene (when online), and the non multiplayer scenes (when offline).

I recently wired up networked scene switching in a personal project. The main thing is to make sure of is that the proper items are marked as DDoL (which it sounds like you are doing for some objects, but maybe not all?).

I would definitely recommended using the NetworkSceneManager since it will auto sync the active scene determined by the host of the room. Because this process is automatic, you just need the host to set that active scene, then when new clients join they will automatically load into the hosts scene.

You can have your scene manager work when not online by having specific loading functions when offline vs online, and choose whichever one you want respectively. That is at least how I’m doing it in my personal project.

1 Like

Thanks for the feedback!

I should probably need to better understand where and when to use my offline scene manager and when to switch logic and use NetworkSceneManager, since it can only be used when the connection is enabled, but in my case, for now I do not have to load or unload scenes online, as the lobby should live in the scene it’s created. For now, I’ll try to explain the problem I’m facing.

Using the MySceneManager component I shared above, given the same scene architecture I explained, this is what happens if I let the flag Enable Scene Management to on in the NetworkManagerVRMultiplayer .

Host:

  1. Bootstrap Scene loaded → AR Scene Menu loaded
  2. Selects Test Scene → AR Scene Menu unloaded → Test Scene loaded
  3. Creates “Player” lobby → Connection started

Client:

  1. Bootstrap Scene loaded → AR Scene Menu loaded
  2. Selects Test Scene → AR Scene Menu unloaded → Test Scene loaded
  3. Joins “Player” lobby → Connection enstablished
  4. Bootstrap Scene loaded (another) → AR Scene Menu loaded

So at the end of this flow, the host has 2 scenes loaded, Bootstrap and Test Scene, but the client reloads all the previous scenes when joins the lobby, so it has 4 scenes loaded: Bootstrap duplicated, Test Scene and AR Scene Menu.

If I let Enable Scene Management to off, once the client joins the lobby that kind of scene overloading does not happen, but the NetworkGameManager debug console shows me some strange behaviour:

The players can see each other moves, but it seems some errors occurs while correctly joining the client to the lobby. It also doesn’t connect properly to the voice chat channel (in the audio pannel it shows “Logging in to Voice Service”), while the host does.

(All these tests are made using ParrelSync)

It’s clear that something is not handled properly both with scene management enabled and disabled. I’m just trying to better understand what is the correct approach to follow. Given an example of app flow like I described before, can you give me some tips based on your experience with your personal project?

Thanks again for your patience!

UPDATE

After digging a bit I have found this thread in which thanks to NoelStephans_Unity I discovered Additive Client Synchronization Mode.

Keeping the Enable Scene Management toggle on and adding this to MySceneManager:

public override void Awake()
{
    base.Awake();

    NetworkManager.Singleton.OnServerStarted += OnServerStarted;

    fadeController.SetAlpha(0);
    LoadScene(firstSceneToLoad, false);
}

private void OnServerStarted()
{
    // This setting tells clients to re-use scenes that are already loaded.
    NetworkManager.Singleton.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive);
}
// Rest of the class

seems to be the right approach to follow. I still have to manage some lobby disconnection logic related to when players want to exit the shared scene, but for now this setting helped me a lot.

1 Like