Unity 通用场景管理器分享

作者:LouisLiu
2019-07-31
17 20 1

前言

场景切换是每一个游戏都会遇到的功能需求,当需要加载的场景较大时,同步加载会造成界面卡住很久再进入下一个场景的问题,导致很差的玩家体验。

笔者通过对网上分享的思路进行优化,完成了方便的全局场景管理器,通过异步加载场景解决卡顿问题。

基本思路

Unity 通过以下函数提供给开发者场景异步加载功能:


我们可以通过开启一个协程调用异步加载函数实现异步加载场景功能。

以下代码展示了基础功能的实现方式:

public string nextSceneName;

private void Start() 

{
    //开启协程
    StartCoroutine("LoadScene");
}

IEnumerator LoadScene() 
{
    //异步加载场景
    async = SceneManager.LoadSceneAsync(nextSceneName);
    yield return null;
}

实现

项目中我们通过调用 SceneManager 完成场景管理的全部功能,以下为代码实现:

/**********************************************
* @file SceneManager.cs
* @brief 场景管理器
* @author LouisLiu
* @date 2019/07/22
* @note 修改说明
**********************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SceneManager : MonoBehaviour
{
  private static SceneManager m_instance;  // 私有单例

  private Action m_onSceneLoaded = null;   // 场景加载完成回调

  private string m_strNextSceneName = null;  // 将要加载的场景名
  private string m_strCurSceneName = null;   // 当前场景名,如若没有场景,则默认返回 Login
  private string m_strPreSceneName = null;   // 上一个场景名

  private bool m_bLoading = false;     // 是否正在加载中

  private bool m_bDestroyAuto = true;  // 自动删除 loading 背景

  private const string m_strLoadSceneName = "LoadingScene";  // 加载场景名字
  private GameObject m_objLoadProgress = null;               // 加载进度显示对象

  //获取当前场景名
  public static string s_strLoadedSceneName => m_instance.m_strCurSceneName;

  public static void CreateInstance(GameObject go)
  {
    if (null != m_instance)
    {
      return;
    }
    m_instance = go.AddComponent<SceneManager>();
    DontDestroyOnLoad(m_instance);
    m_instance.m_strCurSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
  }

  public static void LoadPreScene()
  {
    if (string.IsNullOrEmpty(m_instance.m_strPreSceneName))
    {
      return;
    }
    LoadScene(m_instance.m_strPreSceneName);
  }

  public static void LoadScene(string strLevelName)
  {
    m_instance.LoadLevel(strLevelName, null);
  }

  public static void LoadScene(string strLevelName, Action onSecenLoaded)
  {
    m_instance.LoadLevel(strLevelName, onSecenLoaded);
  }

  private void LoadLevel(string strLevelName, Action onSecenLoaded, bool isDestroyAuto = true)
  {
    if (m_bLoading || m_strCurSceneName == strLevelName)
    {
      return;
    }

    m_bLoading = true;  // 锁屏
    // *开始加载    
    m_onSceneLoaded = onSecenLoaded;
    m_strNextSceneName = strLevelName;
    m_strPreSceneName = m_strCurSceneName;
    m_strCurSceneName = m_strLoadSceneName;
    m_bDestroyAuto = isDestroyAuto;

    //先异步加载 Loading 界面
    StartCoroutine(StartLoadSceneOnEditor(m_strLoadSceneName, OnLoadingSceneLoaded, null));
  }

  /**************************************
  * @fn OnLoadingSceneLoaded
  * @brief 过渡场景加载完成回调
  * @return void
  **************************************/
  private void OnLoadingSceneLoaded()
  {
    // 过渡场景加载完成后加载下一个场景
    StartCoroutine(StartLoadSceneOnEditor(m_strNextSceneName, OnNextSceneLoaded, OnNextSceneProgress));
  }

  /**************************************
  * @fn StartLoadSceneOnEditor
  * @brief 开始加载
  * @param[in] string strLevelName
  * @param[in] Action OnSecenLoaded  场景加载完成后回调
  * @param[in] Action OnSceneProgress
  * @return System.Collections.Generic.IEnumerator
  **************************************/
  private IEnumerator StartLoadSceneOnEditor(string strLevelName, Action OnSecenLoaded, Action<float> OnSceneProgress)
  {
    AsyncOperation async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(strLevelName);
    if (null == async)
    {
      yield break;
    }

    //*加载进度
    while (!async.isDone)
    {
      float fProgressValue;
      if (async.progress < 0.9f)
      {
        fProgressValue = async.progress;
      }
      else
      {
        fProgressValue = 1.0f;
      }
      OnSceneProgress?.Invoke(fProgressValue);
      yield return null;
    }
    OnSecenLoaded?.Invoke();
  }

  /**************************************
  * @fn OnNextSceneLoaded
  * @brief 加载下一场景完成回调
  * @return void
  **************************************/
  private void OnNextSceneLoaded()
  {
    m_bLoading = false;
    OnNextSceneProgress(1);
    m_strCurSceneName = m_strNextSceneName;
    m_strNextSceneName = null;
    m_onSceneLoaded?.Invoke();
  }

  /**************************************
  * @fn OnNextSceneProgress
  * @brief 场景加载进度变化
  * @param[in] float fProgress
  * @return void
  **************************************/
  private void OnNextSceneProgress(float fProgress)
  {
    if (null == m_objLoadProgress)
    {
      m_objLoadProgress = GameObject.Find("TextLoadProgress");
    }
    Text textLoadProgress = m_objLoadProgress.GetComponent<Text>();
    if (null == textLoadProgress)
    {
      return;
    }
    textLoadProgress.text = (fProgress*100).ToString() + "%";
  }
}

使用

1. 制作 Loading 场景,在 OnNextSceneProgress() 中写入展示进度的逻辑(以上代码中,笔者用的是一个 Text 展示进度)

2. 打开 File | Building Settings | Add Open Scenes 把全部场景加入 Scene In Build 列表中,不然系统会找不到需要跳转的场景

3. 在游戏第一个需要跳转的场景中调用以下函数,生成全局的场景管理器 GameObject(跳转场景不会被删除):

private void OnStart()
{
    GameObject objSceneManager = new GameObject("SceneManager");
    SceneManager.CreateInstance(objSceneManager);
}

4. 以后需要切换场景功能的场景直接调用以下函数进行场景的切换

SceneManager.LoadScene("跳转目标场景名称");

5. 需要返回到上一个场景调用以下函数

SceneManager.LoadPreScene();

End

以上就是这次分享的全部内容,如果大家有任何疑问和建议都可以在文章下留言,笔者会尽快回复和修改,谢谢~

ps:笔者在 Unity2018.3.0f2 Personal 下开发以上代码