Unity编辑器中,我们放置各种游戏对象,搭建一个玩法区域的地方是场景(Scene)。例如RPG游戏中,游戏的起始界面、各张小地图、战斗场景等都是Scene。这里我们介绍如何加载和切换Scene,以及Scene使用中需要了解的相关知识。
创建场景很简单,在Unity编辑器的Project工程资源视图中,右键Create -> Scene
即可。
创建好的空场景中,默认只有一个摄像机(Camera)。
在了解切换场景的API前,我们需要将能互相切换的场景加入到工程的Scenes In Build
中。切换到我们需要添加的场景,在Unity编辑器里点击File -> Build Settings
,点击Add Open Scenes
,即可将当前Scene加入构建场景中。
如果不将场景加入构建,切换场景时会报错。
切换场景需要使用SceneManage
这个类。
public static void LoadScene(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);
public static void LoadScene(string sceneName, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);
上面两个函数可用于切换场景,其中第一个参数分别是Scene的构件序号(上图中有标注)或是Scene的名字(就是文件名)。
例子代码:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ChangeScene : MonoBehaviour
{
public string sceneName;
public void changeScene()
{
if (sceneName != null && sceneName != "")
{
SceneManager.LoadScene(sceneName);
}
}
}
实际开发中,如果需要加载的场景数据量很大,同步加载就会造成程序卡死的状况,这时候就需要使用异步加载。SceneManager
还提供了LoadSceneAsync()
函数,可以在不结束当前场景的情况下,异步加载另一个场景。
public static AsyncOperation LoadSceneAsync(string sceneName, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);
public static AsyncOperation LoadSceneAsync(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);
参数意义和之前LoadScene()
一致。
例子代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class AsyncChangeScene : MonoBehaviour
{
public string sceneName;
private AsyncOperation operation;
void Update()
{
if (operation != null)
{
Debug.Log(operation.progress);
}
}
public void AsyncLoad()
{
if (sceneName != null && sceneName != "")
{
operation = SceneManager.LoadSceneAsync(sceneName);
}
}
}
注意LoadSceneAsync()
有一个返回值AsyncOperation
,它包含了异步加载的进度,取值0 ~ 1
。上面代码中,我们读取了进度值,并通过打印在了控制台上。
当异步加载完成后,场景将自动切换。
上面我们实现的异步加载场景,场景加载完成后会自动切换,实际上,我们也可以指定不自动切换。这需要对AsyncOperation
指定allowSceneActivation
为false
。指定该属性后,异步加载完成时也不会自动切换场景,而加载进度会停留在0.9
。当设置该属性为true
时,切换才会生效。
例子代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class AsyncChangeScene : MonoBehaviour
{
public string sceneName;
// 一个提示信息,内容为 加载完成,按SPACE键继续
public Text loadingFinishTip;
private AsyncOperation operation;
private bool isStartLoading = false;
private bool isLoadingFinish = false;
void Update()
{
if (isStartLoading && !isLoadingFinish && operation != null)
{
Debug.Log(operation.progress);
if (operation.progress >= 0.9f)
{
loadingFinishTip.gameObject.SetActive(true);
isLoadingFinish = true;
}
}
if (isLoadingFinish)
{
if (Input.GetKeyDown(KeyCode.Space))
{
operation.allowSceneActivation = true;
}
}
}
public void AsyncLoad()
{
if (sceneName != null && sceneName != "" && !isStartLoading)
{
isStartLoading = true;
operation = SceneManager.LoadSceneAsync(sceneName);
operation.allowSceneActivation = false;
}
}
}
代码很简单,这里就不多说了,有一个需要注意的地方:判断进度是否为0.9
时,不能使用==
,因为浮点数不能精确比较相等,这属于编程常识性的问题了。
在切换场景时,默认情况原来场景中的游戏对象都会被销毁,但有时我们不希望某些对象和数据被销毁,这时可以使用DontDestroyOnLoad()
保存跨场景数据。
public static void DontDestroyOnLoad(Object target);
例子代码:
GameObject.DontDestroyOnLoad(mySprite);
但事情其实没有这么简单。如果包含DontDestroyOnLoad()
的场景被重复加载,跨场景对象也会被实例化多个,这点需要注意。
实际上,跨场景游戏对象的局限性是比较大的,处理不好会引发很多问题。大部分时候,其实我们没必要使用跨场景游戏对象,使用跨场景数据就能够解决大部分问题。比如RPG游戏中切换地图,但是我们想要保留HUD,我们难道真的需要把UI对象做成跨场景对象吗?其实是不必要的,我们可以在某处暂存HUD的数据,然后通过Prefab重新加载HUD的UI对象。
那么在哪里保存全局数据呢?这个方式就有很多了,两种比较常用的方式是存档到磁盘和使用静态空间存储,这里我们介绍比较简单的后者。
下面代码中,我们创建了GlobalData.data
来存储一些全局数据。注意这里我们的类没有也无需继承MonoBehaviour
,这个类不需要挂载到游戏对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GlobalData
{
public static Dictionary<string, string> data = new Dictionary<string, string>();
}
在游戏脚本中直接引用即可,静态属性不会受到场景切换影响。
在实际开发时,许多工程采用单场景开发的模式,在这种开发方式中,起始场景是一个空的Scene,其中只有一个挂载入口脚本的GameObject,而所有的地图、UI等都是通过代码加载AssetBundle的方式加载到场景中的,所有的界面和地图切换也都是通过代码来控制。其余所有Scene都是开发、调试用途,不会真正打包到最终发布的程序中。
这种开发方式的好处是更加符合软件工程化的习惯,具备单一代码入口,完全没有各种跨场景对象问题,而且能够直接实现游戏和数据包的分离(移动端常用此方式),不过这其实和Unity设计Scene这个层级的初衷有些违背。
个人认为两者都没有对错,对于团队开发的商业项目,恐怕这种单场景开发模式是较好的选择,而如果是个人小项目,其实哪种开发方式都问题不大。