当前位置: 首页 > news >正文

1.17 从0开始学习Unity游戏开发--场景切换

前面的所有文章我们都在一个固定的游戏场景内进行开发,在最开始介绍场景这个概念的时候就已经提及,这个场景可以是一张地图,或者是一个对战房间等等,所以显然这个场景可以有多个,并且可以从一个场景切换到另外一个场景,那么在Unity中如何进行场景切换,以及如何处理好场景切换时的各个逻辑呢,本章就会详细讲解。

新建第二个场景

还记得最早讲的如何创建场景资源吗?

在Project窗口里面随便哪个你喜欢的位置右键Create->Scene就可以创建一个新的场景资源,我们已经有了一个Demo场景,那么我们创建一个新的场景叫AnotherDemo

OK,接下来我们需要编辑这个场景的内容,那就是双击这个场景资源文件,注意如果当前打开的场景比如你现在打开的是Demo,然后你有一些修改没有保存(比如Demo的Hierarchy窗口里面根节点是带星号的),那么Unity会提示你要不要保存,你自己决定要不要保存,然后才会打开刚刚双击的场景文件。

双击过后,我们就进入到了这个场景里面,一切都回到了原点:

唯独不同的是,Hierarchy窗口的根节点换成了我们新创建的名字AnotherDemo。

ok,这样就理解了如何创建新的场景,并进去编辑内容,我们先换回到Demo场景中,因为我们接下来需要从Demo场景通过代码逻辑切换到AnotherDemo。

场景切换

既然我们要切换场景,肯定是要有什么逻辑触发,我们可以单独写个组件加到空的GameObject上,组件就在Start触发后3秒进行场景切换,而场景切换的API则是:

https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html

我们让gpt帮我们写个:

using UnityEngine;
using UnityEngine.SceneManagement;public class SceneSwitcher : MonoBehaviour
{public string sceneName;public float delay = 3f;private float startTime = 0f;private bool isSwitching = false;private void Start(){startTime = Time.time;}private void Update(){if (!isSwitching){float elapsedTime = Time.time - startTime;if (elapsedTime >= delay){SceneManager.LoadScene(sceneName);isSwitching = true;}}}
}

写的还不错,delay表示物体开始跑起来后等待多少秒执行场景切换,sceneName则是我们的场景资源文件的名字,Unity相当于收集了我们所有场景文件资源,所以可以通过直接传入名字来切换,聪明的你肯定会想到,如果重名了怎么办,毕竟文件在不同的文件夹下可以拥有一样的名字,Unity的API也给出了详细的说明:

The given sceneNamecan either be the Scene name only, without the .unityextension, or the path as shown in the BuildSettings window still without the .unityextension. If only the Scene name is given this will load the first Scene in the list that matches. If you have multiple Scenes with the same name but different paths, you should use the full path.
Note that sceneName is case insensitive, except when you load the Scene from an AssetBundle.

说的就比较清楚,不要带文件名后缀,我们知道场景资源文件实际在操作系统下的文件名后缀是.unity,我们不需要传入这个后缀,例如就是AnotherDemo,不需要AnotherDemo.unity,如果只传了个名字,会使用第一个找到的,如果传的是路径,则按照路径去找,而路径则是Assets文件夹下的路径,例如Scenes/SampleScene(当然我们创建的并没有在这个文件夹下面)

最后注意这里的路径或者名字是大小写不敏感的,除非是从资源包里面加载,至于资源包是啥,我们后面资源加载的篇章再来聊。

那么我们这里sceneName可以有两种填法:

  1. 直接填AnotherDemo
  2. 填路径AnotherDemo

这两个都没问题,但是我们先创建一个物体把我们的组件挂上去:

Ok,我们跑起来看看,哎,为什么没切换呢?不要着急,有没有注意到编辑器界面底部有一条红色的错误信息?打开Console窗口查看全部的输出:

其实可以看到,说的就是我们需要切换的场景AnotherDemo并没有加入到build settings中,我们在游戏打包的那篇中,有提到过如何将场景加入到我们的打包内容中,其实就是这个操作。

我们从Unity编辑器窗口顶部的菜单File->Build Settings打开:

我们确实没有把AnotherDemo加入进去,那我们还是按照老办法,打开AnotherDemo这个场景文件,然后点击上面这个窗口的右边按钮Add Open Scenes,把场景加进去:

Ok,我们重新打开Demo场景,然后再跑一下,等待3秒后,确实切换到AnotherDemo中一篇虚无的世界了。

场景切换的物体保持

我们成功的切换了之后,可以很显然的理解,旧场景里面的所有GameObject都被自动销毁了,这很好,但是有一些GameObject我们是希望跨场景的,最常见的就是UI,很显然我们不能因为切换了一个场景就导致我们的UI全部消失。

那么我们需要将需要在场景切换后仍然保持存在的GameObject使用特殊API进行标记:

Object.DontDestroyOnLoad​docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html

没错,就是这么简单,我们只需要用这个DontDestoryOnLoad来传入我们需要保持的GameObject即可。我们修改一下场景切换的脚本,允许我们设置哪些GameObject可以被保持:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class SceneSwitcher : MonoBehaviour
{public string sceneName;public float delay = 3f;public List<GameObject> gameObjectToStayAlive;private float startTime = 0f;private bool isSwitching = false;private void Start(){startTime = Time.time;}private void Update(){if (!isSwitching){float elapsedTime = Time.time - startTime;if (elapsedTime >= delay){if (gameObjectToStayAlive != null){foreach (GameObject go in gameObjectToStayAlive){DontDestroyOnLoad(go);}}SceneManager.LoadScene(sceneName);isSwitching = true;}}}
}

可以看到,我们新增了一个GameObject数组gameObjectToStayAlive来存放我们希望切换场景时会继续带到下个场景的GameObject,在切换场景之前我们通过DontDestroyOnLoad来设置这些GameObject不要因为场景切换而销毁。

然后我们可以看到编辑器面板上就能通过点击数组的加减符号来新增一个数组元素:

这其实是Unity编辑器默认给数组成员画的一个更方便一点的编辑方法,如果用过更老一些版本的Unity应该能知道以前的编辑方法很落后,不好用。

这里我们将UI显示所需的元素加进去,注意父元素设置DontDestroyOnLoad对子元素也是生效的,所以我们这里没必要将Canvas物体下的所有子元素都加进去。

Ok,那我们跑起来看看?

切换场景后,我们看到UI还能生效,并且可以观察到,我们设置过的GameObject都跑到了一个奇怪的地方:

他们都跑到了一个DontDestroyOnLoad下面,这其实就是Unity实现DontDestroyOnLoad方案,也就是我们实际打开了两个场景,一个是AnotherDemo场景,一个是DontDestroyOnLoad场景,这两个场景同时生效,而我们设置了DontDestroyOnLoad的GameObject会待在DontDestroyOnLoad场景内,所以即使我们切换其他场景也不会影响在DontDestroyOnLoad场景内的物体的销毁逻辑。

不过现在我们发现了另外一个问题,Console窗口里面疯狂报错:

看起来都是同一个错误,仔细看一下,其实就能理解,这个是我们的UI准心上,挂了一个AimController组件,这个组件有引用场景中的相机来确定我们开火的方向,而我们切换场景了,之前引用的Demo场景里面的相机已经不复存在,那么我们继续引用的话,肯定就报错了。同理的还有FireController组件的引用也是一样,我们切换了场景后,这个被引用的组件所在的GameObject也已经销毁了。

所以我们要解决这个问题的话,就需要在切换场景的时候清理掉旧的引用,并且做好判空的逻辑:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class SceneSwitcher : MonoBehaviour
{public string sceneName;public float delay = 3f;public List<GameObject> gameObjectToStayAlive;public AimController aimController;private float startTime = 0f;private bool isSwitching = false;private void Start(){startTime = Time.time;}private void Update(){if (!isSwitching){float elapsedTime = Time.time - startTime;if (elapsedTime >= delay){if (gameObjectToStayAlive != null){foreach (GameObject go in gameObjectToStayAlive){DontDestroyOnLoad(go);}}if (aimController != null){aimController.mainCamera = null;aimController.fireController = null;}SceneManager.LoadScene(sceneName);isSwitching = true;}}}
}

我们朴素的加了一个aimController成员,然后期望有人能在编辑器里面赋值好这个组件,这样我们在场景切换的时候就能够顺利的清理掉带不走的引用。

同理,我们在AimController脚本里面也需要对两个成员进行判空处理:

using UnityEngine;public class AimController : MonoBehaviour
{public Camera mainCamera;public FireController fireController;private RectTransform rectTransform;private void Start(){rectTransform = GetComponent<RectTransform>();}private void Update(){rectTransform.anchoredPosition = Input.mousePosition;if (mainCamera == null || fireController == null){return;}// 获取屏幕上当前鼠标位置(也就是准心位置)所在的3D空间位置Vector3 aimWorldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y, mainCamera.nearClipPlane));// 通过坐标相减可以得到方向向量Vector3 fireDirection = aimWorldPosition - mainCamera.transform.position;// 归一化后传递给开火控制脚本fireController.SetDirection(fireDirection.normalized);}
}

Ok,那我们在场景里面赋值一下aimController:

再跑一下,错误消失。

从这里我们就可以看到,场景切换保持物体的存在虽然用起来很简单,但是实际上我们仍然需要小心的管理所有对于旧场景的引用,并且所有设置了DontDestroyOnLoad的物体,已经不会因为切换场景而销毁,所以理所当然的需要我们自己管理什么时候去Destroy它。

思考题

  1. 我们切换场景后,抛弃了旧场景里面的相机和FireController,但是我们在新场景里面肯定也是希望可以瞄准+射击的,新场景里面已经有了一个自带的Main Camera,所以现在我们该怎么做来让切换场景后也能正常射击?
  2. 在上面代码里面,我们简单的加入了一个AimController来实现清理旧引用的逻辑,但是作为一个合格的程序员肯定第一时间想到的是职责单一的设计原则,所以想想看,一个合理的解耦写法应该是如何?
  3. 在切换场景的API的官方说明里面,其实已经有明确的提示,这个切换API是阻塞同步操作,应该使用异步API,那么如果使用异步API,我们又应该如何改写代码呢?

下一章

本章我们详细的了解了一下如何切换场景以及切换场景如果要保持GameObject存活要怎么做,以及这么做了之后遇到的一些现实问题。

切换场景的时候其实已经从官方文档中开始了解到有AssetBundle这样的字眼,其实这就是Unity在正式游戏包中的资源管理办法,当我们需要动态加载资源而不是像现在都是引用来搞定的时候,就需要动态加载资源了,所以下一章我们将会讲解Unity内部动态加载资源的几种办法和适用场景。

相关文章:

1.17 从0开始学习Unity游戏开发--场景切换

前面的所有文章我们都在一个固定的游戏场景内进行开发&#xff0c;在最开始介绍场景这个概念的时候就已经提及&#xff0c;这个场景可以是一张地图&#xff0c;或者是一个对战房间等等&#xff0c;所以显然这个场景可以有多个&#xff0c;并且可以从一个场景切换到另外一个场景…...

【golang学习笔记】——(五)Go格式化统一代码风格

我们在入职一家新公司的时候&#xff0c;除了要学习公司的流程规范和规章制度&#xff0c;还会做的一件事情就是进行公司编码规范的学习&#xff0c;基于google的C规范下&#xff0c;做了各自的发散和规范&#xff0c;久而久之就是包罗万象的样子&#xff0c;疲于应付各种规范约…...

CAD转SHP最好的方法 赶快收藏起来吧

1、利用 ArcToolsbox 工具先将 DWG 文件转为 MDB 通过 CASS 软件生成的 DWG 文件&#xff0c;字段中包含有很多属性内容&#xff0c;所以我们先将 DWG 格式 的文件转换为 MDB 格式&#xff0c;再通过 MDB 转换为 SHP 格式数据进行整理。具体步骤如下&#xff1a; 通过 ArcTool…...

PyQt PyQt5 Python VTK Gui Actor 选中 高亮显示 actor

前言&#xff1a; 本文主要介绍了如何使用Python VTK高亮显示actor&#xff0c;使用Python语言&#xff0c;高亮显示选中的actor。当窗口中的圆球actor被选中时&#xff0c;会变成红色&#xff0c;并且会显示actor三遍面片边缘信息。 效果&#xff1a; VTK VTK&#xff0c;&…...

TCP和UDP通信对比

tcp通信流程 服务器: 创建流式套接字 绑定 监听 提取 读写 关闭 客户端: 创建流式套接字 连接 读写 关闭 收发数据: read recv ssize_t recv(int sockfd, void *buf, size_t len, int flags); //flagsMSG_PEEK 读数据不会删除缓冲区的数据 write send ssize_t send(int…...

SpringCloud:ElasticSearch之自动补全

当用户在搜索框输入字符时&#xff0c;我们应该提示出与该字符有关的搜索项&#xff0c;如图&#xff1a; 这种根据用户输入的字母&#xff0c;提示完整词条的功能&#xff0c;就是自动补全了。 因为需要根据拼音字母来推断&#xff0c;因此要用到拼音分词功能。 1.拼音分词器…...

TOOM解析如何搭建一套适合自己的舆情监测系统?完整的实战指南

随着互联网的普及和社交媒体的盛行&#xff0c;人们在网络上的活动越来越多&#xff0c;同时也涌现出大量的信息和舆情。这些信息和舆情在一定程度上会影响社会和个人的发展和进步。因此&#xff0c;舆情监测逐渐成为一项重要的任务。在本篇文章中&#xff0c;我们将为大家介绍…...

技术分享 | OceanBase 手滑误删了数据文件怎么办

作者&#xff1a;张乾 外星人2号&#xff0c;现兼任六位喵星人的资深铲屎官。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 手滑误删了数据文件&#xff0c;并且没有可替换的节点时&…...

windows上Git Bash支持常用命令gcc tree zip wget cmake ninja

windows上Git Bash支持常用命令gcc tree zip wget cmake ninja 前言 Git Bash基于MinGW64, 提供了win32下的linux命令环境&#xff0c;如ls、cat、tar等。 但是Git Bash还是缺少一些命令&#xff0c;如gcc、make、tree、zip、wget、cmake、ninja等 1. Git Bash支持其他命令…...

面试题30天打卡-day10

1、String 和 StringBuffer、StringBuilder 的区别是什么&#xff1f; String、StringBuffer、StringBuilder主要的区别在于执行效率和线程安全上。 String&#xff1a;String字符串常量&#xff0c;意味着它是不可变的&#xff0c;导致每次对String都会生成新的String对象&a…...

【python】制作一个简单的界面,有手就行的界面~

目录 前言准备工作试手小案例开始我们今天的案例教学尾语 &#x1f49d; 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! ttkbootstrap 是一个基于 tkinter 的界面美化库&#xff0c; 使用这个工具可以开发出类似前端 bootstrap 风格的 tkinter 桌面程序。 ttkbootstrap …...

基于RK3568的Linux驱动开发—— GPIO知识点(二)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主系列基于RK3568的Linux驱动开发——GPIO知识点&#xff08;一&#xff09;_daisy.skye的博客-CSDN博客 查看goio使用情况 cat /sys/kernel/debug/gpio 1|rk3568_r:# cat /sys/kernel/debug/gpio gpiochip0: GPIOs 0-3…...

item_get-获得aliexpress商品详情API的调用参数说明

item_get-获得aliexpress商品详情 aliexpress.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;免&#xff09;&#xff08;测&#xff09;&#xff08;试&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&…...

【Python_Scrapy学习笔记(三)】Scrapy框架之全局配置文件settings.py详解

Scrapy框架之全局配置文件settings.py详解 前言 settings.py 文件是 Scrapy框架下&#xff0c;用来进行全局配置的设置文件&#xff0c;可以进行 User-Agent 、请求头、最大并发数等的设置&#xff0c;本文中介绍 settings.py 文件下的一些常用配置 正文 1、爬虫的项目目录…...

spark读写时序数据库 TDengine 错误总结

最近在用spark读取、写入TDengine 数据库遇到了这样一个问题&#xff1a; JDBCDriver找不到动态链接库&#xff08;no taos in java.library.path&#xff09; 我本地都好好的&#xff0c;但是一上服务器写入就会报这个错误&#xff0c;看了很久没有排查出问题&#xff0c;后…...

Web中间件常见漏洞

一、IIS中间组件 1、PUT漏洞 原理&#xff1a;IIS开启了WebDAV&#xff0c;配置了可以写入的权限&#xff0c;造成了任意文件上传漏洞。 防御&#xff1a;关闭webDAV&#xff1b;关闭写入权限 2、短文件名猜解 原理&#xff1a; IIS的短文件名机制&#xff0c;可以暴力破解…...

Python Web 深度学习实用指南:第三部分

原文&#xff1a;Hands-On Python Deep Learning for the Web 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形象&#xff0c;只关…...

C#基础学习--预处理指令

目录 什么是预处理指令 基本规则 #define 和 #undef 指令 条件编译 条件编译结构 诊断指令 行号指令 ​编辑 区域指令 #pragam warning 指令 什么是预处理指令 源代码指定了程序的定义&#xff0c;预处理指令指示编译器如何处理源代码 基本规则 #define 和 #undef 指令…...

Spring Boot 接口加解密

1. 介绍 在我们日常的Java开发中&#xff0c;免不了和其他系统的业务交互&#xff0c;或者微服务之间的接口调用 如果我们想保证数据传输的安全&#xff0c;对接口出参加密&#xff0c;入参解密。 但是不想写重复代码&#xff0c;我们可以提供一个通用starter&#xff0c;提…...

大公司为什么禁止SpringBoot项目使用Tomcat?

前言 在SpringBoot框架中&#xff0c;我们使用最多的是Tomcat&#xff0c;这是SpringBoot默认的容器技术&#xff0c;而且是内嵌式的Tomcat。同时&#xff0c;SpringBoot也支持Undertow容器&#xff0c;我们可以很方便的用Undertow替换Tomcat&#xff0c;而Undertow的性能和内…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...