Menu
CoddyTech

跨场景保留

本课属于 Coddy Unity 学习旅程的“打造真正的游戏”单元。第 20 课,共 60 课。

加载场景会销毁旧场景中的所有对象,但有些内容必须在整个游戏过程中保留下来,例如玩家从一个关卡带到下一个关卡的金币。DontDestroyOnLoad(gameObject) 会将对象从当前场景移出,放入一个独立的场景中。在“开始”时,这个场景在 Hierarchy 中名为 DontDestroyOnLoad,加载场景时不会影响它。它只对根对象有效:如果对象是子对象,Unity 会记录一条警告,并且不会保留任何内容。被保留对象的子对象也会一并保留,但它只指向而未包含的对象不会保留:场景加载后,指向旧场景对象的字段会保存一个已销毁的对象,它与 null 相等。

public class GameSession : MonoBehaviour
{
    public static GameSession Instance;
    public int coins;

    void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
}

这个 GameSession 位于 Menu 场景中。点击“开始”后,游戏从 Menu 进入 Level1,再返回 Menu。Menu 会重新创建其中的所有对象,包括一个新的 GameSession,而之前保留的对象仍然存在。如果 Awake 中没有检查,就会有两个 GameSession,而且新对象会接管 Instance,金币数为 0。有了这个检查,新对象会发现 Instance 已经设置,于是销毁自身并停止运行,而那个经历了各个关卡的会话则会保留下来。

return 很重要,因为 Destroy 会等到帧末尾才执行。如果没有它,Awake 的剩余部分仍会在新对象上运行:Instance = this; 会让所有脚本都指向一个即将被销毁的对象。

challenge icon

挑战

简单

让同一个计时器在每次场景加载后继续运行。

  1. 在 Awake() 中,如果 Instance != null,调用 Destroy(gameObject); 并执行 return;。否则设置 Instance,并调用 DontDestroyOnLoad(gameObject);。
  2. 加载场景会销毁旧关卡的文本,因此在 Update() 中、文本赋值语句之前,如果 clockText == null,就设置 clockText = GameObject.Find("ClockText").GetComponent<TMP_Text>();。

点击运行,前往 Level2 再返回:同一个计时器会继续计时。然后点击检查。

亲手试试

using UnityEngine;
using TMPro;

public class RunClock : MonoBehaviour
{
    public static RunClock Instance;
    public TMP_Text clockText;
    private float seconds;

    void Awake()
    {
        Instance = this;
    }

    void Update()
    {
        seconds += Time.deltaTime;
        clockText.text = "Time: " + seconds.ToString("0.0");
    }

    public void Split()
    {
        Debug.Log("Split: " + seconds.ToString("0.0"));
    }
}

打造真正的游戏中的所有课

自己练习: Unity 在线编辑器