U
10 / 22 · 10 分钟
常见问题

切换场景:LoadScene、加载界面,以及什么会存活下来

简短回答

怎么在 Unity 里切换到另一个场景?

把每个场景都加进 File > Build Profiles 里的 Scene List,然后用场景名调用 SceneManager.LoadScene。切换发生在下一帧,旧场景里的一切都会被销毁,只有标记了 DontDestroyOnLoad 的根物体能存活——以及静态字段和 Time.timeScale,这些没有任何人会替你重置。

加载另一个 scene 只要一行。所有麻烦都出在这行周围:scene 从来没被加进构建、后面的代码还在接着跑、或者你正需要的东西已经跟着旧 scene 一起被丢掉了。

第 1 步 — 把 scene 放进列表

  1. 1

    打开 File▸Build Profiles(Unity 6 之前叫 File▸Build Settings),找到 Scene List。

  2. 2

    把你想放进游戏里的每个 scene 都打开,然后按 Add Open Scenes,或者直接把 .unity 文件从 Project 面板拖进来。

  3. 3

    位于 index 0 的那个,就是构建出来的游戏启动时进入的 scene。把菜单、或者开场放到最上面。

第 2 步 — 加载它

Doorway.cs — the whole of scene switching
using UnityEngine;
using UnityEngine.SceneManagement;   // without this line, SceneManager is not found

public class Doorway : MonoBehaviour
{
    public void Play()
    {
        SceneManager.LoadScene("Arena");        // by name, as spelt in the Scene List
    }

    public void Restart()
    {
        // The scene that is running, whatever it is called.
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

什么能挺过加载,什么不能

  • 旧 scene 里的每个 GameObject 都会被销毁。指向它们的引用变成 null,跑在它们上面的 coroutine 也就停在原地。

  • 标了 DontDestroyOnLoad 的 object 会继续活着 —— 但只有当它是没有父物体的根 object 时才成立。最常留下的是背景音乐、存在内存里的存档,还有 一个 manager。

  • static 字段不属于任何 scene,所以它的值会保留下来。把关卡编号带过去很方便;但如果上一次运行剩下的值还躺在那里,它就是个坑。

  • Time.timeScale 是全局的。用 timeScale = 0 暂停了游戏,然后加载菜单却忘了把它调回 1,菜单就会卡死着打开 —— 而且没有任何报错来解释原因。

加载画面:给那些要等一下的 scene

Loader.cs — a progress bar that actually means something
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class Loader : MonoBehaviour
{
    public Slider bar;

    public void Load(string scene) => StartCoroutine(LoadRoutine(scene));

    IEnumerator LoadRoutine(string scene)
    {
        AsyncOperation op = SceneManager.LoadSceneAsync(scene);

        while (!op.isDone)
        {
            // progress stops at 0.9 while the scene waits its turn to appear,
            // so scale it back up or the bar never reaches the end.
            bar.value = Mathf.Clamp01(op.progress / 0.9f);
            yield return null;
        }
    }
}

同时存在两个 scene

LoadSceneMode.Additive 不是替换掉正在运行的 scene,而是把一个 scene 叠在它上面:比如永远不用再加载的 HUD,或者在主场景旁边流式加载进来的关卡。要撤掉就用 UnloadSceneAsync,并且记住:新生成的 object 会落在当前是 active 的那个 scene 里。

做出这个的那一课

界面、面板与 GameManager

这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 基础,第 10 课。