Skip to content

Addressables Scene Loading support #423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cty41 opened this issue Dec 28, 2020 · 6 comments
Closed

Addressables Scene Loading support #423

cty41 opened this issue Dec 28, 2020 · 6 comments
Labels
type:feature New feature, request or improvement

Comments

@cty41
Copy link

cty41 commented Dec 28, 2020

currently SwitchScene(server) and OnSceneSwitch(client) in NetworkSceneManager directly use SceneManager.LoadSceneAsync,
is there any plan to support addressable load scene?

@cty41 cty41 added the type:feature New feature, request or improvement label Dec 28, 2020
@LukeStampfli LukeStampfli added the stat:awaiting-triage Status - Awaiting triage from the Netcode team. label Mar 2, 2021
@annerajb
Copy link

curious about this. an no update since created.

What is the option / path here? to not use networkscenemanager?

@JesseOlmer JesseOlmer changed the title addressable support Addressables Scene Loading support Aug 10, 2022
@JesseOlmer
Copy link
Contributor

We have compatibility with much of addressable (see `NetworkManager.AddNetworkPrefab), but unfortunately this does not include addressable scene loading. This functionality isn't in our near-term plans for the SDK, but we'd be happy to have a discussion about possible approaches if anyone from the community wants to contribute a solution.

@JesseOlmer JesseOlmer closed this as not planned Won't fix, can't repro, duplicate, stale Aug 10, 2022
@JesseOlmer JesseOlmer removed the stat:awaiting-triage Status - Awaiting triage from the Netcode team. label Aug 10, 2022
@RaphisDev
Copy link

We have compatibility with much of addressable (see `NetworkManager.AddNetworkPrefab), but unfortunately this does not include addressable scene loading. This functionality isn't in our near-term plans for the SDK, but we'd be happy to have a discussion about possible approaches if anyone from the community wants to contribute a solution.

So is there actually a way to do that?

@WhippetsAintDogs
Copy link

WhippetsAintDogs commented Aug 10, 2023

Yes there is @NatureRaph ! We allowed extension of the NetworkSceneManager in our fork of NGO 1.1: fork

Have a look at these files in the commit:

  • ISceneManagerHandler.cs
  • NetworkSceneManager.cs
  • SceneEventProgress.cs

And implemented the addressable scene loading in our codebase like this:

AddressablesSceneManagerHandler.cs

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

public class AddressablesSceneManagerHandler : ISceneManagerHandler
{
    private readonly Dictionary<string, AsyncOperationHandle<SceneInstance>> loadOperationHandles = new Dictionary<string, AsyncOperationHandle<SceneInstance>>();

    public AddressablesSceneManagerHandler() => SceneManager.sceneLoaded += RemoveOperationHandle;

    ~AddressablesSceneManagerHandler() => SceneManager.sceneLoaded -= RemoveOperationHandle;

    private void RemoveOperationHandle(Scene scene, LoadSceneMode loadSceneMode)
    {
        if (loadSceneMode == LoadSceneMode.Single)
        {
            loadOperationHandles.Remove(scene.name);
        }
    }

    public AsyncOperationHandle<SceneInstance> PreloadSceneAsync(string sceneName, LoadSceneMode loadSceneMode) =>
        loadOperationHandles[sceneName] = Addressables.LoadSceneAsync(sceneName, loadSceneMode, activateOnLoad: false);

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
    {
        var activated = false;
        var hook = sceneEventProgress.GetAsyncOperationCompletionHook(check: () => activated);

        LoadSceneAsync(sceneName, loadSceneMode, onActivated: () =>
        {
            activated = true;
            hook?.Invoke();
        });

        return null;
    }

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, Action onActivated = null)
    {
        if (!loadOperationHandles.TryGetValue(sceneName, out var loadOperationHandle))
        {
            loadOperationHandle = PreloadSceneAsync(sceneName, loadSceneMode);
        }

        loadOperationHandle.Completed += operationHandle =>
            operationHandle.Result.ActivateAsync().completed += _ => onActivated?.Invoke();

        return null;
    }

    public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
    {
        if (loadOperationHandles.TryGetValue(scene.name, out var loadOperationHandle))
        {
            var unloadOperationHandle = Addressables.UnloadSceneAsync(loadOperationHandle);

            unloadOperationHandle.Completed += _ =>
                sceneEventProgress.GetAsyncOperationCompletionHook(check: () => unloadOperationHandle.IsDone).Invoke();

            loadOperationHandles.Remove(scene.name);
        }

        return null;
    }
}

You can register your addressable scenes this way:

    /// <summary>
    /// Should be executed immediately after starting the NetworkManager as Server, Host or Client.
    /// </summary>
    public IEnumerator SetupAddressableSceneLoading()
    {
        var sceneManager = NetworkManager.SceneManager;
        sceneManager.SceneManagerHandler = new AddressablesSceneManagerHandler();

        var operationHandle = Addressables.LoadResourceLocationsAsync(ADDRESSABLES_SCENES_LABEL);

        yield return operationHandle;

        if (operationHandle.Status == AsyncOperationStatus.Succeeded)
        {
            sceneManager.RegisterExternalScenes(
                operationHandle.Result.Select(location => location.InternalId).ToArray());
        }

        Addressables.Release(operationHandle);
    }

P.S. With this AddressablesSceneManagerHandler, you can even do scene preloading on the client-side by sending an RPC to the client prior to doing a networked scene loading on the host/server that will call PreloadSceneAsync. This will effectively parallelize the addressable loading and only sequence the activation of the scenes resulting in a total scene loading time of 1.1x a non-networked scene loading (instead of the usual 2x time due to the host/server normally loading its scene completely before asking the clients to do so).

P.P.S. The addressable keys/names of the scenes have to be the same as their scene name and their Internal Asset Naming Mode must be set to Full Path, like this:
image
image

Hope it helps! 😄

@RaphisDev
Copy link

RaphisDev commented Aug 10, 2023

Yes there is @NatureRaph ! We allowed extension of the NetworkSceneManager in our fork of NGO 1.1: fork

Have a look at these files in the commit:

  • ISceneManagerHandler.cs
  • NetworkSceneManager.cs
  • SceneEventProgress.cs

And implemented the addressable scene loading in our codebase like this:

AddressablesSceneManagerHandler.cs

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

public class AddressablesSceneManagerHandler : ISceneManagerHandler
{
    private readonly Dictionary<string, AsyncOperationHandle<SceneInstance>> loadOperationHandles = new Dictionary<string, AsyncOperationHandle<SceneInstance>>();

    public AddressablesSceneManagerHandler() => SceneManager.sceneLoaded += RemoveOperationHandle;

    ~AddressablesSceneManagerHandler() => SceneManager.sceneLoaded -= RemoveOperationHandle;

    private void RemoveOperationHandle(Scene scene, LoadSceneMode loadSceneMode)
    {
        if (loadSceneMode == LoadSceneMode.Single)
        {
            loadOperationHandles.Remove(scene.name);
        }
    }

    public AsyncOperationHandle<SceneInstance> PreloadSceneAsync(string sceneName, LoadSceneMode loadSceneMode) =>
        loadOperationHandles[sceneName] = Addressables.LoadSceneAsync(sceneName, loadSceneMode, activateOnLoad: false);

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
    {
        var activated = false;
        var hook = sceneEventProgress.GetAsyncOperationCompletionHook(check: () => activated);

        LoadSceneAsync(sceneName, loadSceneMode, onActivated: () =>
        {
            activated = true;
            hook?.Invoke();
        });

        return null;
    }

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, Action onActivated = null)
    {
        if (!loadOperationHandles.TryGetValue(sceneName, out var loadOperationHandle))
        {
            loadOperationHandle = PreloadSceneAsync(sceneName, loadSceneMode);
        }

        loadOperationHandle.Completed += operationHandle =>
            operationHandle.Result.ActivateAsync().completed += _ => onActivated?.Invoke();

        return null;
    }

    public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
    {
        if (loadOperationHandles.TryGetValue(scene.name, out var loadOperationHandle))
        {
            var unloadOperationHandle = Addressables.UnloadSceneAsync(loadOperationHandle);

            unloadOperationHandle.Completed += _ =>
                sceneEventProgress.GetAsyncOperationCompletionHook(check: () => unloadOperationHandle.IsDone).Invoke();

            loadOperationHandles.Remove(scene.name);
        }

        return null;
    }
}

You can register your addressable scenes this way:

    /// <summary>
    /// Should be executed immediately after starting the NetworkManager as Server, Host or Client.
    /// </summary>
    public IEnumerator SetupAddressableSceneLoading()
    {
        var sceneManager = NetworkManager.SceneManager;
        sceneManager.SceneManagerHandler = new AddressablesSceneManagerHandler();

        var operationHandle = Addressables.LoadResourceLocationsAsync(ADDRESSABLES_SCENES_LABEL);

        yield return operationHandle;

        if (operationHandle.Status == AsyncOperationStatus.Succeeded)
        {
            sceneManager.RegisterExternalScenes(
                operationHandle.Result.Select(location => location.InternalId).ToArray());
        }

        Addressables.Release(operationHandle);
    }

P.S. With this AddressablesSceneManagerHandler, you can even do scene preloading on the client-side by sending an RPC to the client prior to doing a networked scene loading on the host/server that will call PreloadSceneAsync. This will effectively parallelize the addressable loading and only sequence the activation of the scenes resulting in a total scene loading time of 1.1x a non-networked scene loading (instead of the usual 2x time due to the host/server normally loading its scene completely before asking the clients to do).

P.P.S. The addressable keys/names of the scenes have to be the same as their scene name and their Internal Asset Naming Mode must be set to Full Path, like this: image image

Hope it helps! 😄

Thank you very much

@RaphisDev
Copy link

RaphisDev commented Aug 10, 2023

Yes there is @NatureRaph ! We allowed extension of the NetworkSceneManager in our fork of NGO 1.1: fork

Have a look at these files in the commit:

  • ISceneManagerHandler.cs
  • NetworkSceneManager.cs
  • SceneEventProgress.cs

And implemented the addressable scene loading in our codebase like this:

AddressablesSceneManagerHandler.cs

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

public class AddressablesSceneManagerHandler : ISceneManagerHandler
{
    private readonly Dictionary<string, AsyncOperationHandle<SceneInstance>> loadOperationHandles = new Dictionary<string, AsyncOperationHandle<SceneInstance>>();

    public AddressablesSceneManagerHandler() => SceneManager.sceneLoaded += RemoveOperationHandle;

    ~AddressablesSceneManagerHandler() => SceneManager.sceneLoaded -= RemoveOperationHandle;

    private void RemoveOperationHandle(Scene scene, LoadSceneMode loadSceneMode)
    {
        if (loadSceneMode == LoadSceneMode.Single)
        {
            loadOperationHandles.Remove(scene.name);
        }
    }

    public AsyncOperationHandle<SceneInstance> PreloadSceneAsync(string sceneName, LoadSceneMode loadSceneMode) =>
        loadOperationHandles[sceneName] = Addressables.LoadSceneAsync(sceneName, loadSceneMode, activateOnLoad: false);

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
    {
        var activated = false;
        var hook = sceneEventProgress.GetAsyncOperationCompletionHook(check: () => activated);

        LoadSceneAsync(sceneName, loadSceneMode, onActivated: () =>
        {
            activated = true;
            hook?.Invoke();
        });

        return null;
    }

    public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, Action onActivated = null)
    {
        if (!loadOperationHandles.TryGetValue(sceneName, out var loadOperationHandle))
        {
            loadOperationHandle = PreloadSceneAsync(sceneName, loadSceneMode);
        }

        loadOperationHandle.Completed += operationHandle =>
            operationHandle.Result.ActivateAsync().completed += _ => onActivated?.Invoke();

        return null;
    }

    public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
    {
        if (loadOperationHandles.TryGetValue(scene.name, out var loadOperationHandle))
        {
            var unloadOperationHandle = Addressables.UnloadSceneAsync(loadOperationHandle);

            unloadOperationHandle.Completed += _ =>
                sceneEventProgress.GetAsyncOperationCompletionHook(check: () => unloadOperationHandle.IsDone).Invoke();

            loadOperationHandles.Remove(scene.name);
        }

        return null;
    }
}

You can register your addressable scenes this way:

    /// <summary>
    /// Should be executed immediately after starting the NetworkManager as Server, Host or Client.
    /// </summary>
    public IEnumerator SetupAddressableSceneLoading()
    {
        var sceneManager = NetworkManager.SceneManager;
        sceneManager.SceneManagerHandler = new AddressablesSceneManagerHandler();

        var operationHandle = Addressables.LoadResourceLocationsAsync(ADDRESSABLES_SCENES_LABEL);

        yield return operationHandle;

        if (operationHandle.Status == AsyncOperationStatus.Succeeded)
        {
            sceneManager.RegisterExternalScenes(
                operationHandle.Result.Select(location => location.InternalId).ToArray());
        }

        Addressables.Release(operationHandle);
    }

P.S. With this AddressablesSceneManagerHandler, you can even do scene preloading on the client-side by sending an RPC to the client prior to doing a networked scene loading on the host/server that will call PreloadSceneAsync. This will effectively parallelize the addressable loading and only sequence the activation of the scenes resulting in a total scene loading time of 1.1x a non-networked scene loading (instead of the usual 2x time due to the host/server normally loading its scene completely before asking the clients to do so).

P.P.S. The addressable keys/names of the scenes have to be the same as their scene name and their Internal Asset Naming Mode must be set to Full Path, like this: image image

Hope it helps! 😄

So if I unterstand correctly, I have to download those scripts and add them, right? But the NetworkSceneManager has 314 Errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature New feature, request or improvement
Projects
None yet
Development

No branches or pull requests

6 participants