最终效果
前言
观看本文后,我的希望你对unity场景管理有更好的理解,并且能够制作具有巨大世界
的游戏并无缝加载游戏
的各个部分在后台运行而不中断游戏玩法
,这种方法非常灵活,而且很容易实现,因此无论您是制作小型平台游戏还是大型开放世界游戏,它应该都适用于两者,准备好让我们开始吧!
一、绘制不同的场景
创建不同场景
开始菜单界面场景Menu
主场景 Main,就简单放置个主角人物
房间1场景Room1,简单放置个平台,记得去除摄像机
二、切换场景加载进度
1. 简单实现
新增MainMenuManager 代码,实现加载进度,场景切换和加载
public class MainMenuManager : MonoBehaviour { [SerializeField, Header("加载进度条的父对象")] private GameObject _loadingBarObject; [SerializeField, Header("加载进度条的图像")] private Image _loadingBar; [SerializeField, Header("需要隐藏的对象")] private GameObject[] _objectsToHide; [Space] [SerializeField, Header("主要持久化场景的名称")] private string _persistentGameplay = "Main"; [SerializeField, Header("游戏关卡场景的名称")] private string _levelScene = "Room1"; private List<AsyncOperation> _scenesToLoad = new List<AsyncOperation>(); // 存储待加载场景的列表 private void Awake() { _loadingBarObject.SetActive(false); // 禁用加载进度条 } //开始游戏 public void StartGame() { HideMenu(); _loadingBarObject.SetActive(true); // 启用加载进度条 // 加载持久化场景和游戏关卡场景,并将它们添加到待加载场景列表中 _scenesToLoad.Add(SceneManager.LoadSceneAsync(_persistentGameplay)); _scenesToLoad.Add(SceneManager.LoadSceneAsync(_levelScene, LoadSceneMode.Additive)); StartCoroutine(ProgressLoadingBar()); // 启动异步加载进度条的协程 } // 隐藏界面 private void HideMenu() { for (int i = 0; i < _objectsToHide.Length; i++) { _objectsToHide[i].SetActive(false); // 隐藏需要隐藏的对象 } } //异步加载进度条的协程 private IEnumerator ProgressLoadingBar() { float loadProgress = 0f; // 总的加载进度 for (int i = 0; i < _scenesToLoad.Count; i++) { while (!_scenesToLoad[i].isDone) { loadProgress += _scenesToLoad[i].progress; // 累加加载进度 _loadingBar.fillAmount = loadProgress / _scenesToLoad.Count; // 更新加载进度条的显示 yield return null; // 等待下一帧 } } } }
挂载脚本,配置参数
配置开始游戏点击事件
被忘记在项目设置加入新的场景
效果
2. 优化
正常我们都是按场景名称或者索引去跟踪我们的场景吗,这里其实有一个更好的方法,之后在所有的项目中我们都可以去使用它
灵感来源于一篇Unity论坛的SceneField代码:
https://discussions.unity.com/t/inspector-field-for-scene-asset/40763
解释:这是代码通过使用SceneField类和SceneFieldPropertyDrawer属性绘制器,开发者可以在自定义的脚本中方便地引用和管理场景对象,并在Inspector面板中进行编辑和选择操作。这对于需要频繁切换场景或者处理多个场景的情况非常有用。
我已经复制代码下来,并加入了一些注释,如下
using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif [System.Serializable] public class SceneField { [SerializeField] private Object m_SceneAsset; [SerializeField] private string m_SceneName = ""; public string SceneName { get { return m_SceneName; } } // 使其与现有的Unity方法(LoadLevel / LoadScene)兼容 public static implicit operator string(SceneField sceneField) { return sceneField.SceneName; } } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(SceneField))] public class SceneFieldPropertyDrawer : PropertyDrawer { public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label) { EditorGUI.BeginProperty(_position, GUIContent.none, _property); SerializedProperty sceneAsset = _property.FindPropertyRelative("m_SceneAsset"); SerializedProperty sceneName = _property.FindPropertyRelative("m_SceneName"); _position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label); if (sceneAsset != null) { // 显示场景选择器,让用户选择一个场景 sceneAsset.objectReferenceValue = EditorGUI.ObjectField(_position, sceneAsset.objectReferenceValue, typeof(SceneAsset), false); // 如果已经选择了场景,则将场景名称保存在场景名称变量中 if (sceneAsset.objectReferenceValue != null) { sceneName.stringValue = (sceneAsset.objectReferenceValue as SceneAsset).name; } } EditorGUI.EndProperty(); } } #endif
修改MainMenuManager
[SerializeField, Header("主要持久化场景")] private SceneField _persistentGameplay; [SerializeField, Header("游戏关卡场景")] private SceneField _levelScene;
配置,场景可以通过拖入绑定了
效果,还是和前面一样,没什么问题
三、角色移动和跳跃控制
简单实现一下角色的控制
public class PlayerController : MonoBehaviour { private Rigidbody2D rb; // 刚体组件 public float speed, jumpForce; // 移动速度和跳跃力度 public Transform groundCheck; // 地面检测点 public LayerMask ground; // 地面图层 public bool isGround; // 是否在地面上 bool jumpPressed; // 是否按下跳跃键 //检测范围 public float checkRadius; void Start() { rb = GetComponent<Rigidbody2D>(); // 获取刚体组件 } void Update() { if (Input.GetButtonDown("Jump")) { jumpPressed = true; } } private void FixedUpdate() { isGround = Physics2D.OverlapCircle(groundCheck.position, checkRadius, ground); // 检测是否在地面上 GroundMovement(); // 地面移动 Jump(); // 跳跃 } //画出的射线可以看见 private void OnDrawGizmosSelected() { if (groundCheck != null) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(groundCheck.position, checkRadius); } } void GroundMovement() { float horizontal = Input.GetAxisRaw("Horizontal"); // 获取水平方向输入 rb.velocity = new Vector2(horizontal * speed, rb.velocity.y); // 设置刚体速度 if (horizontal != 0) { transform.localScale = new Vector3(horizontal, 1, 1); // 翻转角色 } } void Jump() { if (jumpPressed && isGround) { rb.velocity = new Vector2(rb.velocity.x, jumpForce); // 设置刚体速度 jumpPressed = true; } } }
挂载脚本,记得配置地面图层为Ground
效果
四、添加虚拟摄像机
效果
五、触发器动态加载场景
新增其他房间场景
新增SceneLoadTrigger ,定义触发器检测异步加载和卸载不同场景
public class SceneLoadTrigger : MonoBehaviour { [SerializeField, Header("需要加载的场景数组")] private SceneField[] _scenesToLoad; [SerializeField, Header("需要卸载的场景数组")] private SceneField[] _scenesToUnload; private GameObject _player; // 玩家对象 private void Awake() { _player = GameObject.FindGameObjectWithTag("Player"); // 查找并赋值玩家对象 } private void OnTriggerEnter2D(Collider2D collision) { if (collision.gameObject == _player) // 碰撞体为玩家时 { LoadScenes(); UnloadScenes(); } } // 加载场景 private void LoadScenes() { for (int i = 0; i < _scenesToLoad.Length; i++) { bool isSceneLoaded = false; for (int j = 0; j < SceneManager.sceneCount; j++) { Scene loadedScene = SceneManager.GetSceneAt(j); if (loadedScene.name == _scenesToLoad[i].SceneName) { isSceneLoaded = true; break; } } if (!isSceneLoaded) { SceneManager.LoadSceneAsync(_scenesToLoad[i].SceneName, LoadSceneMode.Additive); // 异步加载需要加载的场景 } } } // 卸载场景 private void UnloadScenes() { for (int i = 0; i < _scenesToUnload.Length; i++) { for (int j = 0; j < SceneManager.sceneCount; j++) { Scene loadedScene = SceneManager.GetSceneAt(j); if (loadedScene.name == _scenesToUnload[i].SceneName) { SceneManager.UnloadSceneAsync(_scenesToUnload[i].SceneName); // 异步卸载需要卸载的场景 } } } } }
挂载脚本,配置参数,记得配置角色标签为Player,地面我设置了不同颜色,好做区分
运行效果
六、最终效果
可以看到,只要配置得当就可以做到场景地图无缝加载,即使是很大的地图,我们也可以把他分割成几个小场景,不影响游戏运行性能
参考
【视频】https://www.youtube.com/watch?v=6-0zD9Xyu5c