场景管理

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指定allowSceneActivationfalse。指定该属性后,异步加载完成时也不会自动切换场景,而加载进度会停留在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 跨场景游戏对象

在切换场景时,默认情况原来场景中的游戏对象都会被销毁,但有时我们不希望某些对象和数据被销毁,这时可以使用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这个层级的初衷有些违背。

个人认为两者都没有对错,对于团队开发的商业项目,恐怕这种单场景开发模式是较好的选择,而如果是个人小项目,其实哪种开发方式都问题不大。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap