Unity上道秘籍
Unity上道秘籍
- 本文仅用于理解Unity的基础代码运作逻辑
- 能让人快速入门Unity及Unity的编程
🔥Unity的世界观
- 本节主要介绍Unity界面与基础脚本通信
- 提示:强烈建议使用英文编辑器!!
🚩界面介绍
- 默认你有一点点的C#基础,因为我也只会一点点的C#基础👍
- 下图是Unity新项目的界面
- Unity引擎界面由许多窗口构成,窗口能够互相吸附、打开关闭
- 两个窗口可以像网页标签一样重叠
- 标签右方带锁头的窗口可以被上锁,可以固定显示某个物体的状态
- 在标签栏里
Window
中可以调出所有的窗口,窗口都可以多开 - 恢复及设置窗口布局点击界面右上角
Layout
Scene场景窗口
Scene窗口
用于显示一个场景文件Scene
里的所有内容鼠标右键拖动
:以自身为基准旋转(忽略X轴旋转)Alt + 鼠标左键拖动
:以视角中场景的中轴旋转(忽略X轴旋转)鼠标中键拖动
:以固定比率移动自身鼠标中键点击
:若点击至物体,视角会聚焦该物体,中键拖动所移动的比率会智能变化鼠标滚轮
:推动视角纵深- 使用
视角辅助器
能将视角转换为正视角和正交视图
Game游戏窗口
Game窗口
以列表的形式显示Scene场景
中摄像机组件
内的视图Game窗口
左上角的Display序数
必须与此时显示的摄像机相匹配Display序数
选项旁可以设置输出视角的分辨率
Maximize On Play全屏播放选项
若开启,运行游戏时会使Game窗口
最大化
Hierarchy大纲
Hierarchy窗口
用于显示Scene场景
中所包含的所有游戏物体- 在
Hierarchy窗口
中单击右键可以新建几乎所有的Unity预设物体 - 拖动窗口中的物体至另外的物体上,可以设置父子集关系
- 一般场景物体的图标和字体显示黑色,预制体的图标和字体显示蓝色
Hierarchy窗口
中预制体的右边显示箭头,可以将场景转换为预制体编辑模式- 复制物体使用
ctrl + D
,删除物体使用Delete
- 多选物体时,按
ctrl
键加选减选 - 连选物体时,先选择首物体,按
shift
键选择尾物体
Project项目文件
Project窗口
以列表的形式显示整个项目的所有资源文件- 推荐使用文件夹分类的方式进行文件管理(右键->Create->Folder)
- 默认的
Project窗口
有Assets
和Packages
两个文件夹- 在
Assets
里放入项目的所有工程文件 - 在
Packages
里查看该项目所拥有的所有资源包
- 在
- 复制文件使用
ctrl + D
,删除文件使用Delete
- 多选文件时,按
ctrl
键加选减选 - 连选文件时,先选择首文件,按
shift
键选择尾文件
Inspector细节清单
Inspector窗口
用于显示在Hierarchy窗口
或Project窗口
中选择的物体的内部信息- 如果选择
Hierarchy窗口
中物体:(拿场景中Main Camera
举例)- Inspector窗口中显示三个标题字体加粗的区块:
Transform
、Camera
和Audio Listener
- 这三个区块被称为
组件
- 所有
组件
之上,可以设置该物体的Name名称
、Tag标签
、Layer层级
和Static是否静态
- 所有
组件
之下,按钮Add Component
可以为该物体添加新组件
- 几乎任何物体都拥有
Transform组件
- Inspector窗口中显示三个标题字体加粗的区块:
- 如果选择
Project窗口
中文件:- 如果该文件是预制体,则会显示与选择
Hierarchy窗口
中物体的显示大致一致 - 如果该文件是C#脚本或其他文本文件,则会显示脚本文字的预览
- 其他文件都有其特殊的显示
- 如果该文件是预制体,则会显示与选择
Console日志窗口
- 通过标签栏->Window->General->Console或
Ctril + Shift + C
打开Console窗口
Console窗口
用于显示游戏日志,包括游戏bug、警告及玩家测试语句
🚩脚本功能
- 在Project窗口里新建文件夹用来装脚本
新建脚本
- 在文件夹内右键->Create->C# Script来创建脚本,同时为脚本命名
- 如果不命名,默认名为
NewBehaviourScript
,同时不建议在Project窗口
里改脚本名
- 双击打开新建的脚本(默认下载了VisualStudio并安装了Unity依赖项)
- 新建的脚本如下:
- 如果脚本类需要改名字,则需要保证脚本内类名与
Project窗口
中脚本文件名一致
- 如果脚本类需要改名字,则需要保证脚本内类名与
1 | using System.Collections;//引用非泛型集合 |
组件挂载
- 继承自MonoBehaviour类的脚本属于
组件
脚本,可以挂载到任何游戏物体上来作为组件
- 保存脚本后,Unity会自动编译,同时引擎右下角会转圈
- 在
Hierarchy窗口
中右键新建一个空物体
(Create Empty),命名为GameObject_1
- 将
Project窗口
中MyScript_1
脚本拖到此新建游戏物体的Inspector窗口
下方空白处 - 于是
GameObject_1
物体就拥有了MyScript_1
脚本组件
- 右键组件标题栏->Remove Component用于移除该组件
- 一个物体可以添加多个组件,组件与组件之间是平行关系,没有顺序限制
- 预制体也可以重载添加组件,但重载的组件不允许放置在已预置组件之前
- 物体身上的多个相同组件默认以放置的顺序来排序
- 如果组件拥有像
Start()
或Update()
类似的线程函数,在Inspector窗口
会显示对勾Inspector窗口
左上角的对勾用于控制该物体是否显示
Mono类的生命周期
- 在继承自MonoBehaviour类的脚本中,内置了许多线程函数,它们的调用拥有先后顺序(生命周期)
- 官方绘制的周期图:网页
- 在下图(不完整)中,蓝色标注的是常用函数,粉色标注的是事件函数
🚩脚本通信
- 游戏是由许许多多逻辑回圈构成的,这种逻辑回圈被称为
系统
- 如数值系统、动画系统、联机系统、界面系统等
- 上述系统都属于功能较为完整单一的逻辑回圈,各司其职、相互访问数值,从而构成游戏
- 逻辑回圈中最重要的就是脚本通信
- 最基本的脚本通信逻辑即
获取(Get)
到其他脚本内部的数值,或直接设置(Set)
它们
Get和Set
- Get和Set的来历:
- 在类定义时,
私有private
或受保护protected
的变量无法被外部脚本直接获取 - 这时就需要设置
公开public
成员函数来间接进行GetSet操作
- 在类定义时,
组件访问
在Unity中,对于相对静态的物体身上脚本组件的访问大致拥有几种类型:
一:在Inspector窗口拖拽
二:先获取物体,再获取脚本组件
如何查看组件的真实类名:
- 在任何不熟悉的组件的标题栏上右键->如果没有
Edit Script
选项:- 此组件为Unity官方组件
- 此组件类的类名与该加粗标题名一致
- 因
Camera
组件没有Edit Script
选项,所以可判断该组件类名为Camera
- 因
TextMeshPro-Text(UI)
组件有Edit Script
选项,查看其类名为TextMeshProUGUI
- 在任何不熟悉的组件的标题栏上右键->如果没有
物体组件访问函数
- 因为组件都是挂到物体上的,所以需要先获取物体,再获取其身上的组件
- 获取语句建议写在
Start()
方法里 - 获取物体的方法:
- 直接搜索名字,但是需要保证没有重名物体
- GameObject GameObject.Find()
- 将物体设置某个标签Tag,但是需要保证只有该物体有该标签
- GameObject GameObject.FindGameObjectWithTag()
- GameObject[] GameObject.FindGameObjectsWithTag()
- GameObject GameObject.FindWithTag()
- 直接搜索名字,但是需要保证没有重名物体
- 获取物体中组件的方法:
- 直接获取
- <Template T> T GetComponent<T>()
- <Template T> T[] GetComponents<T>()
- 通过父子物体获取
- Component Component.GetComponentInChildren()
- Component[] Component.GetComponentsInChildren()
- Component Component.GetComponentInParent()
- Component[] Component.GetComponentsInParent()
- 直接获取
参数的访问
print()
和Debug.Log()
一样,都是输出一条消息给Console窗口
- 比如说要获取和控制摄像机组件身上的视场角
- 需要先获取场景中的摄像机物体,这里使用拖拽的方法
- 查看摄像机组件里的参数
- 注意,这里的参数是被
界面脚本
加工过的,有时与脚本内部值不匹配 - 建议到官网API或Manual上查询
- 注意,这里的参数是被
- 根据代码提示找到
cam.fieldOfView
接口 - 尝试GetSet
- 保存代码,运行游戏,打开
Console窗口
查看数值波动 - 在运行过程中修改
MyScript_1
组件暴露的fov
参数(拖拽)Game窗口
显示的视场发生了变化,Camera
组件中FieldOfView
参数动态参与改变
小结
- 以上即基础的参数访问,同时也实现了两个类(
Camera
与MyScript_1
)之间的通信 - 使用代码分流(if-else/switch)及以上介绍的脚本通信,足以实现基础逻辑回圈制作
- 脚本通信是整个Unity学习的梗概,其他的东西其实都是万变不离其宗的
🔥组件基础
- 组件的访问是在获取到装载组件的物体后,对其物体使用
GetComponent<组件类名>()
模板函数 - 组件的添加是在物体上使用
gameObject.AddComponent<组件类名>()
模板函数
🔥属性与参数暴露
🚩常用访问修饰符
- public公开
- 公开的属性能够被其他脚本所访问,并暴露在
Inspector面板
上
- 公开的属性能够被其他脚本所访问,并暴露在
- private私有
- 私有的属性只能在该脚本中访问(无修饰符变量默认是私有的)
- protected保护
- 保护的属性只能在该脚本和继承该脚本的脚本所访问
🚩常用数值属性
这些都是Unity自带的参数属性,如果对于检视面板美化感兴趣,可以查看另一篇讲
Odin
插件的文章携带属性:
[RequireComponent(typeof(脚本类名))]
- 修饰类的属性,可以使物体添加该脚本时强制添加某附着脚本
- 如果原先有该附着脚本,则锁定该附着脚本
- 添加到组件工具集:
[AddComponentMenu("分栏名")]
- 修饰类的属性,可以使该脚本添加到Unity的组件工具集中
- 小标题:
[Header("标题名")]
- 修饰变量的属性,为其修饰的public变量及以下变量添加小标题
- 该属性下的第一个变量一定得是public修饰变量
- 可序列化和于细节面板隐藏:
[SerializeField]
&[HideInInspector]
SerializeField
能无视private修饰,直接暴露到编辑器,见下图- 但由于private的缘故,无法在其他脚本内被访问
HideInInspector
能将一切被修饰变量于细节面板隐藏,无视public- 但由于public的缘故,还是能够在其他脚本内被访问
- 文本区、空白区、数值范围:
[TextArea()]
&[space()]
&[Range()]
- TextArea提供一个比较大的范围的文本输入区,可以用于查看文字
- 需要填入最小和最大显示行数,超过最大行数会显示滚动条
- TextArea于Multiline属性相似,都是拓展文本区
- Space单纯就是空白
- Range为数值类型提供了限制,需要输入最小值和最大值
- 类序列化:
[System.Serializable]
- 将类序列化成结构体,同样适用于结构体序列化
- 暴露在细节面板中是以下拉菜单的形式存在的
- 小提示:
[Tooltip()]
- 鼠标放在细节面板的变量上,会出现小提示
🔥预制体
- 类似于虚幻的蓝图类,但是Unity的预制体内容较为单薄
- 预制体就是预先设计好的,存储成文件的,允许携带层级关系的一套物件单例
🚩预制体制作
- 将
Hierarchy大纲
里的单个物体或物体父级拖入Project项目文件
窗口中,即可创建 - 只有在
Project窗口
中的才是预制体,Hierarchy窗口
里的是预制体实例 - 删除预制体实例,预制体不会受影响;删除预制体,预制体实例会失联
- 修改预制体参数,预制体实例内的参数也会发生改变
- 右键预制体实例选择
Break Prefab
,即可与预制体断开连接,使实例变为普通场景物体
🚩预制体实例化
- 预制体属于文件,同样也属于物体,同样可以通过GameObject类型来获取
- 使用
GameObject.Instantiate()
方法在游戏一开始生成预制体实例
🔥方位运动
🚩DeltaTime秒每帧
- 如果要在游戏中控制人物的移动,实现逻辑是在
Update()
方法里每帧对人物方位进行修改 - 假设说玩家的速度很快,达到了惊人的1米/帧
- 玩家的电脑性能很好,帧速率(每秒刷新次数)达到了惊人的60帧/秒
- 通过计算,这位玩家1秒钟就能前进60米
- 另一个玩家电脑性能很烂,帧速率(每秒刷新次数)达到了20帧/秒
- 通过计算,这位玩家1秒钟才能前进20米
- 在游戏人物速度一致的情况下,仅通过电脑性能就能使两个联机的玩家在速度上拉开差距
- 这是不可取的,所以要引入秒每帧(增量时间)
DeltaTime
的概念 - 尝试使用1去除帧速率,60帧/秒的玩家的增量时间为1/60,20帧/秒的玩家为1/20
- 然后在玩家速度上乘以这个变量
- 60帧/秒的玩家最终速度为60*(1/60)=1米/秒,1秒钟前进1米
- 20帧/秒的玩家最终速度为20*(1/20)=1米/秒,1秒钟前进1米
- 虽然速度大幅下降,但是两位玩家达成了速度的归一化,排除了硬件影响
- 所以说,DeltaTime拯救了高ping玩家
- 在Unity里,使用
Time.deltaTime
来获取增量时间
🚩移动旋转缩放
- 一般情况下,需要乘上速度和增量时间
🔥输入事件
- 输入由
Input
接口去管理 - 主要有以下几种输入事件:
🚩鼠标事件
- Input.GetMouseButton() 鼠标持续按下状态
- Input.GetMouseButtonDown() 鼠标按下状态
- Input.GetMouseButtonUp() 鼠标抬起状态
- Input.MousePosition 鼠标位置
- Input.mouseScrollDelta 鼠标滚轮状态
🚩键盘事件
- Input.GetKey() 键盘持续按下状态
- Input.GetKeyDown() 键盘按下状态
- Input.GetKeyUp() 键盘抬起状态
- Input.anyKey 任意键持续按下状态
- Input.anyKeyDown 任意键按下状态
🚩轴事件
- 在Unity的Project Settings项目设置窗口里的Input Manager设置按键轴参数
- 通过轴名称的字符串来获取轴按键输入
- 里面的参数自行翻译,没什么好说的
- Input.GetAxis() 获取轴输入,返回浮点值
🚩轴按键事件
轴输入获取的是轴的数值状态
要获取Input Manager里自定义按键的按钮事件,就得使用按键事件
Input.GetButton() 按键持续按下状态
Input.GetButtonDown() 按键按下状态
Input.GetButtonUp() 按键抬起状态
🚩手柄事件
- 手柄输入分为手柄按键和手柄轴两个部分:
- 手柄按键:Input.GetKey/Down/Up(KeyCode.JoystickButton1~19)
- 手柄输入:Input.GetAxis()
- 手柄按键输入具体要按照手柄品牌和型号而定,下图是Xbox版本
- 手柄输入需要在Project Settings里设置,主要设置:
Dead
忽视阈值,输入不达阈值则看作没输入Sensitivity
灵敏度,轴返回值是-1~1之间的数,再乘上灵敏度Invert
是否翻转轴Axis
具体哪个轴,见下图所示
🔥接触事件
- 在场景里新建一个
Cube方块
,在Inspector面板里可以看到BoxCollider箱体碰撞箱
组件 - 碰撞和触发事件就是通过此组件进行的
- 将我们的脚本
MyScript_1
附着在此方块上 - 接触事件都有2d和3d两个版本
🚩碰撞事件
- 碰撞事件产生的前提条件是碰撞的双方至少有一方拥有
刚体Rigidbody
组件 - 产生碰撞事件需要将
BoxCollider
组件上的is Trigger
取消勾选 - 碰撞事件与初始化、更新事件同级,都属于事件方法
- 当物体产生碰撞时会自动调用碰撞方法,并返回
Collision
类型的参数
🚩碰撞层级
- 在
Project Settings
里找到Tags and Layers
,添加两个自定义层级
- 将两个需要碰撞的物体分别设置成不同的碰撞层
- 别忘了碰撞箱组件和刚体组件
- 在
Project Settings
里找到Physics
,翻到最底下的Layer Collision Matrix
- 横轴纵轴之间交叉的勾选框即代表两个层级的物体之间是否能够发生碰撞
🚩触发事件
触发事件产生的前提条件同样是是触发的双方至少有一方拥有
刚体Rigidbody
组件与碰撞事件类似,触发事件只需将需要检测触发的BoxCollider组件的is Trigger勾选上即可
任何一方勾选is Trigger都可以触发触发事件
🔥射线检测
- 射线检测就是从一个点到另一个点之间拉一条红外线,经过的物体会触发射线检测
🚩直线射线检测
- 最简单的射线检测
- 对射线做出一些限制,射线检测允许使用物体层级来区分
- 如何检测一堆物体
🚩圆弧射线检测
🔥物理运算
🚩刚体
- Unity的绝大多数物理运算都与刚体组件
Rigidbody
有关 - 作为引导类的文章,只介绍为什么要使用刚体,以及一些基础操作
- 在之前的碰撞检测里面以及提及了刚体在碰撞事件中的作用,所以只需要简要介绍一下其他内容:
- Mass:质量,质量越大惯性越大,推动它也需要更多的力
- Drag:阻力,移动时,物体会受到一种类似空气阻力的反作用力
- Angular Drag:旋转阻力,旋转时所受到的阻力
- Use Gravity:是否开启重力,不开启重力会失重,凡受力之后会永远匀速运动下去
- Is Kinematic:是否开启动力学,不开启时不会受力移动,适合用于一些特殊情况
- Constraints:限制,允许冻结移动或旋转的轴向
🔥界面绑定
- Unity里的界面是使用一种与三维共存的方式来运行的,在使用界面之前,需要在
Hierarchy大纲
里右键新建一个Canvas
画布,同时会创建一个EventSystem
物体,用来管理控制行为
- 右键
Canvas
创建的一切物体都隐藏了其Transform
组件,取而代之的是RectTransform
组件 - 双击
Canvas
让视图居中,点击Scene窗口
左上方的2D视图按钮
,用来方便查看 Pos X
和Pos Y
都指的是屏幕坐标,如果Game窗口
里定义的大小是1920*1080像素
,那么如果想要将物体贴于屏幕右上角,它的坐标就会是(960,540)
,因为坐标原点是屏幕中心width
和height
是图像宽和高,单位像素Anchors
是物体锚点,位于0到1之间,锚点用来规定图像的延展区间,以防由于输出分辨率的变化,界面元素显示错误的问题- 锚点的用处在于:无论分辨率如何,物体与锚点之间的距离恒定
- 点击组件左上角的锚点预设图标,允许选取锚点预设
- 锚点预设能将物体锚点设置在屏幕的多个位置,但不会修改物体的实际位置
- 在选取锚点预设时按住
alt
键,就能在修改锚点的时候修改物体位置
🚩编码提示
在使用Unity的界面之前,需要做上引用:
using UnityEngine.UI;
最常见的界面元素是文本,也就是
Text组件
,在代码中获取组件后,便可以修改其中text
参数,也就是显示的文本了图像是
Image组件
,其中图像参数是sprite
单选框是
Toggle组件
,其中是否开启参数是isOn
滑动条是
Slider组件
,其中滑动数值参数是value
🚩按钮与事件绑定
- 按钮是
Button组件
,组件最下方有一个OnClick()
点击事件接口,点击右下方的加号用来添加函数槽
🔥美术与程序
- 因为美术的知识太繁杂,也不好描述,这里只讲美术与程序之间的调用
🚩材质与默认着色器
着色器Shader
是材质Material
的爹,一个着色器可以拥有多个材质的实例,着色器控制材质的渲染方法与参数,着色器就是材质的源码,在Unity里,官方提供了一个叫Standard Shader
的基础着色器将材质拖给任何携带类似
Renderer
组件的物体时,能够直接赋予上去在Project窗口里右键新建一个材质,在
Inspector窗口
中是这样显示的
- 右键
Inspector窗口
中的第一栏,选择Select Shader
用来查看默认着色器参数
- 在
Properties
下面,左边一栏是参数在其源码中的名称,意思是,如果在代码里调用着色器参数,只能调用这种名称
🚩粒子
- 在
Hierarchy窗口
里右键新建Effects->ParticleSystem
创建粒子系统 - 其中
Play On Awake
是是否在启动时运行 - 太多了,这些东西需要上手去实操,其他的有时间再聊
🔥数据持久化
- 数据持久化就是存档,游戏不能没有存档
- 除了Unity自带的
PlayerPrefs
方法,还有Json
、XML
、bit
等方法也能更好的存档
🚩PlayerPrefs
🔥场景跳转与退出
- 点击工具栏的
File->BuildSettings
,查看游戏构建设置 - 将需要参与场景跳转的所有场景(就是最终输出的游戏场景)拖入
Scenes In Build
栏位里
- 在代码中需要引用
using UnityEngine.SceneManagement;
- 代码比较简单