Last active
March 29, 2024 12:02
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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/[email protected]/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
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!