9.场景模块
9.1 知识点
为什么需要场景切换模块
在游戏开发中,我们经常需要进行场景切换。只要存在场景切换,我们往往需要在切换场景时和切换场景结束后进行一些操作。
常见需求:
- 切换场景中
- 更新Loading界面进度条
- 显示加载百分比
- 播放加载动画
- 切换场景后
- 隐藏Loading界面
- 动态创建场景中关卡信息:如角色、怪物、场景物件等
- 播放场景背景音乐
- 初始化游戏逻辑
问题分析:
- 场景切换逻辑分散在各个地方,缺乏统一管理
- 无法统一监听加载进度事件
- 异步加载进度获取困难
场景模块实现思路
主要实现思路:
- 制作SceneMgr单例模式管理器:统一管理场景切换
- 实现同步加载场景的方法:封装SceneManager.LoadScene
- 实现异步加载场景的方法:使用协程实现非阻塞加载
- 实现外部获取异步加载场景进度:通过事件中心发送进度事件
设计要点:
- 进度监听:使用协程每帧检测AsyncOperation.progress
- 事件分发:通过EventCenter发送加载进度事件
- 回调机制:加载完成后执行回调函数
- 协程管理:通过MonoMgr启动协程
场景模块实现
源码实现:SceneMgr.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
/// <summary>
/// 场景切换管理器
/// 主要用于切换场景,提供同步和异步加载功能
/// </summary>
public class SceneMgr : Singleton<SceneMgr>
{
#region 构造函数
private SceneMgr() { }
#endregion
#region 同步加载场景
/// <summary>
/// 同步切换场景的方法
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
public void LoadScene(string name, UnityAction callBack = null)
{
// 切换场景
SceneManager.LoadScene(name);
// 调用回调
callBack?.Invoke();
callBack = null;
}
#endregion
#region 异步加载场景
/// <summary>
/// 异步切换场景的方法
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
public void LoadSceneAsyn(string name, UnityAction callBack = null)
{
// 通过MonoMgr启动协程进行异步加载
MonoMgr.Instance.StartCoroutine(ReallyLoadSceneAsyn(name, callBack));
}
/// <summary>
/// 真正的异步加载场景协程
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
private IEnumerator ReallyLoadSceneAsyn(string name, UnityAction callBack)
{
// 开始异步加载场景
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
// 【核心功能】不停地每帧检测是否加载结束
// 如果加载结束就不会进这个循环,每帧执行了
while (!ao.isDone)
{
// 可以在这里利用事件中心每帧将进度发送给想要得到的地方
EventCenter.Instance.EventTrigger<float>(E_EventType.E_SceneLoadChange, ao.progress);
yield return 0;
}
// 【重要】避免最后一帧直接结束了,没有同步1出去
EventCenter.Instance.EventTrigger<float>(E_EventType.E_SceneLoadChange, 1);
// 加载完成,调用回调
callBack?.Invoke();
callBack = null;
}
#endregion
}
场景模块使用测试
创建测试脚本:SceneManagerUsageTest.cs
using UnityEngine;
/// <summary>
/// 场景管理器使用测试
/// 演示场景管理器的所有功能
/// </summary>
public class SceneManagerUsageTest : MonoBehaviour
{
void Start()
{
Debug.Log("场景管理器使用测试:开始");
// 1. 测试同步加载场景
TestSynchronousLoad();
// 2. 测试异步加载场景
TestAsynchronousLoad();
}
/// <summary>
/// 测试同步加载场景
/// </summary>
private void TestSynchronousLoad()
{
Debug.Log("测试同步加载场景");
// 同步加载场景
SceneMgr.Instance.LoadScene("GameScene", () =>
{
Debug.Log("场景加载完成");
// 输出:场景加载完成
// 注意:场景加载完成后,当前场景的对象都会被销毁
// 因此这里的回调在场景切换后不会执行
});
}
/// <summary>
/// 测试异步加载场景
/// </summary>
private void TestAsynchronousLoad()
{
Debug.Log("测试异步加载场景");
// 先监听场景加载进度事件
EventCenter.Instance.AddEventListener<float>(E_EventType.E_SceneLoadChange, OnSceneLoadProgress);
// 显示Loading界面
ShowLoadingPanel();
// 异步加载场景
SceneMgr.Instance.LoadSceneAsyn("GameScene", () =>
{
Debug.Log("场景加载完成");
// 隐藏Loading界面
HideLoadingPanel();
// 移除事件监听
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_SceneLoadChange, OnSceneLoadProgress);
// 可以在这里做场景加载后的初始化工作
// 例如:创建角色、初始化游戏逻辑等
});
}
/// <summary>
/// 场景加载进度回调
/// </summary>
/// <param name="progress">加载进度(0-1)</param>
private void OnSceneLoadProgress(float progress)
{
Debug.Log($"场景加载进度:{progress * 100}%");
// 输出:场景加载进度:0% ... 100%
// 更新进度条
UpdateLoadingProgress(progress);
}
/// <summary>
/// 显示Loading界面
/// </summary>
private void ShowLoadingPanel()
{
Debug.Log("显示Loading界面");
// 实际操作:UIMgr.Instance.ShowPanel<LoadingPanel>();
}
/// <summary>
/// 隐藏Loading界面
/// </summary>
private void HideLoadingPanel()
{
Debug.Log("隐藏Loading界面");
// 实际操作:UIMgr.Instance.HidePanel<LoadingPanel>();
}
/// <summary>
/// 更新加载进度
/// </summary>
/// <param name="progress">加载进度</param>
private void UpdateLoadingProgress(float progress)
{
// 实际操作:更新进度条的显示
// loadingPanel.SetProgress(progress);
}
}
Loading面板实现示例
创建Loading面板:LoadingPanel.cs
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Loading面板
/// 显示场景加载进度
/// </summary>
public class LoadingPanel : BasePanel
{
private Slider progressSlider;
private Text progressText;
protected override void Awake()
{
base.Awake();
// 获取进度条和文本
progressSlider = GetControl<Slider>("progressSlider");
progressText = GetControl<Text>("progressText");
}
private void Start()
{
// 监听场景加载进度事件
EventCenter.Instance.AddEventListener<float>(E_EventType.E_SceneLoadChange, UpdateProgress);
// 初始化进度
UpdateProgress(0f);
}
/// <summary>
/// 更新加载进度
/// </summary>
/// <param name="progress">加载进度(0-1)</param>
private void UpdateProgress(float progress)
{
// 更新进度条
if (progressSlider != null)
{
progressSlider.value = progress;
}
// 更新进度文本
if (progressText != null)
{
progressText.text = $"{(int)(progress * 100)}%";
}
}
/// <summary>
/// 显示面板时的逻辑
/// </summary>
public override void ShowMe()
{
Debug.Log("LoadingPanel显示");
}
/// <summary>
/// 隐藏面板时的逻辑
/// </summary>
public override void HideMe()
{
Debug.Log("LoadingPanel隐藏");
}
private void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_SceneLoadChange, UpdateProgress);
}
}
完整场景切换流程
创建完整的场景切换流程:GameSceneTransition.cs
using UnityEngine;
/// <summary>
/// 游戏场景切换流程
/// 演示完整的场景切换流程
/// </summary>
public class GameSceneTransition : MonoBehaviour
{
void Start()
{
Debug.Log("游戏场景切换流程:开始");
// 开始切换到游戏场景
StartGameScene();
}
/// <summary>
/// 开始游戏场景
/// </summary>
private void StartGameScene()
{
Debug.Log("开始切换到游戏场景");
// 1. 显示Loading面板
UIMgr.Instance.ShowPanel<LoadingPanel>(E_UILayer.System, (panel) =>
{
Debug.Log("Loading面板显示完成");
// 2. 异步加载场景
SceneMgr.Instance.LoadSceneAsyn("GameScene", () =>
{
Debug.Log("游戏场景加载完成");
// 3. 隐藏Loading面板
UIMgr.Instance.HidePanel<LoadingPanel>(true);
// 4. 播放场景背景音乐
MusicMgr.Instance.PlayBKMusic("GameBGM");
// 5. 初始化游戏逻辑
InitializeGameLogic();
});
});
}
/// <summary>
/// 初始化游戏逻辑
/// </summary>
private void InitializeGameLogic()
{
Debug.Log("初始化游戏逻辑");
// 创建角色
CreatePlayer();
// 创建敌人
CreateEnemies();
// 加载关卡数据
LoadLevelData();
}
/// <summary>
/// 创建角色
/// </summary>
private void CreatePlayer()
{
Debug.Log("创建角色");
// 从资源管理器加载角色预设体
ABResMgr.Instance.LoadResAsync<GameObject>("player", "Player", (player) =>
{
GameObject playerObj = Instantiate(player);
playerObj.name = "Player";
Debug.Log("角色创建完成");
});
}
/// <summary>
/// 创建敌人
/// </summary>
private void CreateEnemies()
{
Debug.Log("创建敌人");
// 从资源管理器加载敌人预设体
for (int i = 0; i < 5; i++)
{
ABResMgr.Instance.LoadResAsync<GameObject>("enemy", "Enemy", (enemy) =>
{
GameObject enemyObj = Instantiate(enemy);
enemyObj.name = "Enemy";
Debug.Log("敌人创建完成");
});
}
}
/// <summary>
/// 加载关卡数据
/// </summary>
private void LoadLevelData()
{
Debug.Log("加载关卡数据");
// 实际项目中可以从配置文件加载关卡数据
// LevelMgr.Instance.LoadLevel(levelID);
}
}
场景模块进阶和优化
场景预加载优化
在实际项目中,可以通过预加载场景资源来提高切换速度。
优化思路:
- 资源预加载:在Loading阶段预加载场景所需资源
- 分块加载:将大场景分块加载,提高加载体验
- 场景预热:在后台预热下一个场景的资源
示例代码:
/// <summary>
/// 场景预加载
/// </summary>
/// <param name="sceneName">场景名称</param>
/// <param name="callBack">预加载完成回调</param>
public void PreloadScene(string sceneName, UnityAction callBack = null)
{
MonoMgr.Instance.StartCoroutine(ReallyPreloadScene(sceneName, callBack));
}
private IEnumerator ReallyPreloadScene(string sceneName, UnityAction callBack)
{
// 1. 预加载场景资源
// 根据场景名称预加载该场景所需的资源
// 例如:ABResMgr.Instance.LoadResAsync<GameObject>("scene1", "scene_res1");
// 2. 预加载场景
AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName);
ao.allowSceneActivation = false; // 不激活场景,只加载资源
// 3. 等待加载到90%
while (ao.progress < 0.9f)
{
yield return null;
}
// 4. 预加载完成,调用回调
callBack?.Invoke();
}
场景资源管理
结合资源管理模块,在场景切换时自动清理不用的资源。
使用示例:
/// <summary>
/// 切换场景并清理资源
/// </summary>
/// <param name="sceneName">场景名称</param>
/// <param name="callBack">切换完成回调</param>
public void LoadSceneWithCleanup(string sceneName, UnityAction callBack = null)
{
// 1. 隐藏当前场景的UI
UIMgr.Instance.HideAllPanels();
// 2. 清空对象池
PoolMgr.Instance.ClearPool();
// 3. 清空音效
MusicMgr.Instance.ClearSound();
// 4. 卸载未使用的资源
ResMgr.Instance.UnloadUnusedAssets(() =>
{
// 5. 加载新场景
SceneMgr.Instance.LoadSceneAsyn(sceneName, callBack);
});
}
9.2 知识点代码
SceneMgr.cs(场景管理器)
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
/// <summary>
/// 场景切换管理器
/// 主要用于切换场景,提供同步和异步加载功能
/// 支持加载进度监听和回调机制
/// </summary>
public class SceneMgr : Singleton<SceneMgr>
{
#region 构造函数
private SceneMgr() { }
#endregion
#region 同步加载场景
/// <summary>
/// 同步切换场景的方法
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
public void LoadScene(string name, UnityAction callBack = null)
{
// 切换场景
SceneManager.LoadScene(name);
// 调用回调
callBack?.Invoke();
callBack = null;
}
#endregion
#region 异步加载场景
/// <summary>
/// 异步切换场景的方法
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
public void LoadSceneAsyn(string name, UnityAction callBack = null)
{
// 通过MonoMgr启动协程进行异步加载
MonoMgr.Instance.StartCoroutine(ReallyLoadSceneAsyn(name, callBack));
}
/// <summary>
/// 真正的异步加载场景协程
/// </summary>
/// <param name="name">场景名称</param>
/// <param name="callBack">加载完成后的回调函数</param>
/// <returns>协程迭代器</returns>
private IEnumerator ReallyLoadSceneAsyn(string name, UnityAction callBack)
{
// 开始异步加载场景
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
// 【核心功能】不停地每帧检测是否加载结束
// 如果加载结束就不会进这个循环,每帧执行了
while (!ao.isDone)
{
// 可以在这里利用事件中心每帧将进度发送给想要得到的地方
EventCenter.Instance.EventTrigger<float>(E_EventType.E_SceneLoadChange, ao.progress);
yield return 0;
}
// 【重要】避免最后一帧直接结束了,没有同步1出去
EventCenter.Instance.EventTrigger<float>(E_EventType.E_SceneLoadChange, 1);
// 加载完成,调用回调
callBack?.Invoke();
callBack = null;
}
#endregion
}
LoadingPanel.cs(Loading面板)
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Loading面板
/// 显示场景加载进度
/// 监听场景加载进度事件并更新UI
/// </summary>
public class LoadingPanel : BasePanel
{
#region 字段
/// <summary>
/// 进度条
/// </summary>
private Slider progressSlider;
/// <summary>
/// 进度文本
/// </summary>
private Text progressText;
#endregion
#region Unity生命周期
protected override void Awake()
{
base.Awake();
// 获取进度条和文本
progressSlider = GetControl<Slider>("progressSlider");
progressText = GetControl<Text>("progressText");
}
private void Start()
{
// 监听场景加载进度事件
EventCenter.Instance.AddEventListener<float>(E_EventType.E_SceneLoadChange, UpdateProgress);
// 初始化进度
UpdateProgress(0f);
}
private void OnDestroy()
{
// 移除事件监听
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_SceneLoadChange, UpdateProgress);
}
#endregion
#region 进度更新
/// <summary>
/// 更新加载进度
/// </summary>
/// <param name="progress">加载进度(0-1)</param>
private void UpdateProgress(float progress)
{
// 更新进度条
if (progressSlider != null)
{
progressSlider.value = progress;
}
// 更新进度文本
if (progressText != null)
{
progressText.text = $"{(int)(progress * 100)}%";
}
}
#endregion
#region BasePanel实现
/// <summary>
/// 显示面板时的逻辑
/// </summary>
public override void ShowMe()
{
Debug.Log("LoadingPanel显示");
}
/// <summary>
/// 隐藏面板时的逻辑
/// </summary>
public override void HideMe()
{
Debug.Log("LoadingPanel隐藏");
}
#endregion
}
SceneManagerUsageTest.cs(场景管理器使用测试)
using UnityEngine;
/// <summary>
/// 场景管理器使用测试
/// 演示场景管理器的所有功能
/// </summary>
public class SceneManagerUsageTest : MonoBehaviour
{
void Start()
{
Debug.Log("场景管理器使用测试:开始");
// 测试异步加载场景
TestAsynchronousLoad();
}
/// <summary>
/// 测试异步加载场景
/// </summary>
private void TestAsynchronousLoad()
{
Debug.Log("测试异步加载场景");
// 先监听场景加载进度事件
EventCenter.Instance.AddEventListener<float>(E_EventType.E_SceneLoadChange, OnSceneLoadProgress);
// 显示Loading界面
ShowLoadingPanel();
// 异步加载场景
SceneMgr.Instance.LoadSceneAsyn("GameScene", () =>
{
Debug.Log("场景加载完成");
// 隐藏Loading界面
HideLoadingPanel();
// 移除事件监听
EventCenter.Instance.RemoveEventListener<float>(E_EventType.E_SceneLoadChange, OnSceneLoadProgress);
// 可以在这里做场景加载后的初始化工作
// 例如:创建角色、初始化游戏逻辑等
});
}
/// <summary>
/// 场景加载进度回调
/// </summary>
/// <param name="progress">加载进度(0-1)</param>
private void OnSceneLoadProgress(float progress)
{
Debug.Log($"场景加载进度:{progress * 100}%");
// 输出:场景加载进度:0% ... 100%
// 更新进度条
UpdateLoadingProgress(progress);
}
/// <summary>
/// 显示Loading界面
/// </summary>
private void ShowLoadingPanel()
{
Debug.Log("显示Loading界面");
UIMgr.Instance.ShowPanel<LoadingPanel>(E_UILayer.System, (panel) =>
{
Debug.Log("Loading面板显示完成");
});
}
/// <summary>
/// 隐藏Loading界面
/// </summary>
private void HideLoadingPanel()
{
Debug.Log("隐藏Loading界面");
UIMgr.Instance.HidePanel<LoadingPanel>(true);
}
/// <summary>
/// 更新加载进度
/// </summary>
/// <param name="progress">加载进度</param>
private void UpdateLoadingProgress(float progress)
{
// 实际操作:更新进度条的显示
// LoadingPanel.Instance.SetProgress(progress);
}
}
GameSceneTransition.cs(完整场景切换流程)
using UnityEngine;
/// <summary>
/// 游戏场景切换流程
/// 演示完整的场景切换流程
/// </summary>
public class GameSceneTransition : MonoBehaviour
{
void Start()
{
Debug.Log("游戏场景切换流程:开始");
// 开始切换到游戏场景
StartGameScene();
}
/// <summary>
/// 开始游戏场景
/// </summary>
private void StartGameScene()
{
Debug.Log("开始切换到游戏场景");
// 1. 显示Loading面板
UIMgr.Instance.ShowPanel<LoadingPanel>(E_UILayer.System, (panel) =>
{
Debug.Log("Loading面板显示完成");
// 2. 异步加载场景
SceneMgr.Instance.LoadSceneAsyn("GameScene", () =>
{
Debug.Log("游戏场景加载完成");
// 3. 隐藏Loading面板
UIMgr.Instance.HidePanel<LoadingPanel>(true);
// 4. 播放场景背景音乐
MusicMgr.Instance.PlayBKMusic("GameBGM");
// 5. 初始化游戏逻辑
InitializeGameLogic();
});
});
}
/// <summary>
/// 初始化游戏逻辑
/// </summary>
private void InitializeGameLogic()
{
Debug.Log("初始化游戏逻辑");
// 创建角色
CreatePlayer();
// 创建敌人
CreateEnemies();
// 加载关卡数据
LoadLevelData();
}
/// <summary>
/// 创建角色
/// </summary>
private void CreatePlayer()
{
Debug.Log("创建角色");
// 从资源管理器加载角色预设体
ABResMgr.Instance.LoadResAsync<GameObject>("player", "Player", (player) =>
{
GameObject playerObj = Instantiate(player);
playerObj.name = "Player";
Debug.Log("角色创建完成");
});
}
/// <summary>
/// 创建敌人
/// </summary>
private void CreateEnemies()
{
Debug.Log("创建敌人");
// 从资源管理器加载敌人预设体
for (int i = 0; i < 5; i++)
{
ABResMgr.Instance.LoadResAsync<GameObject>("enemy", "Enemy", (enemy) =>
{
GameObject enemyObj = Instantiate(enemy);
enemyObj.name = "Enemy";
Debug.Log("敌人创建完成");
});
}
}
/// <summary>
/// 加载关卡数据
/// </summary>
private void LoadLevelData()
{
Debug.Log("加载关卡数据");
// 实际项目中可以从配置文件加载关卡数据
// LevelMgr.Instance.LoadLevel(levelID);
}
}
SceneTransitionWithCleanup.cs(场景切换并清理资源)
using UnityEngine;
/// <summary>
/// 场景切换并清理资源
/// 演示如何在场景切换时清理资源
/// </summary>
public class SceneTransitionWithCleanup : MonoBehaviour
{
void Start()
{
Debug.Log("场景切换并清理资源测试:开始");
// 切换场景并清理资源
SwitchSceneWithCleanup();
}
/// <summary>
/// 切换场景并清理资源
/// </summary>
private void SwitchSceneWithCleanup()
{
Debug.Log("准备切换场景并清理资源");
// 1. 隐藏当前场景的UI
UIMgr.Instance.HideAllPanels();
Debug.Log("隐藏所有UI面板");
// 2. 清空音效
MusicMgr.Instance.ClearSound();
Debug.Log("清空所有音效");
// 3. 显示Loading面板
UIMgr.Instance.ShowPanel<LoadingPanel>(E_UILayer.System, (panel) =>
{
Debug.Log("Loading面板显示");
// 4. 卸载未使用的资源
ResMgr.Instance.UnloadUnusedAssets(() =>
{
Debug.Log("资源卸载完成");
// 5. 加载新场景
SceneMgr.Instance.LoadSceneAsyn("NextScene", () =>
{
Debug.Log("场景切换完成");
// 6. 隐藏Loading面板
UIMgr.Instance.HidePanel<LoadingPanel>(true);
});
});
});
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com