[Unity Practical Combat] Switching scene loading progress and how to asynchronously load game scenes with a huge world in the background to achieve seamless connection (project source code attached)

final effect

Insert image description here

Preface

After watching this article, I hope you have a better understanding of unity scene management and can make games with 巨大世界 and 无缝加载游戏 Partially running in the background, this approach is very flexible and easy to implement, so whether you're making a small platformer or a large open world game, it should work for both, so be prepared let's start! 不中断游戏玩法

1. Draw different scenes

Create different scenarios
Insert image description here

Start menu interface scene Menu
Insert image description here
Main scene Main, simply place a protagonist character
Insert image description here
Room 1 scene Room1, simply place a platform, remember to remove it Camera
Insert image description here

2. Switch scene loading progress

1. Simple implementation

Added MainMenuManager code to implement loading progress, scene switching and loading

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; // 等待下一帧
            }           
        }
    }
}

Mount script, configuration parameters

Insert image description here
Configure start game click event
Insert image description here
Forgot to add new scene in project settings
Insert image description here
Effect
Insert image description here

2. Optimization

Normally we track our scenes by scene name or index? There is actually a better method here, and we can use it in all projects later.

Inspired by a Unity forum SceneField code:
https://discussions.unity.com/t/inspector-field-for-scene-asset/40763 a>
Insert image description here
Explanation: This is the code. By using the SceneField class and SceneFieldPropertyDrawer property painter, developers can easily reference and manage scene objects in custom scripts, and edit and select in the Inspector panel. This is very useful for situations where you need to switch scenes frequently or deal with multiple scenes.

I have copied the code and added some comments as follows

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

Modify MainMenuManager

[SerializeField, Header("主要持久化场景")] private SceneField _persistentGameplay;
[SerializeField, Header("游戏关卡场景")] private SceneField _levelScene;

Configuration, the scene can be bound by dragging in
Insert image description here
The effect is still the same as before, no problem
Insert image description here

3. Character Movement and Jump Control

Simply implement character control

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;
        }
    }
}

Mount the script, remember to configure the ground layer as Ground
Insert image description here

Effect
Insert image description here

4. Add virtual camera

Insert image description here
Effect
Insert image description here

5. Trigger dynamic loading scene

Add other room scenes
Insert image description here
Add SceneLoadTrigger to define triggers to detect asynchronous loading and unloading of different scenes

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); // 异步卸载需要卸载的场景
                }
            }
        }
    }
}

Mount the script and configure parameters. Remember to configure the role label as Player. I have set different colors for the ground to make it easier to distinguish
Insert image description here
Running effect
Insert image description here

6. Final effect

It can be seen that as long as the configuration is appropriate, the scene map can be loaded seamlessly. Even if it is a large map, we can divide it into several small scenes without affecting the game running performance.
Insert image description here

reference

【Video】https://www.youtube.com/watch?v=6-0zD9Xyu5c

Source code

https://gitcode.net/unity1/unity-bigscene
Insert image description here

end

Giving roses to others will leave a lingering fragrance in your hands! If the content of the article is helpful to you, please don't be stingy with your 点赞评论和关注 so that I can receive feedback as soon as possible. Every time you 支持 The greatest motivation for creation. Of course, if you find 存在错误 or 更好的解决方法 in the article, you are welcome to comment and send me a private message!

Good, I am向宇,https://xiangyu.blog.csdn.net

A developer who has been working quietly in a small company recently started studying Unity by himself out of interest. If you encounter any problems, you are also welcome to comment and send me a private message. Although I may not necessarily know some of the questions, I will check the information from all parties and try to give the best suggestions. I hope to help more people who want to learn programming. People, encourage each other~
Insert image description here

Guess you like

Origin blog.csdn.net/qq_36303853/article/details/134576923