[Unity] 使用 Addressables 切换场景,使用一个加载场景作为缓冲,控制显示加载进度条百分比

1,919 阅读2分钟

全用 SceneManager

我以前纯用 SceneManager 的加载场景和卸载场景

很简单……为了赶工

对以下测试脚本,调用 LoadHome()

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Universal;

public class AppController : Singleton<AppController>
{
    private GameUI gameUI;
    
    private void Start()
    {
        DontDestroyOnLoad(gameObject);
    }

    public void LoadHome()
    {
        StartCoroutine(WaitForLoadHome());
    }

    public IEnumerator WaitForLoadHome()
    {
        int oldSceneID = SceneManager.GetActiveScene().buildIndex;
        
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("Scenes/LoadingScene", LoadSceneMode.Additive);

        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        asyncLoad = SceneManager.UnloadSceneAsync(oldSceneID);
        
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        LoadingController.Instance.BeginLoading();
        
        yield return LoadingController.Instance.SetProgress(100);
        
        asyncLoad = SceneManager.LoadSceneAsync("Scenes/HomeScene", LoadSceneMode.Additive);

        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        asyncLoad = SceneManager.UnloadSceneAsync("Scenes/LoadingScene");
        
        while (!asyncLoad.isDone)
        { 
            yield return null;
        }
        
        HomeController.Instance.BeginHome();
    }

    public void LoadGame()
    {
        StartCoroutine(WaitForLoadGame());
    }
    
    private IEnumerator WaitForLoadGame()
    {
        int oldSceneID = SceneManager.GetActiveScene().buildIndex;
        
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("Scenes/LoadingScene", LoadSceneMode.Additive);

        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        asyncLoad = SceneManager.UnloadSceneAsync(oldSceneID);
        
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        LoadingController.Instance.BeginLoading();
        
        yield return LoadingController.Instance.SetProgress(100);
        
        asyncLoad = SceneManager.LoadSceneAsync("Scenes/GameScene", LoadSceneMode.Additive);

        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        asyncLoad = SceneManager.UnloadSceneAsync("Scenes/LoadingScene");
        
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        GameController.Instance.BeginGame();
    }
}

谨慎使用 AsyncOperationHandle<SceneInstance>.WaitForCompletion()

然后换成 Addressables 之后就有问题了……具体的 Debug 代码像这样:

对以下测试脚本,调用 TestLoad()

public string loadingSceneAddress;

private AsyncOperationHandle<SceneInstance> loadHandle;

private IEnumerator TestLoad()
{
    loadHandle = Addressables.LoadSceneAsync(loadingSceneAddress, LoadSceneMode.Additive);
    loadHandle.Completed += OnSceneLoaded;
        
    Debug.Log("=============    1    ================");
    Debug.Log(loadHandle.Status);
    Debug.Log(loadHandle.Result.Scene.isLoaded);
    Debug.Log(SceneManager.sceneCount);
    
    yield return loadHandle.WaitForCompletion();

    Debug.Log("=============    2    ================");
    Debug.Log(loadHandle.Status);
    Debug.Log(loadHandle.Result.Scene.isLoaded);
    Debug.Log(SceneManager.sceneCount);

    yield return new WaitForEndOfFrame();

    Debug.Log("=============    3    ================");
    Debug.Log(loadHandle.Status);
    Debug.Log(loadHandle.Result.Scene.isLoaded);
    Debug.Log(SceneManager.sceneCount);
    
    yield return new WaitForEndOfFrame();

    Debug.Log("=============    5    ================");
    Debug.Log(loadHandle.Status);
    Debug.Log(loadHandle.Result.Scene.isLoaded);
    Debug.Log(SceneManager.sceneCount);
}

private void OnSceneLoaded(AsyncOperationHandle<SceneInstance> obj)
{
    Debug.Log("=============    4    ================");
    Debug.Log(loadHandle.Status);
    Debug.Log(loadHandle.Result.Scene.isLoaded);
    Debug.Log(SceneManager.sceneCount);
}

打印结果:

=============    1    ================

None

False

2

=============    2    ================

None

False

2

=============    3    ================

None

False

2

=============    4    ================

Succeeded

True

2

=============    5    ================

Succeeded

True

2

可以看到他这个 WaitForCompletion() 是完全没有效果的

同时,在 handle 的 WaitForCompletion() 前后,handle 的 Status 也是 None,我看了一下他这个 Status 的赋值,感觉是在引用计数为 0 的时候才赋的 None。那这就说明调用了 Addressables.LoadSceneAsync 之后 StatusNone,可能是因为还不知道是成功或者失败吧

但同时场上却有他这个 Addressables 想要 load 的场景,因为打印出来场景数量是 2,但是这个场景却没有 loaded,就很奇怪,但是既然现在他 Completed 是有效的,那也不管了

改为使用 Addressables

现在就可以实现场景 A 到场景 B 之间的切换有一个加载场景作为缓冲,并且能够显示加载进度条的功能了

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using Universal;

public class AppController : Singleton<AppController>
{
    public string homeSceneAddress;
    public string loadingSceneAddress;
    public string gameSceneAddress;
    
    private void Start()
    {
        DontDestroyOnLoad(gameObject);
    }

    public void LoadHome()
    {
        TryLoad(homeSceneAddress, () => HomeController.Instance.BeginHome());
    }

    public void LoadGame()
    {
        TryLoad(gameSceneAddress, () => GameController.Instance.BeginGame());
    }
    
    private void TryLoad(string nextSceneAddress, Action callback = null)
    {
        AsyncOperationHandle<SceneInstance> lastLoadHandle = Addressables.LoadSceneAsync(loadingSceneAddress, LoadSceneMode.Single);
        lastLoadHandle.Completed += (AsyncOperationHandle<SceneInstance> op) =>
        {
            if(op.Status == AsyncOperationStatus.Succeeded)
            {
                LoadingController.Instance.BeginLoading();
                StartCoroutine(WaitForLoading(lastLoadHandle, nextSceneAddress, callback));
            }
        };
    }
    
    private IEnumerator WaitForLoading(AsyncOperationHandle<SceneInstance> lastLoadHandle, string nextSceneAddress, Action callback = null)
    {
        AsyncOperationHandle<SceneInstance> currLoadHandle = Addressables.LoadSceneAsync(nextSceneAddress, LoadSceneMode.Additive);

        // 新场景加载时刷新进度条
        while (currLoadHandle.Status == AsyncOperationStatus.None)
        {
            LoadingController.Instance.SetPercent(currLoadHandle.PercentComplete);
            yield return null;
        }

        // 如果新场景加载完毕
        if(currLoadHandle.Status == AsyncOperationStatus.Succeeded)
        {
            // 进度条缓冲时间,免得 loading 界面一闪而过,给人晃眼的感觉
            LoadingController.Instance.SetPercent(1f);
            yield return new WaitForSeconds(0.5f);
            
            // 卸载 loading 场景
            Addressables.UnloadSceneAsync(lastLoadHandle)
                .Completed += (AsyncOperationHandle<SceneInstance> op2) =>
            {
                if (op2.Status == AsyncOperationStatus.Succeeded)
                {
                    callback?.Invoke();
                }
            };
        }
        
        yield return null;
    }
}