场景管理
场景(Scene)是 Unity 组织游戏内容的基本单位。有效的场景管理对于游戏的流畅运行、内存控制和用户体验至关重要。本章将详细讲解 Unity 的场景系统,包括场景加载、卸载、异步加载、多场景编辑等核心功能。
场景概念
什么是场景?
场景是游戏对象的容器,可以理解为游戏中的一个"关卡"或"画面"。每个场景包含:
- 游戏对象(GameObject):场景中的实体
- 环境设置:天空盒、雾效、环境光
- 导航数据:NavMesh 信息
- 光照数据:光照贴图、探针
场景组织策略
合理的场景划分可以提高开发效率和运行性能:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 按关卡划分 | 每个关卡一个场景 | 关卡制游戏 |
| 按功能划分 | 菜单、游戏、UI 分别成场景 | 大多数游戏 |
| 按区域划分 | 开放世界的不同区域 | 开放世界游戏 |
| 按功能模块 | 地形、角色、UI 作为独立场景 | 大型项目 |
典型场景结构:
项目场景结构:
├── Boot.unity # 启动场景(加载核心系统)
├── MainMenu.unity # 主菜单
├── Loading.unity # 加载场景
├── Level_01.unity # 关卡1
├── Level_02.unity # 关卡2
├── GameUI.unity # 游戏UI(附加加载)
└── Environment/ # 环境场景(附加加载)
├── Terrain.unity
├── Lighting.unity
└── Audio.unity
场景基础操作
编辑器操作
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
public class EditorSceneOperations
{
// 创建新场景
[MenuItem("Scenes/Create New Scene")]
static void CreateNewScene()
{
Scene newScene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
EditorSceneManager.SaveScene(newScene, "Assets/Scenes/NewScene.unity");
}
// 打开场景
[MenuItem("Scenes/Open Main Menu")]
static void OpenMainMenu()
{
EditorSceneManager.OpenScene("Assets/Scenes/MainMenu.unity");
}
// 保存当前场景
[MenuItem("Scenes/Save Current Scene")]
static void SaveCurrentScene()
{
Scene activeScene = EditorSceneManager.GetActiveScene();
EditorSceneManager.SaveScene(activeScene);
}
}
#endif
运行时场景信息
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneInfo : MonoBehaviour
{
void Start()
{
// 获取当前活动场景
Scene activeScene = SceneManager.GetActiveScene();
Debug.Log($"场景名称: {activeScene.name}");
Debug.Log($"场景路径: {activeScene.path}");
Debug.Log($"构建索引: {activeScene.buildIndex}");
Debug.Log($"是否已加载: {activeScene.isLoaded}");
Debug.Log($"根对象数量: {activeScene.rootCount}");
// 获取所有已加载的场景
int sceneCount = SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
Debug.Log($"已加载场景 {i}: {scene.name}");
}
// 遍历场景中的根对象
GameObject[] rootObjects = activeScene.GetRootGameObjects();
foreach (var obj in rootObjects)
{
Debug.Log($"根对象: {obj.name}");
}
}
}
同步场景加载
LoadScene 基本用法
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
// 按名称加载场景
public void LoadSceneByName(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
// 按构建索引加载场景
public void LoadSceneByIndex(int buildIndex)
{
SceneManager.LoadScene(buildIndex);
}
// 加载场景示例
public void LoadMainMenu()
{
SceneManager.LoadScene("MainMenu");
}
public void LoadLevel1()
{
SceneManager.LoadScene(1); // 假设关卡1的构建索引是1
}
}
加载模式
public class SceneLoadModes : MonoBehaviour
{
// Single 模式:卸载当前所有场景,加载新场景
public void LoadSingle(string sceneName)
{
SceneManager.LoadScene(sceneName, LoadSceneMode.Single);
}
// Additive 模式:保留当前场景,额外加载新场景
public void LoadAdditive(string sceneName)
{
SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
}
// 示例:加载游戏UI作为附加场景
public void LoadGameUI()
{
SceneManager.LoadScene("GameUI", LoadSceneMode.Additive);
}
}
LoadSceneMode 对比:
| 模式 | 行为 | 使用场景 |
|---|---|---|
| Single | 卸载所有当前场景,只保留新加载的场景 | 关卡切换、返回主菜单 |
| Additive | 保留当前场景,额外加载新场景 | 动态加载地形、UI、环境 |
场景卸载
public class SceneUnloader : MonoBehaviour
{
// 卸载指定场景
public void UnloadScene(string sceneName)
{
// 检查场景是否已加载
Scene scene = SceneManager.GetSceneByName(sceneName);
if (scene.isLoaded)
{
SceneManager.UnloadSceneAsync(sceneName);
}
}
// 卸载除指定场景外的所有场景
public void UnloadAllExcept(string sceneToKeep)
{
for (int i = SceneManager.sceneCount - 1; i >= 0; i--)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.name != sceneToKeep && scene.isLoaded)
{
SceneManager.UnloadSceneAsync(scene);
}
}
}
}
异步场景加载
异步加载允许在后台加载场景,避免游戏卡顿,同时可以显示加载进度。
LoadSceneAsync 基本用法
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class AsyncSceneLoader : MonoBehaviour
{
public string sceneToLoad = "Level_01";
public void LoadSceneAsync()
{
StartCoroutine(LoadSceneCoroutine());
}
IEnumerator LoadSceneCoroutine()
{
// 开始异步加载
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneToLoad);
// 禁止场景加载完成后自动切换
asyncLoad.allowSceneActivation = false;
// 等待加载完成
while (!asyncLoad.isDone)
{
// 输出进度(0 到 0.9)
Debug.Log($"加载进度: {asyncLoad.progress * 100}%");
// 进度达到 0.9 表示加载完成
if (asyncLoad.progress >= 0.9f)
{
Debug.Log("场景加载完成,准备切换");
asyncLoad.allowSceneActivation = true;
}
yield return null;
}
Debug.Log("场景切换完成");
}
}
异步加载进度说明
Unity 的异步加载进度有一个特殊机制:
- 0.0 - 0.9:实际加载进度
- 0.9:加载完成,等待激活
- 1.0:场景已激活
// 正确理解进度
IEnumerator LoadWithProgress(string sceneName)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
operation.allowSceneActivation = false;
float displayProgress = 0f;
float targetProgress = 0f;
while (!operation.isDone)
{
// 实际进度(最高 0.9)
targetProgress = operation.progress;
// 平滑显示进度
displayProgress = Mathf.MoveTowards(displayProgress, targetProgress, Time.deltaTime);
Debug.Log($"显示进度: {displayProgress * 100}%");
if (operation.progress >= 0.9f)
{
// 加载完成,可以激活
displayProgress = 1f;
operation.allowSceneActivation = true;
}
yield return null;
}
}
加载画面实现
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;
public class LoadingScreen : MonoBehaviour
{
[Header("UI 组件")]
public GameObject loadingPanel;
public Slider progressBar;
public Text progressText;
public Text tipText;
[Header("提示文本")]
public string[] tips = {
"提示:按空格键可以跳跃",
"提示:收集金币可以获得额外分数",
"提示:注意躲避敌人的攻击"
};
public void LoadLevel(string levelName)
{
StartCoroutine(LoadLevelAsync(levelName));
}
IEnumerator LoadLevelAsync(string levelName)
{
// 显示加载面板
loadingPanel.SetActive(true);
// 显示随机提示
if (tipText != null && tips.Length > 0)
{
tipText.text = tips[Random.Range(0, tips.Length)];
}
// 开始加载
AsyncOperation operation = SceneManager.LoadSceneAsync(levelName);
operation.allowSceneActivation = false;
float displayProgress = 0f;
while (!operation.isDone)
{
// 平滑进度条
float targetProgress = Mathf.Clamp01(operation.progress / 0.9f);
displayProgress = Mathf.Lerp(displayProgress, targetProgress, Time.deltaTime * 5f);
// 更新 UI
if (progressBar != null)
{
progressBar.value = displayProgress;
}
if (progressText != null)
{
progressText.text = $"加载中... {(int)(displayProgress * 100)}%";
}
// 加载完成
if (operation.progress >= 0.9f)
{
// 等待一小段时间让玩家看到 100%
yield return new WaitForSeconds(0.5f);
// 激活场景
operation.allowSceneActivation = true;
}
yield return null;
}
// 隐藏加载面板
loadingPanel.SetActive(false);
}
}
带动画的加载画面
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;
public class AnimatedLoadingScreen : MonoBehaviour
{
[Header("动画设置")]
public Animator fadeAnimator;
public float fadeDuration = 0.5f;
[Header("UI 组件")]
public GameObject loadingScreen;
public Slider progressBar;
public Text progressText;
private string nextSceneName;
// 从当前场景切换到目标场景
public void TransitionToScene(string sceneName)
{
nextSceneName = sceneName;
StartCoroutine(TransitionRoutine());
}
IEnumerator TransitionRoutine()
{
// 淡出当前场景
fadeAnimator.SetTrigger("FadeOut");
yield return new WaitForSeconds(fadeDuration);
// 显示加载画面
loadingScreen.SetActive(true);
// 淡入加载画面
fadeAnimator.SetTrigger("FadeIn");
yield return new WaitForSeconds(fadeDuration);
// 开始加载
yield return StartCoroutine(LoadSceneRoutine(nextSceneName));
// 淡出加载画面
fadeAnimator.SetTrigger("FadeOut");
yield return new WaitForSeconds(fadeDuration);
// 隐藏加载画面
loadingScreen.SetActive(false);
// 淡入新场景
fadeAnimator.SetTrigger("FadeIn");
}
IEnumerator LoadSceneRoutine(string sceneName)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
operation.allowSceneActivation = false;
while (!operation.isDone)
{
float progress = Mathf.Clamp01(operation.progress / 0.9f);
if (progressBar != null)
progressBar.value = progress;
if (progressText != null)
progressText.text = $"加载中... {(int)(progress * 100)}%";
if (operation.progress >= 0.9f)
{
yield return new WaitForSeconds(0.3f);
operation.allowSceneActivation = true;
}
yield return null;
}
}
}
多场景管理
Additive 加载策略
使用 Additive 模式可以同时运行多个场景,适合以下场景:
- 大型开放世界:动态加载卸载区域
- 持久化 UI:UI 场景始终存在
- 共享资源:环境、光照作为独立场景
public class MultiSceneManager : MonoBehaviour
{
[Header("持久化场景")]
public string persistentScene = "PersistentManagers";
public string uiScene = "GameUI";
[Header("关卡场景")]
public string[] levelScenes;
private int currentLevelIndex = -1;
void Start()
{
StartCoroutine(InitializeGame());
}
IEnumerator InitializeGame()
{
// 加载持久化管理场景
yield return SceneManager.LoadSceneAsync(persistentScene, LoadSceneMode.Additive);
// 加载 UI 场景
yield return SceneManager.LoadSceneAsync(uiScene, LoadSceneMode.Additive);
Debug.Log("游戏初始化完成");
}
// 切换关卡
public void LoadLevel(int levelIndex)
{
StartCoroutine(SwitchLevel(levelIndex));
}
IEnumerator SwitchLevel(int levelIndex)
{
// 卸载当前关卡
if (currentLevelIndex >= 0)
{
yield return SceneManager.UnloadSceneAsync(levelScenes[currentLevelIndex]);
}
// 加载新关卡
yield return SceneManager.LoadSceneAsync(levelScenes[levelIndex], LoadSceneMode.Additive);
currentLevelIndex = levelIndex;
// 设置为新关卡为活动场景(用于实例化对象)
Scene newScene = SceneManager.GetSceneByName(levelScenes[levelIndex]);
SceneManager.SetActiveScene(newScene);
}
}
动态区域加载
开放世界游戏中,根据玩家位置动态加载卸载区域:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class WorldStreamer : MonoBehaviour
{
[System.Serializable]
public class WorldRegion
{
public string sceneName;
public Vector3 center;
public Vector3 size;
public bool isLoaded;
}
public Transform player;
public WorldRegion[] regions;
public float checkInterval = 1f;
public float loadDistance = 50f;
private Dictionary<string, AsyncOperation> loadingOperations = new Dictionary<string, AsyncOperation>();
void Start()
{
StartCoroutine(CheckRegionsCoroutine());
}
IEnumerator CheckRegionsCoroutine()
{
while (true)
{
UpdateRegions();
yield return new WaitForSeconds(checkInterval);
}
}
void UpdateRegions()
{
if (player == null) return;
foreach (var region in regions)
{
float distance = GetDistanceToRegion(region);
if (distance < loadDistance && !region.isLoaded && !IsLoading(region.sceneName))
{
LoadRegion(region);
}
else if (distance > loadDistance * 1.5f && region.isLoaded)
{
UnloadRegion(region);
}
}
}
float GetDistanceToRegion(WorldRegion region)
{
// 计算玩家到区域边界的最近距离
Vector3 closestPoint = new Vector3(
Mathf.Clamp(player.position.x, region.center.x - region.size.x / 2, region.center.x + region.size.x / 2),
Mathf.Clamp(player.position.y, region.center.y - region.size.y / 2, region.center.y + region.size.y / 2),
Mathf.Clamp(player.position.z, region.center.z - region.size.z / 2, region.center.z + region.size.z / 2)
);
return Vector3.Distance(player.position, closestPoint);
}
void LoadRegion(WorldRegion region)
{
StartCoroutine(LoadRegionAsync(region));
}
IEnumerator LoadRegionAsync(WorldRegion region)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(region.sceneName, LoadSceneMode.Additive);
loadingOperations[region.sceneName] = operation;
yield return operation;
loadingOperations.Remove(region.sceneName);
region.isLoaded = true;
Debug.Log($"区域加载完成: {region.sceneName}");
}
void UnloadRegion(WorldRegion region)
{
StartCoroutine(UnloadRegionAsync(region));
}
IEnumerator UnloadRegionAsync(WorldRegion region)
{
yield return SceneManager.UnloadSceneAsync(region.sceneName);
region.isLoaded = false;
Debug.Log($"区域卸载完成: {region.sceneName}");
}
bool IsLoading(string sceneName)
{
return loadingOperations.ContainsKey(sceneName);
}
// 调试可视化
void OnDrawGizmosSelected()
{
if (regions == null) return;
foreach (var region in regions)
{
Gizmos.color = region.isLoaded ? Color.green : Color.red;
Gizmos.DrawWireCube(region.center, region.size);
}
}
}
场景合并
有时需要将多个场景合并为一个:
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
public class SceneMerger
{
[MenuItem("Tools/Merge Scenes")]
static void MergeSelectedScenes()
{
// 获取选中的场景文件
var selectedScenes = Selection.GetFiltered<SceneAsset>(SelectionMode.DeepAssets);
if (selectedScenes.Length < 2)
{
Debug.LogError("请选择至少两个场景");
return;
}
// 加载第一个场景作为目标场景
string firstScenePath = AssetDatabase.GetAssetPath(selectedScenes[0]);
Scene targetScene = EditorSceneManager.OpenScene(firstScenePath);
// 合并其他场景
for (int i = 1; i < selectedScenes.Length; i++)
{
string scenePath = AssetDatabase.GetAssetPath(selectedScenes[i]);
EditorSceneManager.OpenSceneAdditive(scenePath);
}
// 保存合并后的场景
EditorSceneManager.SaveScene(targetScene);
Debug.Log("场景合并完成");
}
}
#endif
场景事件
场景加载事件
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneEventHandler : MonoBehaviour
{
void OnEnable()
{
// 订阅场景事件
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
SceneManager.activeSceneChanged += OnActiveSceneChanged;
}
void OnDisable()
{
// 取消订阅
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneUnloaded -= OnSceneUnloaded;
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
}
// 场景加载完成时调用
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log($"场景已加载: {scene.name}, 模式: {mode}");
// 根据场景执行初始化
switch (scene.name)
{
case "MainMenu":
InitMainMenu();
break;
case "Level_01":
InitLevel();
break;
}
}
// 场景卸载完成时调用
void OnSceneUnloaded(Scene scene)
{
Debug.Log($"场景已卸载: {scene.name}");
// 清理资源
Resources.UnloadUnusedAssets();
System.GC.Collect();
}
// 活动场景变更时调用
void OnActiveSceneChanged(Scene oldScene, Scene newScene)
{
Debug.Log($"活动场景从 {oldScene.name} 变更为 {newScene.name}");
}
void InitMainMenu()
{
// 主菜单初始化逻辑
}
void InitLevel()
{
// 关卡初始化逻辑
}
}
场景加载完成回调
public class SceneLoadCallback : MonoBehaviour
{
public void LoadSceneWithCallback(string sceneName, System.Action<Scene> onLoaded)
{
StartCoroutine(LoadSceneCoroutine(sceneName, onLoaded));
}
IEnumerator LoadSceneCoroutine(string sceneName, System.Action<Scene> callback)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
yield return operation;
Scene loadedScene = SceneManager.GetSceneByName(sceneName);
callback?.Invoke(loadedScene);
}
}
// 使用示例
public class ExampleUsage : MonoBehaviour
{
void Start()
{
var loader = GetComponent<SceneLoadCallback>();
loader.LoadSceneWithCallback("Level_01", (scene) =>
{
Debug.Log($"{scene.name} 加载完成,开始初始化");
InitializeLevel(scene);
});
}
void InitializeLevel(Scene scene)
{
// 初始化逻辑
}
}
场景状态保存
使用 DontDestroyOnLoad
让对象在场景切换时保持存在:
public class PersistentObject : MonoBehaviour
{
public static PersistentObject Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
管理多个持久化对象
public class PersistentManager : MonoBehaviour
{
public GameObject[] persistentPrefabs;
void Awake()
{
// 检查是否已存在
if (FindObjectsOfType<PersistentManager>().Length > 1)
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
// 实例化其他持久化对象
foreach (var prefab in persistentPrefabs)
{
var instance = Instantiate(prefab);
DontDestroyOnLoad(instance);
}
}
}
场景数据传递
在场景之间传递数据:
// 方式1:使用静态类
public static class SceneData
{
public static int selectedLevel;
public static string playerName;
public static Dictionary<string, object> customData = new Dictionary<string, object>();
public static void Clear()
{
selectedLevel = 0;
playerName = "";
customData.Clear();
}
}
// 方式2:使用 ScriptableObject
[CreateAssetMenu(fileName = "GameSession", menuName = "Game/Session Data")]
public class GameSessionData : ScriptableObject
{
public int currentLevel;
public int score;
public float health;
public void Reset()
{
currentLevel = 0;
score = 0;
health = 100f;
}
}
// 使用
public class GameManager : MonoBehaviour
{
public GameSessionData sessionData;
public void StartGame(int level)
{
sessionData.currentLevel = level;
SceneManager.LoadScene("Game");
}
}
Build Settings 管理
场景构建设置
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
public class BuildSettingsManager
{
// 自动添加所有场景到构建设置
[MenuItem("Build/Auto Add Scenes")]
static void AutoAddScenes()
{
string[] sceneGuids = AssetDatabase.FindAssets("t:Scene", new[] { "Assets/Scenes" });
EditorBuildSettingsScene[] buildScenes = new EditorBuildSettingsScene[sceneGuids.Length];
for (int i = 0; i < sceneGuids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(sceneGuids[i]);
buildScenes[i] = new EditorBuildSettingsScene(path, true);
}
EditorBuildSettings.scenes = buildScenes;
Debug.Log($"已添加 {buildScenes.Length} 个场景到构建设置");
}
// 获取场景构建索引
public static int GetSceneBuildIndex(string sceneName)
{
for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
{
if (EditorBuildSettings.scenes[i].path.Contains(sceneName))
{
return i;
}
}
return -1;
}
}
#endif
运行时获取场景信息
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneBuildInfo : MonoBehaviour
{
void Start()
{
// 获取构建设置中的场景数量
int sceneCount = SceneManager.sceneCountInBuildSettings;
Debug.Log($"构建设置中的场景数量: {sceneCount}");
// 遍历所有场景
for (int i = 0; i < sceneCount; i++)
{
string scenePath = SceneUtility.GetScenePathByBuildIndex(i);
Debug.Log($"场景 {i}: {scenePath}");
}
}
// 检查场景是否在构建设置中
public bool IsSceneInBuildSettings(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
string path = SceneUtility.GetScenePathByBuildIndex(i);
if (path.Contains(sceneName))
{
return true;
}
}
return false;
}
// 获取场景构建索引
public int GetBuildIndex(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
{
string path = SceneUtility.GetScenePathByBuildIndex(i);
if (System.IO.Path.GetFileNameWithoutExtension(path) == sceneName)
{
return i;
}
}
return -1;
}
}
完整场景管理器
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 完整的场景管理器,支持同步/异步加载、加载进度、多场景管理
/// </summary>
public class SceneManagerEx : MonoBehaviour
{
public static SceneManagerEx Instance { get; private set; }
[Header("加载设置")]
public float minimumLoadTime = 1f;
public bool showLoadingScreen = true;
[Header("UI 引用")]
public GameObject loadingScreenPrefab;
// 状态
private bool isLoading = false;
private GameObject loadingScreenInstance;
private string currentSceneName;
// 场景栈(用于返回上一个场景)
private Stack<string> sceneHistory = new Stack<string>();
// 事件
public event System.Action<string> OnSceneLoadStart;
public event System.Action<string, float> OnSceneLoadProgress;
public event System.Action<string> OnSceneLoadComplete;
public event System.Action<string> OnSceneUnloadComplete;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
// 订阅场景事件
SceneManager.sceneLoaded += HandleSceneLoaded;
SceneManager.sceneUnloaded += HandleSceneUnloaded;
}
else
{
Destroy(gameObject);
}
}
void OnDestroy()
{
SceneManager.sceneLoaded -= HandleSceneLoaded;
SceneManager.sceneUnloaded -= HandleSceneUnloaded;
}
#region 公共方法
/// <summary>
/// 同步加载场景
/// </summary>
public void LoadScene(string sceneName)
{
if (isLoading)
{
Debug.LogWarning("正在加载中,请等待");
return;
}
// 记录历史
sceneHistory.Push(currentSceneName);
currentSceneName = sceneName;
OnSceneLoadStart?.Invoke(sceneName);
SceneManager.LoadScene(sceneName);
}
/// <summary>
/// 异步加载场景(带加载画面)
/// </summary>
public void LoadSceneAsync(string sceneName)
{
if (isLoading)
{
Debug.LogWarning("正在加载中,请等待");
return;
}
StartCoroutine(LoadSceneAsyncRoutine(sceneName));
}
/// <summary>
/// 异步加载场景(附加模式)
/// </summary>
public void LoadSceneAdditive(string sceneName, System.Action onComplete = null)
{
StartCoroutine(LoadSceneAdditiveRoutine(sceneName, onComplete));
}
/// <summary>
/// 卸载场景(附加模式)
/// </summary>
public void UnloadSceneAsync(string sceneName, System.Action onComplete = null)
{
StartCoroutine(UnloadSceneRoutine(sceneName, onComplete));
}
/// <summary>
/// 返回上一个场景
/// </summary>
public void GoBack()
{
if (sceneHistory.Count > 0)
{
string previousScene = sceneHistory.Pop();
LoadSceneAsync(previousScene);
}
else
{
Debug.LogWarning("没有上一个场景");
}
}
/// <summary>
/// 重新加载当前场景
/// </summary>
public void ReloadCurrentScene()
{
if (!string.IsNullOrEmpty(currentSceneName))
{
LoadSceneAsync(currentSceneName);
}
}
#endregion
#region 协程
IEnumerator LoadSceneAsyncRoutine(string sceneName)
{
isLoading = true;
// 记录历史
sceneHistory.Push(currentSceneName);
currentSceneName = sceneName;
OnSceneLoadStart?.Invoke(sceneName);
// 显示加载画面
if (showLoadingScreen && loadingScreenPrefab != null)
{
loadingScreenInstance = Instantiate(loadingScreenPrefab);
DontDestroyOnLoad(loadingScreenInstance);
}
// 开始加载
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
operation.allowSceneActivation = false;
float elapsedTime = 0f;
float displayProgress = 0f;
while (!operation.isDone)
{
elapsedTime += Time.deltaTime;
// 计算进度
float actualProgress = Mathf.Clamp01(operation.progress / 0.9f);
displayProgress = Mathf.Lerp(displayProgress, actualProgress, Time.deltaTime * 5f);
// 触发进度事件
OnSceneLoadProgress?.Invoke(sceneName, displayProgress);
// 更新加载画面
UpdateLoadingScreen(displayProgress);
// 确保最短加载时间
if (operation.progress >= 0.9f && elapsedTime >= minimumLoadTime)
{
operation.allowSceneActivation = true;
}
yield return null;
}
// 隐藏加载画面
if (loadingScreenInstance != null)
{
Destroy(loadingScreenInstance);
}
isLoading = false;
OnSceneLoadComplete?.Invoke(sceneName);
}
IEnumerator LoadSceneAdditiveRoutine(string sceneName, System.Action onComplete)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
yield return operation;
onComplete?.Invoke();
}
IEnumerator UnloadSceneRoutine(string sceneName, System.Action onComplete)
{
AsyncOperation operation = SceneManager.UnloadSceneAsync(sceneName);
yield return operation;
// 卸载未使用的资源
yield return Resources.UnloadUnusedAssets();
onComplete?.Invoke();
}
#endregion
#region 事件处理
void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 更新当前场景名
if (mode == LoadSceneMode.Single)
{
currentSceneName = scene.name;
}
}
void HandleSceneUnloaded(Scene scene)
{
OnSceneUnloadComplete?.Invoke(scene.name);
}
#endregion
#region 辅助方法
void UpdateLoadingScreen(float progress)
{
if (loadingScreenInstance == null) return;
// 更新进度条
var progressBar = loadingScreenInstance.GetComponentInChildren<UnityEngine.UI.Slider>();
if (progressBar != null)
{
progressBar.value = progress;
}
// 更新进度文本
var progressText = loadingScreenInstance.GetComponentInChildren<UnityEngine.UI.Text>();
if (progressText != null)
{
progressText.text = $"加载中... {(int)(progress * 100)}%";
}
}
/// <summary>
/// 检查场景是否已加载
/// </summary>
public bool IsSceneLoaded(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
if (SceneManager.GetSceneAt(i).name == sceneName)
{
return true;
}
}
return false;
}
/// <summary>
/// 获取当前场景名
/// </summary>
public string GetCurrentSceneName()
{
return currentSceneName;
}
#endregion
}
// 使用示例
public class SceneLoadExample : MonoBehaviour
{
void Start()
{
// 订阅事件
SceneManagerEx.Instance.OnSceneLoadStart += OnLoadStart;
SceneManagerEx.Instance.OnSceneLoadProgress += OnLoadProgress;
SceneManagerEx.Instance.OnSceneLoadComplete += OnLoadComplete;
}
public void LoadLevel(int levelIndex)
{
SceneManagerEx.Instance.LoadSceneAsync($"Level_{levelIndex:00}");
}
void OnLoadStart(string sceneName)
{
Debug.Log($"开始加载场景: {sceneName}");
}
void OnLoadProgress(string sceneName, float progress)
{
Debug.Log($"加载进度: {progress * 100}%");
}
void OnLoadComplete(string sceneName)
{
Debug.Log($"场景加载完成: {sceneName}");
}
}
最佳实践
1. 场景划分原则
- 按功能划分:菜单、游戏、结算分为不同场景
- 控制场景大小:单个场景不宜过大,影响加载速度和内存
- 合理使用 Additive:共享资源作为独立场景附加加载
2. 加载优化
- 预加载:在合适的时机预加载即将需要的场景
- 异步加载:使用异步加载避免卡顿
- 最小加载时间:设置最小加载时间,让玩家看到加载信息
3. 内存管理
- 及时卸载:不需要的场景及时卸载
- 资源清理:卸载场景后调用
Resources.UnloadUnusedAssets() - 监控内存:使用 Profiler 监控场景切换时的内存变化
4. 用户体验
- 加载进度反馈:提供清晰的加载进度提示
- 加载动画:使用动画过渡让切换更平滑
- 错误处理:处理场景加载失败的情况