【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)

文章目录

  • 最终效果
  • 前言
  • 一、绘制不同的场景
  • 二、切换场景加载进度
    • 1. 简单实现
    • 2. 优化
  • 三、角色移动和跳跃控制
  • 四、添加虚拟摄像机
  • 五、触发器动态加载场景
  • 六、最终效果
  • 参考
  • 源码
  • 完结

最终效果

在这里插入图片描述

前言

观看本文后,我的希望你对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

源码

源码整理好了我会放上来,催更加急

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/189597.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java抽象类和接口(1)

&#x1f435;本篇文章将对抽象类和接口相关知识进行讲解 一、抽象类 先来看下面的代码&#xff1a; class Shape {public void draw() {System.out.println("画");} } class Cycle extends Shape {public void draw() {System.out.println("圆形");} } …

飞翔的鸟小游戏

第一步是创建项目 项目名自拟 第二步创建个包名 来规范class 再创建一个包 来存储照片 如下&#xff1a; package game; import java.awt.*; import javax.swing.*; import javax.imageio.ImageIO;public class Bird {Image image;int x,y;int width,height;int size;doub…

一个超强算法模型实战案例!

哈喽,大家周末愉快,今儿不了很多的原理性内容。准备和大家一起实现一个开源且重要的项目:MNIST数字分类机器学习。 大概介绍下:MNIST数字分类项目旨在使用机器学习技术来构建一个模型,能够自动识别手写数字的图像。这个项目是一个经典的图像分类任务,常用于入门级机器学…

基于51单片机超市快递寄存自动柜设计源程序

一、系统方案 1、本设计采用这51单片机作为主控器。 2、存包&#xff0c;GSM短信取件码。 3、液晶1620显示。 4、矩阵键盘输入取件码&#xff0c;完成取包。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /******************************…

MFC 绘制单一颜色三角形、渐变颜色边框三角形、渐变填充三角形、正弦函数曲线实例

MFC 绘制三种不同圆形以及绘制正弦函数曲线 本文使用visual Studio MFC 平台实现绘制单一颜色圆形、渐变颜色边框圆形、渐变填充圆形以及绘制三角函数正弦函数曲线. 关于基础工程的创建请参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-vis…

吉他初学者学习网站搭建系列(1)——目录

文章目录 背景文章目录功能网站地址网站展示展望 背景 这个系列是对我最近周末搭建的吉他工具类平台YUERGS的总结。我个人业余爱好是自学吉他&#xff0c;我会在这个平台中动手集成我认为很有帮助的一些工具&#xff0c;来提升我的吉他水平和音乐素养&#xff0c;希望也可以帮…

C++ day39 动态规划 不同路径 不同路径Ⅱ

题目1&#xff1a;62 不同路径 题目链接 &#xff1a;不同路径 对题目的理解 机器人位于m*n的网格中的左上角start,求解走到网格右下角finish的移动路径 动规五部曲 1&#xff09;dp数组的含义以及下标i的含义 dp[i][j]&#xff1a;从start&#xff08;0&#xff0c;0&…

交换机的VRRP主备配置例子

拓朴如下&#xff1a; 主要配置如下&#xff1a; [S1] vlan batch 10 20 # interface Vlanif10ip address 10.1.1.1 255.255.255.0vrrp vrid 1 virtual-ip 10.1.1.254vrrp vrid 1 priority 200vrrp vrid 1 preempt-mode timer delay 20 # interface Vlanif20ip address 13.1.1…

CH01_适应设计模式

Iterator模式&#xff08;迭代器模式&#xff09; 迭代器模式&#xff08;Iterator&#xff09;,提供一种方法&#xff0c;顺序访问一个聚合对象中各个元素&#xff0c;而不是暴露该对象的内部表示。 类图结构 说明 Iterator&#xff08;迭代器&#xff09; 该角色负责定义按…

保护您的IP地址:预防IP地址盗用的关键措施

随着互联网的发展&#xff0c;IP地址作为标识互联网设备的重要元素&#xff0c;成为网络通信的基石。然而&#xff0c;IP地址盗用威胁正不断增加&#xff0c;可能导致敏感信息泄露、未经授权的访问和网络攻击。本文将介绍一些有效的方法&#xff0c;以帮助组织和个人预防IP地址…

2023年亚太杯数学建模A题——深度学习苹果图像识别(思路+模型+代码+成品)

Image Recognition for Fruit-Picking Robots 水果采摘机器人的图像识别功能 问题 1&#xff1a;计数苹果 根据附件 1 中提供的可收获苹果的图像数据集&#xff0c;提取图像特征&#xff0c;建立数学模型&#xff0c;计算每幅图像中的苹果数量&#xff0c;并绘制附件 1 中所有…

Vue框架学习笔记——事件scroll和wheel的区别

文章目录 前文提要滚动条滚动事件 scroll鼠标滚动事件 wheel二者不同点 前文提要 本人仅做个人学习记录&#xff0c;如有错误&#xff0c;请多包涵 滚动条滚动事件 scroll scroll事件绑定html页面中的指定滚动条&#xff0c;无论你拖拽滚动条&#xff0c;选中滚动条之后按键盘…

ubuntu下配置qtcreator交叉编译环境

文章目录 安装交叉编译工具安装qt creator开发环境配置交叉编译示例demo参考 安装交叉编译工具 安装qt creator开发环境 1 官网 2 填写信息 3 下载 默认没有出现Qt5.15版本 WISONIC\80081001ub16-1001:~$ /opt/Qt/Tools/QtCreator/bin/qtcreator /opt/Qt/Tools/QtCreat…

与Windows 10更新大同小异!一步一步教你如何更新Windows 11

如果你想让你的Windows 11设备获得最佳性能&#xff0c;那么定期更新是至关重要的。即使是最好的电脑如果不更新也会受到影响&#xff0c;因为更新会应用软件调整&#xff0c;帮助你的设备更快、更平稳地运行。它还提高了安全性&#xff0c;意味着你可以从Microsoft的最新功能中…

chrome driver 截图和填表

昨天突然有一个需求&#xff08;自己的&#xff09;&#xff0c;想把某个网站题目主体部分翻译并保存成图片&#xff0c;开始时用了国内网站的翻译&#xff08;人工、简单翻译&#xff09;&#xff0c;后来发现很多地方翻译的不尽人意&#xff0c;于是只好用翻译插件对原始网站…

机器学习【03】在本地浏览器使用远程服务器的Jupyter Notebook【conda环境】

1.激活虚拟环境 conda activate 虚拟环境名字2.虚拟环境下安装jupyter notebook pip install jupyter3.配置 jupyter 文件 在 Jupyter Notebook 的配置目录中生成一个配置文件 jupyter_notebook_config.py jupyter notebook --generate-config3.设置密码 jupyter notebook …

webshell之内置函数免杀

原始webshell 查杀的点在于Runtime.getRuntime().exec非常明显的特征 利用ProcessBuilder替换Runtime.getRuntime().exec(cmd) Runtime.getRuntime().exec(cmd)其实最终调用的是ProcessBuilder这个函数&#xff0c;因此我们可以直接利用ProcessBuilder来替换Runtime.getRunti…

梯度详解与优化实战

什么是梯度 对所有自变量求偏微分构成的向量&#xff0c;它是一个向量&#xff08;有大小和函数值增长方向&#xff09; 导数是一个标量 找最小值点坐标的案例 import torchimport numpy as np import matplotlib.pyplot as plt def himmelblau(x):return (x[0]**2x[1]-11)…

再见 Pandas,再见算法

大家好,《再见pandas》 系列已有200多位朋友加入学习了,这段时间亲眼见证了很多朋友的飞跃进步,从无到有,从一个问问题的小白到开始慢慢回答别人的问题,在讨论和练习中不断成长。虽说pandas已经很普及了,但普及内容的深度却远远不够。 下面这套原创图文是我和几位小伙伴…