How to Develop a Game
Scene System
PublishDate: 2025-06-01 | CreateDate: 2025-06-01 | LastModify: 2025-06-01 | Creator:ljf12825

Unity Scene System是Unity中用于组织和管理游戏世界的基础结构,Unity支持多个Scene的加载与卸载,允许构建出大型、分块化的世界

Scene

Scene是Unity游戏项目中的一个基础构建单元,它就像游戏世界中的一个“地图”或“关卡”,一个Scene就是一个逻辑/物理空间的容器,包含了:

在Unity中,一个Scene对应一个.unity文件

Scene 生命周期

1.创建或打开场景(.unity文件)

2.布置场景内容

3.保存场景

4.构建和加载场景(Build Settings里添加场景)

5.运行时加载和卸载场景

场景最佳实践

Multi Scene

多场景允许你同时加载和管理多个场景,不同场景可以同时存在并且运行在游戏中,允许你灵活地处理加载、切换和卸载场景的需求

  1. 加载和卸载场景
  1. 常见的多场景应用场景

加载场景(Additive 和 Single)

Additive加载意味着将一个新场景加载到现有场景的基础上,当前场景不会被卸载,而是与新场景一起共存。使用这种方式可以让多个场景并行运行,从而实现一些复杂的场景管理,例如分割大型场景,或者在后台加载新的场景内容

使用场景:

// Additive
SceneManager.LoadScene("NewScene", LoadSceneMode.Additive);

Single加载模式表示加载一个新场景并卸载当前的场景。这是传统的场景切换方式,通常用于当你需要完全切换到另一个场景时

使用场景:

// 加载场景(Single)
SceneManager.LoadScene("NewScene", LoadSceneMode.Single);

卸载场景

如果你使用了Additive加载多个场景,可以选择卸载某个场景

// 卸载场景
SceneManager.UnloadSceneAsync("OldScene");

多场景加载的复杂性和管理

在使用Additive加载时,场景之间的资源管理尤为重要。不同场景之间共享资源,可能会导致以下问题:

避免场景间资源冲突

  1. 资源打包:利用Unity的资源打包系统(例如Asset Bundles或Addressables),可以让每个场景只加载所需的资源,避免多个场景之间的资源冲突

  2. 场景划分与依赖管理:避免场景间有过强的依赖关系。例如,将游戏逻辑与UI、背景和音效分开,确保每个场景只包含特定职责的内容

  3. 共享资源:通过DontDestroyOnLoad来管理那些需要跨场景的共享资源(如音频管理器、玩家数据管理器等)

切换场景并保持场景之间的交互

使用多场景时,有时候你希望不同的场景之间可以交互。比如,可以在一个场景中控制另一个场景中的对象。
可以通过SceneManager.GetSceneByNameSceneManager.GetSceneAt获取其他加载的场景,然后通过Scene.GetRootGameObjects()获取该场景中的所有根级对象,进一步操作它们

// 获取其他场景的根物体
Scene otherScene = SceneManager.GetSceneByName("OtherScene");
GameObject[] rootObjects = otherScene.GetRootGameObjects();

// 操作场景中的物体
foreach (GameObject obj in rootObjects) obj.SetActive(false); // 隐藏物体

异步加载场景

为什么使用异步加载

异步加载允许你在场景加载的过程中保持游戏的流畅运行。异步加载时,Unity会在后台加载场景,不会阻塞主线程,因此可以避免游戏界面卡顿或掉帧
异步加载常用于:

异步加载基础

通过SceneManager.LoadSceneAsync可以异步加载场景,可以使用AsyncOperation对象来监控加载进度

示例:

IEnumerator LoadSceneAsync(string sceneName)
{
    // 异步加载场景
    AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);

    // 等待场景加载完成
    while (!asyncLoad.isDone)
    {
        // 显示加载进度
        float progress = Mathf.Clamp01(asyncLoad.progress / 0.9f); // 进度值[0, 1]
        Debug.Log("Loading progress: " + progress);

        // 可以在这里更新进度条UI

        yield return null; // 每帧执行
    }
}

asyncawait异步加载

Unity在2017之后对C#的异步功能支持更好,支持使用asyncawait来简化异步操作。你可以将异步加载过程包装成一个异步方法,从而避免繁琐的协程逻辑

saync Task LoadSceneAsync(string sceneName)
{
    AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);

    // 等待加载完成
    while (!asyncLoad.isDone)
    {
        float progress = Mathf.Clamp01(asyncLoad.progress / 0.9f);
        Debug.Log("Loading progress: " + progress);
        await Task.Yield(); // 等待下一帧
    }
}

资源顺序加载

有时候,我们希望保证某些资源或场景按特定顺序加载,或者在某些资源完全加载后才能加载其他资源。使用异步加载时,asyncawait可以让你控制加载的顺序

async Task LoadSceneInOrider()
{
    // 加载第一个场景
    await LoadSceneAsync("Scene1");

    // 加载第二个场景
    await LoadSceneAsync("Scene2");

    // 继续加载其他资源
}

避免资源加载顺序问题

确保在加载多个场景时,场景中的资源顺序正确。对于大型场景或者有依赖关系的资源,可以使用Addressables系统来更细粒度地控制资源的加载顺序

资源优化

异步加载场景和资源时,性能优化是非常重要的。可以使用以下方法:

多场景注意事项

实例:多场景管理器

可以创建一个管理多个场景加载的系统

using UnityEngine;
using UnityEngine.SceneManagement;

public class MultiSceneManager : MonoBehaviour
{
    public string mainScene = "MainScene";
    public string uiScene = "UIScene";

    void Start()
    {
        // 加载主场景和UI场景
        LoadMainScene();
        LoadUIScene();
    }

    public void LoadMainScene() => SceneManager.LoadScene(mainScene, LoadSceneMode.Additive);

    public void LoadUIScene() => SceneManager.LoadScene(uiScene, LoadSceneMode.Additive);

    public void UnloadMainScene() => SceneManager.UnloadSceneAsync(mainScene);

    public void UnloadUIScene() => SceneManager.UnloadSceneAsync(uiScene);
}

SceneManager

SceneManager是Unity中用来管理场景加载、卸载和切换的一个类。它提供了许多用于操作场景的方法,比如异步加载场景、场景之间切换、场景的同步加载、查询场景信息等。

SceneManagerUnityEngine.SceneManagement命名空间的一部分

常用API

加载场景

SceneManager.LoadScene用于加载指定的场景

SceneManager.LoadScene("SceneName");

异步加载场景是为了避免在加载时阻塞主线程,可以提高游戏体验

AsyncOperation asyncOp = SceneManager.LoadSceneAsync("SceneName");

异步加载场景时,可以通过AsyncOperation对象来获取加载进度,甚至可以控制场景是否在加载完成后自动激活

asyncOp.allowSceneActivation = false; // 控制场景是否自动激活
卸载场景

SceneManager.UnloadSceneAsync用于卸载一个场景,通常用于切换场景时

SceneManager.UnloadSceneAsync("SceneName");

这个方法是异步的,因此可以在后台卸载场景,不会影响游戏的运行

获取当前场景

SceneManager.GetActiveScene用于获取当前激活的场景,返回的是一个Scene对象

Scene currentScene = SceneManager.GetActiveScene();

可以通过Scene对象获取场景的名称、路径、索引等信息

string sceneName = currentScene.name;
场景切换

可以使用SceneManager.LoadScene来加载多个场景,这对于一些需要同时存在多个场景的情况(如多人联机或大场景加载)非常有用

SceneManager.LoadScene("Scene1", LoadSceneMode.Additive); // 加载一个附加场景

通过LoadSceneMode.Additive,新的场景会叠加到现有场景上

如果你要切换到一个新的场景并卸载当前场景,可以在加载新场景时使用LoadSceneMode.Single,它会在加载新场景的同时卸载当前场景

SceneManager.LoadScene("NewScene", LoadSceneMode.Single);
场景索引

可以通过Scene对象来查询更多关于场景的信息:

string sceneName = currentScene.name
int sceneIndex = currentScene.buildIndex
GameObject[] rootObjects = currentScene.GetRootGameObjects();
事件和回调

Unity提供了一些事件和回调来监听场景加载的状态

这个事件会在场景加载完成时触发,你可以通过订阅这个事件来执行场景加载后的操作

SceneManger.sceneLoaded += OnSceneLoaded;

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    Debug.Log("Scene " + scene.name + " loaded.");
}

这个事件会在场景卸载时触发

SceneManager.sceneUnloaded += OnSceneUnloaded;

void OnSceneUnloaded(Scene scene) => Debug.Log("Scene " + scene.name + " unloaded.");

UnityScripting SceneManager

DontDestroyOnLoad

DontDestroyOnLoad是Unity中一个非常使用的函数,它可以将某个对象在加载新场景时保持不被销毁。这对于需要在多个场景间共享的对象(如音频管理器、玩家数据等)非常有用

1.基本使用

通过DontDestroyOnLoad,你可以使一个游戏对象在场景切换时不被销毁。当你加载一个新场景时,默认情况下,当前场景中所有对象都会被卸载。但如果某个对象调用了DontDestroyOnLoad,它就会被保留,直到手动销毁它

void Start() => DontDestroyOnLoad(gameObject); // 保持这个对象不被销毁

2.适用场景

DontDestroyOnLoad通常适用于以下情况:

3.注意事项

可能引起的问题

解决办法:通常做法是在脚本中加上一个检查,确保只有第一个创建的对象调用DontDestroyOnLoad,而其他对象则销毁自己

void Start()
{
    if (FindObjectsOfType(typeof(MyManager)).Length > 1)
        Destroy(gameObject); // 如果场景中已经有一个该类型的对象,销毁当前对象
    
    else DontDestroyOnLoad(gameObject); // 否则保留该对象
}

使用DontDestroyOnLoad时的对象管理

跨场景对象命名问题 DontDestroyOnLoad对象仍然存在于内存中,因此你需要特别注意它们的名字。为了避免在多个场景中有相同名称的对象,通常可以将DontDestroyOnLoad对象的名称修改为唯一的标识符

void Start()
{
    if (FindObjectOfType<MyManager>() == null)
    {
        gameObject.name = "UniqueManager";
        DontDestroyOnLoad(gameObject);
    }
    else Destroy(gameObject);
}

底层实现(推测)

Unity的底层实现并没有公开DontDestroyOnLoad的具体源码,但我们可以推测它的工作原理基于以下几个步骤:

场景打包与构建设置

构建设置

访问构建设置

构建设置可以通过Unity编辑器的菜单访问:

构建设置窗口列出了所有可用的场景、平台选项、打包模式、构建设置等。你可以通过它来管理场景、选择构建平台、设置构建选项

Build Settings

场景的添加与移除

在构建设置中,所有需要包含在游戏构建中的场景都必须被添加到场景列表中

场景顺序:场景在构建设置中的顺序很重要。Unity会按顺序加载这些场景,并且默认加载列表中的第一个场景作为游戏启动场景

构建平台选择

Unity支持多个平台的构建,包括PC、Mac、Linux、WebGL、iOS、Android、Console等,可以选择目标平台并进行构建。不同平台可能会有不同的构建要求

场景打包模式

在构建设置中,场景的加载方式分为两种模式:

通常情况下,需要选择不同的加载模式来优化内存管理和加载时间

场景构建与打包优化

资源优化

优化游戏的场景和资源打包时提升游戏性能的重要步骤,以下是常见的优化方法:

场景打包设置

Unity提供了对场景打包的一些设置,帮助你控制场景打包的方式:

多平台支持

Unity支持在不同平台上进行构建。你可以针对每个平台选择不同的场景优化策略:

构建设置中的其他选项

场景分块加载

对于大型游戏,特别是开放世界类型的游戏,将场景分割成小的块(Chunk)是非常重要的,这样可以按需加载场景,从而减小内存消耗并提高游戏性能

分块加载场景的优势:

如何实现场景分块加载:

  1. 将一个大场景拆分成多个小场景(例如,分成多个区域)
  2. 使用SceneManager.LoadSceneSceneManger.LoadSceneAsync按需加载这些小场景
  3. 使用LoadSceneMode.Additive加载多个场景,而不卸载当前场景
// 异步加载多个场景
IEnumerator LoadChunksAsync()
{
    AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("Chunk1", LoadSceneMode.Additive);

    while(!asyncLoad.isDone) yield return null;

    asyncLoad = SceneManager.LoadSceneAsync("Chunk2", LoadSceneMode.Additive);

    while (!asyncLoad.isDone) yield return null;
}

场景分层管理

通过分层管理来控制场景的加载,主要将游戏内容和UI、背景音乐等资源分离,可以提高场景的加载效率,例如:

触发器加载机制

Addressable与场景加载

Addressable Asset System

Unity的Addressable Asset System是一个非常强大的资源管理和加载系统,可以让你更精确地控制资源的加载、卸载、分发和优化。它可以用于按需加载资源,特别适合大规模场景的动态加载

主要特点:

在构建场景时,可以通过Addressable资源来管理和加载场景文件及资源。例如,可以将场景预先打包为Addressable,并在运行时加载它

示例:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

void LoadSceneWithAddressable(string sceneName)
{
    // 使用Addressable加载场景
    AsyncOperationHandle<SceneInstance> handle = Addressables.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

    // 可以监听加载过程
    handle.Completed += (op) => { Debug.Log("Scene Loaded: " + sceneName);};
}

场景打包为Addressable

将场景打包为Addressable可以极大地优化场景的加载过程,特别是在资源分发和网络加载时,Unity提供了专门的工具来标记场景为Addressable,并管理它们的加载

  1. 在Addressable中创建一个项目条目
  2. 通过Asset Bundle管理器,将场景资源标记为Addressable
  3. 使用异步加载方法来加载场景并优化内存使用