Skip to content

Instantly share code, notes, and snippets.

@luke161
Last active March 29, 2024 12:02
Show Gist options
  • Save luke161/ed99d2db61c6048cfc9bce5cd9abaa4e to your computer and use it in GitHub Desktop.
Save luke161/ed99d2db61c6048cfc9bce5cd9abaa4e to your computer and use it in GitHub Desktop.
Handles loading and unloading of Scenes using Unity's Addressable Assets system. Adds support for using LoadSceneParameters when loading a scene, which is currently missing in the official APIs.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
public class SceneLoadData
{
public Scene scene;
public AsyncOperation operation;
}
public static class AddressablesSceneLoader
{
public static AsyncOperationHandle<SceneLoadData> LoadSceneAsync(object key, LoadSceneParameters parameters, bool allowSceneActivation = true, int priority = 0)
{
IResourceLocation location = GetLocation(key);
AsyncOperationHandle dependencies = Addressables.DownloadDependenciesAsync(location.Dependencies, false);
SceneLoadOperation operation = new SceneLoadOperation(Addressables.ResourceManager);
operation.Init(location, parameters, allowSceneActivation, priority, dependencies);
return Addressables.ResourceManager.StartOperation<SceneLoadData>(operation, dependencies);
}
public static AsyncOperationHandle UnloadSceneAsync(AsyncOperationHandle<SceneLoadData> sceneLoadHandle)
{
UnloadSceneOperation unloadOp = new UnloadSceneOperation();
unloadOp.Init(sceneLoadHandle);
return Addressables.ResourceManager.StartOperation(unloadOp, sceneLoadHandle);
}
private static IResourceLocation GetLocation(object key)
{
foreach (IResourceLocator locator in Addressables.ResourceLocators)
{
IList<IResourceLocation> locations = new List<IResourceLocation>();
bool success = locator.Locate(key, typeof(SceneInstance), out locations);
if (success)
{
return locations[0];
}
}
return null;
}
private sealed class SceneLoadOperation : AsyncOperationBase<SceneLoadData>, IUpdateReceiver
{
private SceneLoadData _scene;
private AsyncOperationHandle _dependencies;
private IResourceLocation _location;
private ResourceManager _resourceManager;
private LoadSceneParameters _loadParameters;
private bool _allowSceneActivation;
private int _priority;
public SceneLoadOperation(ResourceManager manager)
{
_resourceManager = manager;
}
public void Init(IResourceLocation location, LoadSceneParameters parameters, bool allowSceneActivation, int priority, AsyncOperationHandle dependencies)
{
_dependencies = dependencies;
// Line found in the SceneProvider.cs source but Acquire() call is internal only?
//if (_dependencies.IsValid())
// _dependencies.Acquire();
_location = location;
_loadParameters = parameters;
_allowSceneActivation = allowSceneActivation;
_priority = priority;
}
protected override void Destroy()
{
if (_dependencies.IsValid()) Addressables.Release(_dependencies);
base.Destroy();
}
protected override void GetDependencies(List<AsyncOperationHandle> deps)
{
if (_dependencies.IsValid()) deps.Add(_dependencies);
}
protected override string DebugName { get { return string.Format("Scene({0})", _location == null ? "Invalid" : _resourceManager.TransformInternalId(_location)); } }
protected override void Execute()
{
var loadingFromBundle = false;
if (_dependencies.IsValid())
{
IList list = _dependencies.Result as IList;
foreach (var d in list)
{
var abResource = d as IAssetBundleResource;
if (abResource != null && abResource.GetAssetBundle() != null)
loadingFromBundle = true;
}
}
_scene = InternalLoadScene(_location, loadingFromBundle, _allowSceneActivation, _priority);
((IUpdateReceiver)this).Update(0.0f);
}
void IUpdateReceiver.Update(float unscaledDeltaTime)
{
if (_scene.operation.isDone || (!_allowSceneActivation && Mathf.Approximately(_scene.operation.progress, 0.9f)))
Complete(_scene, true, null);
}
private SceneLoadData InternalLoadScene(IResourceLocation location, bool loadingFromBundle, bool allowSceneActivation, int priority)
{
string internalId = _resourceManager.TransformInternalId(location);
AsyncOperation op = InternalLoad(internalId, loadingFromBundle, _loadParameters);
op.allowSceneActivation = allowSceneActivation;
op.priority = priority;
return new SceneLoadData() { operation = op, scene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1) };
}
private AsyncOperation InternalLoad(string path, bool loadingFromBundle, LoadSceneParameters parameters)
{
#if !UNITY_EDITOR
return SceneManager.LoadSceneAsync(path, parameters);
#else
if (loadingFromBundle)
return SceneManager.LoadSceneAsync(path, parameters);
else
{
if (!path.ToLower().StartsWith("assets/") && !path.ToLower().StartsWith("packages/"))
path = "Assets/" + path;
if (path.LastIndexOf(".unity") == -1)
path += ".unity";
return UnityEditor.SceneManagement.EditorSceneManager.LoadSceneAsyncInPlayMode(path, parameters);
}
#endif
}
}
private sealed class UnloadSceneOperation : AsyncOperationBase<SceneLoadData>
{
private SceneLoadData _instance;
private AsyncOperationHandle<SceneLoadData> _sceneLoadHandle;
public void Init(AsyncOperationHandle<SceneLoadData> sceneLoadHandle)
{
// Line found in the SceneProvider.cs source but ReferenceCount is internal only?
//if (sceneLoadHandle.ReferenceCount > 0)
{
_sceneLoadHandle = sceneLoadHandle;
_instance = _sceneLoadHandle.Result;
}
}
protected override void Execute()
{
if (_sceneLoadHandle.IsValid() && _instance.scene.isLoaded)
{
var unloadOp = SceneManager.UnloadSceneAsync(_instance.scene);
if (unloadOp == null)
UnloadSceneCompleted(null);
else
unloadOp.completed += UnloadSceneCompleted;
}
else
UnloadSceneCompleted(null);
}
private void UnloadSceneCompleted(AsyncOperation obj)
{
if (_sceneLoadHandle.IsValid())
Addressables.Release(_sceneLoadHandle);
Complete(_instance, true, "");
}
}
}
@Ariel-Feldman
Copy link

Ariel-Feldman commented Sep 21, 2022

Dude, this is great work! thanks for sharing! I agree with @Yurii-Orlov, it's kind of weird that eventually, you used SceneManager.LoadSceneAsync and not the Addressables.LoadSceneAsync, also (and I'm not sure yet) it feels like a bit overcooked:) when you reach the AsyncOperationBase class and use the Conditional Compilation ifs, I think Unity already did that work and we can use it more simply, again not sure yet, but ill update once I get my version of it, anyway great work! learned a lot from it!

@luke161
Copy link
Author

luke161 commented Sep 24, 2022

@Ariel-Feldman this is basically the source code for Addressables.LoadSceneAsync, it mirrors what that API is doing under the hood (it eventually calls SceneManager itself). The main goal here was to add support for LoadSceneParameters, as there wasn't an API for that in the Addressables package at the time, but this has since been updated :) https://docs.unity3d.com/Packages/com.unity.addressables@1.20/api/UnityEngine.AddressableAssets.Addressables.LoadSceneAsync.html#UnityEngine_AddressableAssets_Addressables_LoadSceneAsync_System_Object_UnityEngine_SceneManagement_LoadSceneParameters_System_Boolean_System_Int32_

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment