笔者开发的Unity通用场景管理器,实现了异步场景切换、加载界面进度条显示、退回上一个场景功能
前言
场景切换是每一个游戏都会遇到的功能需求,当需要加载的场景较大时,同步加载会造成界面卡住很久再进入下一个场景的问题,导致很差的玩家体验。
笔者通过对网上分享的思路进行优化,完成了方便的全局场景管理器,通过异步加载场景解决卡顿问题。
基本思路
Unity通过以下函数提供给开发者场景异步加载功能:
我们可以通过开启一个协程调用异步加载函数实现异步加载场景功能。
以下代码展示了基础功能的实现方式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15publicstring nextSceneName;
private void Start()
{
//开启协程
StartCoroutine("LoadScene");
}
IEnumerator LoadScene()
{
//异步加载场景
async = SceneManager.LoadSceneAsync(nextSceneName);
yield return null;
}
实现
项目中我们通过调用SceneManager完成场景管理的全部功能,以下为代码实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162/**********************************************
*@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 => BuildingSettings => AddOpenScenes 把全部场景加入Scene In Build列表中,不然系统会找不到需要跳转的场景
3.在游戏第一个需要跳转的场景中调用以下函数,生成全局的场景管理器GameObject(跳转场景不会被删除):1
2
3
4
5private void OnStart()
{
GameObject objSceneManager = new GameObject("SceneManager");
SceneManager.CreateInstance(objSceneManager);
}
4.以后需要切换场景功能的场景直接调用以下函数进行场景的切换1
SceneManager.LoadScene("跳转目标场景名称");
5.需要返回到上一个场景调用以下函数1
SceneManager.LoadPreScene();