【Unity基础】
目录
- 1. 基础知识
- 1.1. 熟悉软件
- 1.1.1.中文设置
- 1.1.2. 软件构成
- 1.1.3. 物体基本操作
- 1.1.4. 标签与图层
- 1.1.5. 导入导出模型
- 1.1.6. 资源商店
- 1.1.7. 预制体与变体
- 1.2. Unity地形
- 1.2.1. 地形架构
- 1.2.2. 地形美化
- 1.3. 基本理论
- 1.3.1. 坐标系与原点
- 1.3.2. 三维向量(Vector3)
- 1.3.3. 欧拉角与四元数
- 1.4. 常见问题
- 1.4.1. 包管理器错误
- 2. 组件基础
- 2.1. 组件入门
- 2.1.1. 熟悉组件
- 2.1.2. Debug调试
- 2.1.3. 脚本生命周期
- 2.1.4. 执行顺序
- 2.1.5. Transform
- 2.2. Unity基础类
- 2.2.1. 物体类
- 2.2.2. 时间类
- 2.2.3. Application类
- 2.3. 交互类
- 2.3.1. 键鼠操作
- 2.3.2. 虚拟轴
- 2.3.3. 触摸方法
- 3. 组件进阶
- 3.1. 场景相关
- 3.1.1. 场景
- 3.1.2. 灯光
- 3.1.3. 相机
- 3.1.4. 音视频
- 3.2. 物理相关
- 3.2.1. 刚体与碰撞
- 3.2.2. 特殊物理关节
- 3.2.3. 物理材质
- 3.2.4. 射线检测
- 3.2.5. 粒子、线条与拖尾
- 3.3. 动画相关
- 3.3.1. 动画小试
- 3.3.2. 角色动画
- 3.3.3. FBX导入器
- 3.3.4. 混合动画与分层
- 3.3.5. 视频动捕方案
- 3.3.6. 反向动力学
- 3.4. 导航
- 3.4.1. 导航网格
- 3.4.2. 网格链接
- 3.4.3. 导航成本
- 3.5. UI界面
- 3.5.1. UI小试
- 3.5.2. 轴心点与锚点
- 3.5.3. UI控件
- 3.5.4. 常用UI组件
主要参考:unity-哔哩哔哩_bilibili
官网:unity.com
Unity中国:unity.cn
Unity引擎优势
- 基于C#编程,拥有易上手、高安全性的特性。
- 独特的面向组件游戏开发思想让游戏开发更加简单易复用
- 拥有十分成熟的所见即所得开发编辑器
- 拥有良好生态圈,商城中包含大量成熟的功能脚本与资源
- 强大的跨平台特性,可以制作PC、主机、手机、AR、VR等多平台游戏
1. 基础知识
unity的下载相当简单,先去官网下载unityhub,再通过unityhub下载unity编辑器,按自己需求安装即可
参考博客:
【Unity】下载与安装(保姆级教程)_unity下载-CSDN博客
Unity安装步骤、设置中文界面,新建一个项目_没有安装编辑器,请在创建项目之前安装unity-CSDN博客
1.1. 熟悉软件
1.1.1.中文设置
-
unity hub设置中文

-
新建项目

-
项目设置中文
unity项目设置中文需要先再unity hub中先下载语言包

在项目中导入语言包,再重启项目即可
菜单栏 -> Edit -> Preferences -> Languages -> Editor language -> 简体中文
1.1.2. 软件构成
-
软件大致构成

-
场景窗口:显示当前场景
-
游戏窗口:场景运行的时候的样子
-
项目窗口:存放工程的各种资源
-
层级面板窗口:展示场景物体的层级关系。
-
检查器窗口:查看物体的组件和属性
-
-
工程目录

文件夹 说明 Assets 资源文件 Library 系统库 Logs 日志 Packages 导入的包 ProjectSettings 工程设置 Temp 临时文件(文件过大可以删除部分缓存) UserSettings 设置
1.1.3. 物体基本操作

-
技巧
-
工具栏切换对应快捷键分布规律
从工具栏从上到下分别对应着Tab键右边的Q(拖动视角)、W(移动)、E(旋转)…
-
精调属性
选中物体后,在检查器的的Transforms中,点击选中要精调的属性,拖动即可

-
-
移动
箭头方向为全局坐标系正方向,点击选中物体
-
拖动操纵杆,即可延该操作杆方向平移物体
-
拖动操纵杆自己的平面也可以移动物体,选中一种(如蓝色面)颜色的面来移动物体,则对应颜色的(对应y轴)坐标不会改变

-
-
缩放
灰色是整体缩放,其他颜色是相应方向缩放

-
其他操作
操作 说明 鼠标左键 / Alt + 鼠标左键 旋转视图视角 2 3D/2D切换 滚轮 放大缩小视图 鼠标左键 选择 W/A/S/D场景视图漫游 双击场景物体或者双击层级窗口物体 物体居中 Ctrl + Shift + F 将摄像机移动到当前视角 -
检查器手动重置立方体坐标为(0,0,0)

-
-
创建物体
-
可以通过顶部导航栏 -> 游戏对象来创建物体
-
通过鼠标右键层级窗口空白处(或者左上角“+”号)来创建物体(可以ctrl C,ctrl V 复制粘贴物体,也可以通过Ctrl D克隆物体)
层级窗口中的物体是和场景窗口的物体一一对应的,见名知意,如文件管理器一样,层级窗口主要体现物体的父子级关系
在创建并点击选中物体后,检查器窗口可以查看和改变3D物体的属性

-
-
四边形和平面的区别
选择阴影线框混合模式

如图,平面由很多三角形构成,而四边形是由两个三角形组成的简单平面,如果对平面要求不高,可以使用四边形代替平面
而且为了节省性能,unity的所有物体或面采用默认的背面剔除的方式,也就是说,只有(法向量方向)正面可以看到物体

1.1.4. 标签与图层
选中物体,在检查器窗口就会在检查器窗口出现标签和图层两个属性

-
标签
-
在unity中,标签是拿来做分类管理的,一个标签可以绑定多个物体,一个物体只能绑定一个标签。比如一个游戏中,有敌人和队友,那么敌人是一个标签,队友又会是另外一个标签。
-
在复杂的游戏场景中,我们要找某类物体来进行批量操作,就可以给这类物体加上某个标签,通过标签来找这类物体
-
添加标签

-
-
图层
-
图层最多只能设置32个,常用来显示过滤或者做碰撞
-
添加图层

-
通过图层来显示过滤
-
如下图有dog,cat,player三个物体;dog,cat属于animal图层,player属于human图层

如图,选中相机,在检查器窗口的剔除遮罩取消勾选human

如下图,游戏窗口不再显示human层级的物体player

-
-
1.1.5. 导入导出模型
-
导入
在项目窗口打开资源管理器

项目窗口的项目文件和打开的文件管理器相对应,我们可以通过复制粘贴或者拖拽的方式完成文件的导入(obj、fbx或者unity打包的工程文件等都可以,glb等文件不行,需要通过blender等3D软件转化格式)

导入成功后,可以在检查器窗口的底部预览模型,还可以将模型拖入场景或层级(拖入场景和层级都可以,他们是相通的)

-
导出
同样,在项目资源管理器中点击鼠标右键也可以导出包(unity工程文件包),导出的可以直接再导入进来

-
给物体添加材质

添加材质后,将材质往项目窗口或场景窗口的物体上面拖,即可为物体染色,如图,黑衣人也是变成了红色小兵了,只为世人留下了一个孤独的背影,(材质的其他属性可以自己调调,学过一些blender之类的建模软件一个应该能够轻易上手)

-
选择网格

-
选择材质

1.1.6. 资源商店
除了系统自带了的资源,和自己创建的资源
顶部菜单栏 => 窗口 => 资源商店
-
在如图的地方打勾,以后直接打开浏览器资源商店,而不再打开Unity内嵌窗口

贫穷如我的人,也会像我一样,快速的找到免费资源

-
使用资源
顶部菜单栏 => 窗口 => 包管理器 => 在包管理器中下载再导入使用即可
-
报错
[包管理器窗口] 获取验证码时出错:System.InvalidOperationException: Failed to call Unity ID to get auth code.UnityEditor.AsyncHTTPClient:Done (UnityEditor.AsyncHTTPClient/State,int) -
原因:在打开浏览器资源商店时,我们已经申请了ID号,但是ID号没有同步到账号上
-
解决
- 关闭项目,回到unityhub
- 账号登出、登入
- 再重新打开项目即可
-
1.1.7. 预制体与变体
-
预制体
- 预制体其实就是面向对象中继承的思想,可以理解为预制体为父类,封装了一些共有的属性和方法,我们通过预制体创建的物体(后面我就称为预制体实例为子类),通过预制体实例(子类)继承预制体(父类)的属性和方法
体验预制体
-
在场景中,我创建了一个物体

如图,将物体从层级窗口,拖动到项目窗口(项目窗口中的Cube就是预制体),层级窗口的物体样式变蓝,这表明,层级窗口中的物体不再是一个独立的物体,而是由预制体生成的物体

-
预制体实例改变,预制体不会(主动)改变

-
预制体改变,预制体实例会改变
双击预制体,进入预制体编辑模式(或者在选中预制体后,点击检查器窗口顶部的打开,来进入预制体编辑模式),我们在这个模式下删除物体的性感白色嘴唇

-
补充:进入预制体实例编辑模式(这个用处要小一些,可以专注修改预制体实例,退出按钮和上面的退出预制体编辑模式一样)

如图,预制体的右眼被删除,预制体实例的右眼也被删除(其他的属性、脚本等,也是一样的)

-
预制体实例来反向影响预制体
上面不是说:预制体实例改变,预设体不会(主动)改变吗,那么就是可以被动改变咯。
-
预制体实例覆盖预制体
-
完全覆盖
如图,我在预制体实例中又创建了右眼(有绿色“+”号的为新创建,),我们将层级窗口的预制体实例拖动到项目窗口上的预制体上,就可以用预制体实例覆盖预制体

-
部分覆盖
上面的方法是用预制体实例把预制体完全覆盖,如果你只是想将预制体实例的部分组件覆盖到预制体上,可以这样做
如图,组件图标右下角有绿色的“+”号的组件是预制体实例有,预制体没有的组件,安装以下步骤,完成组件的覆盖,覆盖完后,组件图标右下角有绿色的“+”号消失

-
-
-
预制体变体
这个的话相当于预制体套娃,如果有预制体变体,那么预制体变体实例 继承于 预制体变体 继承于 预制体
-
体验预制体变体
复制Cube,为Cube(1)添加一顶白帽子(姚广孝行为)

再将预制体实例Cube(1)从层级窗口拖动到项目窗口,出现如图弹窗,选择预制体变体(如果选择原始预制件,那么项目窗口会新建一个与预制体Cube一样的预制体Cube(1),而预制体实例Cube(1)会关联预制体Cube(1),而不是生成预制体变体)

如图,生成了预制体变体Cube(1) Variant

有些绕哈,继承关系如图

我现在给Cube(1)预制体变体加一个性感的白色嘴唇
对应的Cube(1)预制体变体实例也发生相应改变

1.2. Unity地形
1.2.1. 地形架构
层级窗口鼠标右键 => 3D对象 => 地形
如图,切换地址组件tabs栏,我们可以看见所创建的地形的宽度,后面对地形不同类型的操作,也需要切换地址组件tabs栏

-
添加地形
如上图地形组件tabs栏的第一个按钮创建相邻地形
点击黄框创建地形

-
绘制地形
-
笔刷是画笔的形状
-
画笔大小是画笔范围,通过
[或者]也可以快速调节画笔大小 -
不透明度为画笔强度

-
-
另外的设置项含义如图

- Set Height(设置高度):直接指定地形的绝对高度。
- Smooth Height(平滑高度):当前选中项,用于平滑地形起伏,消除尖锐棱角。
- Paint Texture(绘制纹理):为地形表面涂抹不同材质(如草地、岩石、泥土等)。
- Paint Holes(绘制孔洞):在地形上创建凹陷或孔洞效果。
- Stamp Terrain(地形印章):使用预设的形状(如山坡、悬崖)快速生成地形特征。
- Raise or Lower Terrain(抬高或降低地形):通过笔刷动态调整地形高度(类似“雕刻”功能)。
多尝试一下,可以完成简单的地形制作,但要更精美还需要多加练习

-
小技巧
- Set Height模式下,Shift + 鼠标左键,即可完成取高度
- Smooth Height模式下,模糊方向的作用是平滑高度的趋势
- 模糊方向为0,则相邻某个范围类,高的变低,低的不变高
- 模糊方向为1,则相邻某个范围类,低的变高,高的不变低
1.2.2. 地形美化
-
我们可以在资源商店下载Terrain和Tree9资源然后导入项目中

-
地形层
地形层 => 编辑地形 => 创建层 => 选择地形

然后通过微调就可以达到想要的效果(注意,一个地形可以有多个地形层纹理)

-
树(草也一样)
-
在资产库里面导入树

-
大量放置树

-
报错
所选树没有LOD
-
LOD是什么?
LOD(Level of Detail) 是一种性能优化技术,它的核心思想是:根据物体与摄像机的距离远近,自动切换该物体不同精度的模型,从而在不影响视觉效果的前提下,显著提升游戏运行效率。
-
解决方案参考博客
unity Terrain绘制树时提示所选树没有LOD组解决方法 - 伊凡晴天 - 博客园
unity树优化 speedtree lod问题_所选树没有lod组-CSDN博客
-
-
1.3. 基本理论
本节为一些空间或者数学理论,不影响正常使用和后续学习,如果不感兴趣,跳过
1.3.1. 坐标系与原点
- 坐标系分为左手坐标系和右手坐标系,unity采用的是左手坐标系,如图,不同坐标系的关键是right方向(x轴方向不同)

-
全局与局部
在二维坐标系中,坐标系是与原点绑定的,通样在三维世界也使用,在空间中有两种原点,一种的局部原点,对应局部坐标,一种的全局原点,对应全局坐标。
局部坐标系通常存储模型数据(模型空间)
-
顶点数据:模型的顶点坐标、法线、UV等属性默认存储在局部坐标系(也称为模型空间或对象空间)中。例如,一个立方体的顶点可能是相对于其几何中心的坐标
(0,0,0)到(1,1,1)。 -
独立性:局部坐标使模型设计独立于场景中的位置、旋转或缩放,便于复用(如多个相同模型在不同位置)。
全局坐标系(世界空间)通过变换实现
-
变换矩阵:模型在场景中的位置、方向、缩放由**模型矩阵(Model Matrix)**定义,它将局部坐标转换为全局坐标(世界空间)。这一转换通过矩阵乘法实时计算,而非直接存储全局坐标。
模型矩阵(Model Matrix)是一个 4x4 的变换矩阵,用于将物体的局部坐标系(模型空间)下的顶点坐标转换到全局坐标系(世界空间)。它描述了物体在3D世界中的位置、旋转和缩放。
-
灵活性:存储局部坐标+变换矩阵的方式更高效。例如,移动模型只需更新模型矩阵,无需修改顶点数据。
-
-
物体的父子级
在层级视图中,如Plane和Quad这种包含关系为父子级关系
- 如电脑系统的文件管理器,对父元素的移动,移动、缩放等操作也会影响子元素
- 但反过来,子元素的移动、缩放等操作不会影响父元素

-
父子对何坐标系的影响
-
没有父子级
世界坐标 = 模型矩阵 × 局部坐标
-
有父子级(层级变换)
子物体的模型矩阵 = 父物体的模型矩阵 × 子物体的局部变换矩阵
-
-
关闭父物体原点的重新计算
如图,选中父物体后,发现父物体的原点是通过父物体原本的原点的子物体原点重新计算过得到的

切换工具手柄位置为轴心即可关闭父物体原点的重新计算

-
全局与局部
创建的物体的局部坐标系默认方向和全局坐标系相同,如果我们要对局部坐标系进行修改,则需要切换局部模式
如下图,blender也有这个,默认是全局;全局模式下,移动物体会使物体和物体的原点(中心点)一起移动,局部模式则只移动物体的原点(中心点),不移动物体

1.3.2. 三维向量(Vector3)
向量是既有大小,又有方向的量
关于向量的知识,高中就已经学过一次了,这里就简单回顾一下
-
向量的模
即向量的大小(向量的长度)
-
二维向量:|v|=√(x2+y2)
-
三维向量:|v|=√(x2+y2+z^2)
-
-
单位向量
大小为1的向量,把向量转为单位向量的过程被称为单位化或归一化
-
向量加法
-
平面四边形原则

-
三角形原则

-
-
向量减法
向量的减法与向量加法相反,拿上面三角形原则的图举例,a向量 - b向量 = BA向量,减数向量(b向量)与商向量(BA向量)首位相连,构成被减数

-
点乘
点乘是为了得到两个向量之间的夹角
图片来自:1.4 向量的点乘运算(内积) - 知乎

基于此知道两个向量,就可以计算出向量夹角,模,点乘、叉乘等等,下面的Vector3底层就是这样运算的
-
Vector3
在计算机图形学底层,因为向量的特性,向量并不一定当作向量使用,还可以代表坐标、旋转、缩放
向量初始化
-
new
Vector3 v = new Vector3(1, 1, 1); -
特殊初始化
-
数字类
Vector3 v1 = Vector3.zero; // (0,0,0) Vector3 v2 = Vector3.one; // (1,1,1) -
方位类
可以参考上一节的左手

Vector3 v3 = Vector3.forward; // (0,0,1) Vector3 v4 = Vector3.back; // (0,0,-1) Vector3 v5 = Vector3.left; // (-1,0,0) Vector3 v6 = Vector3.right; // (1,0,0) Vector3 v7 = Vector3.up; // (0,1,0) Vector3 v8 = Vector3.down; // (0,-1,0)
创建后也可以修改值,如
v1.x = 0 -
向量运算:
// 计算两个向量的夹角(角度) Debug.Log(Vector3.Angle(v4, v5)); // 原代码缺少右括号 // 计算两点之间的距离 Debug.Log(Vector3.Distance(v4, v5)); // 原代码缺少右括号 // 点积(点乘) Debug.Log(Vector3.Dot(v4, v5)); // 原代码缺少右括号 // 叉积(叉乘) Debug.Log(Vector3.Cross(v4, v5)); // 原代码缺少右括号 // 线性插值(注意方法名大小写) Debug.Log(Vector3.Lerp(v1, v2, 0.8f)); // 原代码小写"lerp"且缺少右括号 // 向量的模(长度) Debug.Log(v2.magnitude); // 原代码错误使用了Lerp // 规范化向量(单位向量) Debug.Log(v2.normalized); // 原代码错误使用了Lerp注意事项:
-
问题
在编写上面脚本时,编辑框可能没有提示;执行脚本,可能会报错

-
解决方法
顶部菜单栏 => 编辑 => 首选项 => 外部工具 => 如图… => 重启项目

-
1.3.3. 欧拉角与四元数
-
欧拉角就是360°的角度制,在Transform中的旋转值就是欧拉角
-
什么是欧拉角?
欧拉角是一种非常直观的描述物体在3D空间中朝向的方法。它的核心思想是:将一个复杂的旋转分解成三个绕特定坐标轴的、连续的简单旋转。
- 偏航角 (Yaw):绕 垂直轴(Y轴) 旋转。就像你站在原地左右转头。
- 俯仰角 (Pitch):绕 侧向轴(X轴) 旋转。就像你点头。
- 翻滚角 (Roll):绕 前后轴(Z轴) 旋转。就像你歪头
上节不是讲过向量可以表示旋转吗,下面向量就可以用来代表绕y轴旋转30°
Vector3 rotate = new Vector3(0, 30, 0); -
什么是万向锁?
当中间的旋转(比如俯仰角)达到 ±90° 时,第一次旋转(偏航)和第三次旋转(翻滚)的旋转轴会重合,导致丢失一个自由度(失去一个方向的旋转能力)。原本的三个独立旋转轴,瞬间退化成了两个。
万向锁产生的原因:欧拉角描述的是变换而非运动,每个变换都是独立的,先旋转的轴会影响后旋转轴的方向。
-
-
四元数是一种四维空间的高阶复数,相当于在xyz的基础上多了w,可以用来表示角度,效率比较高,且不会造成万向锁
-
初始化
Quaternion quaternion1 = new Quaternion(); -
看向一点
这里的Vector3表示的是一个坐标
Quaternion quaternion2 = Quaternion.LookRotation(new Vector3(0, 0, 0));
-
-
既然都是角度,欧拉角和四元数之间是可以相互转换的
-
欧拉角转四元数
Quaternion quaternion3 = Quaternion.Euler(rotate); -
四元数转欧拉角
Vector3 rotate1 = quaternion.eulerAngles;
-
1.4. 常见问题
1.4.1. 包管理器错误
-
错误描述
包管理器窗口] 获取验证码时出错:System.InvalidOperationException: Failed to call Unity ID to get auth code. UnityEditor.AsyncHTTPClient:Done (UnityEditor.AsyncHTTPClient/State,int)c -
解决方法
-
重新登录Unity账户
首先尝试退出并重新登录Unity账户。这可以刷新你的身份验证令牌,解决由于令牌过期或无效导致的问题。
-
检查网络连接和代理设置
确保你的网络连接正常,并且没有启用影响连接的代理服务器。如果使用了代理服务器,尝试关闭代理服务器。
-
2. 组件基础
2.1. 组件入门
在Unity中,组件对应属性、功能等各方各面,如在检查器窗口中,所框部分都作为组件存在。如物体组件,创建后,仍然可以通过检查窗口的组件来改变形状等属性。
其中,transform为一个物体必须存在的组件,因为位置、旋转、缩放是对一个三维物体的基本描述。如果仅有transform,那么这个物体为空物体

-
尝试:给物体添加重力组件
按如图步骤给物体添加重力组件然后单击unity顶部中间的播放按钮,图片会受重力影响掉落

-
了解组件编写
同样的,有些组件系统本身就带有(如重力组件),但是有些不经常用的组件或定制化较高的组件,我们就需要自己编写或到资源商店等途径获取
如下图,创建一个move脚本,并拖拽挂载到Capsule上,脚本组件即可生效,双击move脚本,也可以打开VS编辑器编写脚本

选择脚本,在检查器窗口点击打开

打开VS,即有如下初始化代码,我们可以在初始化代码基础上进行脚本开发
using System.Collections; using System.Collections.Generic; using UnityEngine;public class move : MonoBehaviour {// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){} } -
运行脚本
using System.Collections; using System.Collections.Generic; using UnityEngine;public class move : MonoBehaviour {private void Awake(){Debug.Log("awake");}void Start(){}void Update(){} }编写好脚本之后,我们可以运行脚本尝试

也可以通过 顶部导航栏 => 窗口 => 常规 => 控制台来打开控制台
或快捷键
Ctrl + Shift + C
2.1.1. 熟悉组件
-
创建组件
- 顶部菜单栏 => 组件
- 检查器窗口 => 添加组件(可以通过搜索框快捷搜索想要的组件,自己写的脚本组件也可以搜索到),如果搜索组件不存在,可以创建并挂载新脚本脚
-
启用组件

-
控制组件
如图,点击三个点模样的按钮可以对组件进行操作

-
复制粘贴组件
选择复制组件 -> 再选择粘贴为新组件,即可完成组件粘贴(同时复制组件值)
如果选择的是粘贴组件值就是仅仅粘贴值,不会创建新组件(只有在同一种组件才会有效)
-
2.1.2. Debug调试
-
Debug输出
void Start() {Debug.Log("Debug.Log");Debug.LogWarning("Debug.LogWarning");Debug.LogError("Debug.LogError"); }
-
Debug绘制
为什么需要Debug绘制呢?
-
为一些抽象代码提供可视化,方便调试,比如你可以尝试运行下面一段代码,操作方向会绘制相应射线方便调试
// 水平轴 float horizontal = Input.GetAxis("Horizon"); // 垂直轴 float vertical = Input.GetAxis(" Vertical"); // 创建三维向量 Vector3 dir = new Vector3(horizontal, 0, vertical); Debug.DrawRay(transform.position, dir, Color.red);
射线的第二个参数是方向,而且射线是长度无限的,为了优化才将射线长度显示有限长
// 起点、终点、颜色 Debug.DrawLine(new Vector3(1, 0, 0), new Vector3(1, 1, 0), Color.black); // 起点、方向、颜色 Debug.DrawRay(new Vector3(1, 0, 0), new Vector3(1, 1, 0), Color.white);Debug绘制主要的作用是做参考,如图,在启动脚本后,绘制了黑色线段和白色射线,还有也层级窗口出现如果了
Debug Updater,如果你在场景窗口找不到Debug绘制的线段,不妨双击Debug Updater居中。
-
2.1.3. 脚本生命周期
-
创建C#脚本后,默认创建了Start方法和Update方法
方法名称 调用时间 Awake 最早调用,所以一般用于实现单例模式(调用一次) OnEnable 组件激活时调用一次,之后激活一次调用一次 Start Update前,OnEnable后调用一次 FixedUpdate (固定频率调用) Update (每帧调用一次,调用频率不固定) LateUpdate Update之后调用(每帧调用一次) OnDisable 组件未激活调用 OnDestroy 销毁后调用一次 注意事项:初始化要放
Awake里面而不是OnEnable -
生命周期体验(记得保存后再运行,运行流程可以参考上一节)
-
Awake
private void Awake() {Debug.Log("awake"); } -
OnEnable
private void OnEnable() {Debug.Log("onEnable"); }组件激活时调用一次

点击取消勾选框,模拟激活调用

-
Start
void Start() {Debug.Log("start"); }启动项目,
-
FixedUpdate
private void FixedUpdate() {Debug.Log("fixedUpdate"); }
-
Update
void Update() {Debug.Log("update"); }运行脚本后,每帧调用一次(可以选择折叠显示)

-
2.1.4. 执行顺序
-
小试验
创建test1和test2两个脚本
-
test1
public class text1 : MonoBehaviour {void Start(){Debug.Log("TEXT--1");} }test2
public class test2 : MonoBehaviour {void Start(){Debug.Log("TEXT--2");} }
如下图,我们并不能控制同一物体的不同脚本的同一个生命周期的代码执行顺序
-
执行顺序
同一物体的所有脚本的同一生命周期按序执行,一般我们在同一脚本通过生命周期、代码位置来控制代码的执行时机

但是如果我们想要控制不同脚本的执行顺序,就需要用unity来进行脚本执行顺序控制(可以看到,在未添加我们自己编写的脚本时,就有默认的系统初始化脚本了)

打开方式:
-
方式1

-
方式2
顶部菜单栏 => 编辑 => 项目设置 => 脚本执行顺序
-
-
排列脚本顺序
添加脚本

每个脚本都要权重值,权重值决定了脚本的执行顺序,权重值越小,脚本越先执行(所以系统初始化脚本的默认权重值为负,一般来说,我们自己一般自己编写的脚本权重值都为正)

除了手动在权重值框输入值来改变脚本权重外,还可以拖动脚本来改变脚本执行顺序,如下图,test1拖动到test2上面后,test1权重值变为50,排在另外两个脚本中间

另外,改变权重值后,不要忘记应用
-
修改后的执行顺序

-
注意事项
同一个物体的脚本执行顺序只是说不同脚本的同一生命周期的执行顺序,而不是脚本总统的执行顺序
- 也就是说,如上面我们的顺序,
- 是test1的Awake生命周期 => test2的Awake生命周期 => 是test1的OnEnable生命周期 => …
- 而不是test1执行完后才执行test2
- 也就是说,如上面我们的顺序,
-
2.1.5. Transform
-
获取属性
-
获取位置
Debug.Log(transform.position); Debug.Log(transform.localPosition); -
获取旋转
Debug.Log(transform.rotation); Debug.Log(transform.localRotation); Debug.Log(transform.eulerAngles); Debug.Log(transform.localEulerAngles); -
获取缩放
// 获取缩放倍数 Debug.Log(transform.localScale); -
获取坐标轴方向的单位向量
Debug.Log(transform.forward); Debug.Log(transform.right); Debug.Log(transform.up);这里放一张老图,承上启下,希望能吸引读者的阅读兴趣

-
-
看向定点
// 看向原点,相当于z轴的正方向指向原点 void Update() {transform.LookAt(Vector3.zero); } -
旋转移动
-
旋转
void Update() {// 自转,每帧旋转一度transform.Rotate(Vector3.up, 1);// 公转,参数分别为:// 1. point(Vector3.zero):旋转的中心点(世界坐标)。// 2. axis(Vector3.up):旋转的轴向(单位向量)。// 3. angle(5):旋转的角度(度)。transform.RotateAround(Vector3.zero, Vector3.up, 5); } -
移动
// 朝z轴正方向每帧移动0.1 transform.Translate(Vector3.forward * 0.1f);
-
-
层级关系
-
父物体操作
// 获取父物体 Debug.Log(transform.parent.gameObject);// 解除与所有子物体的父子关系 transform.DetachChildren();// 设置父物体 trans1.SetParent(transform);// 判断一个物体是否是一个物体的父物体(trans1是否是trans2的子物体) bool isParent = trans2.IsChildOf(trans1); -
子物体操作
// 通过游戏物体名称获取子物体 Transform trans1 = transform.Find("Child"); // 通过游戏物体索引获取子物体 Transform trans2 = transform.GetChild(0);// 获取子物体个数 Debug.Log(transform.childCount);
-
2.2. Unity基础类
2.2.1. 物体类
-
获取游戏物体
-
获取当前脚本所挂载的物体

创建空物体Empty和脚本ObjectTest,将脚本挂载在Empty
代码
// 1. 打印物体名 GameObject obj = this.gameObject; // this可以省略 Debug.Log(obj.name); // 也可以省略为 Debug.Log(gameObject.name);// 2. 打印物体名 Debug.Log(gameObject.tag); Debug.Log(gameObject.layer);打印输出(图层打印的图层的索引)

-
获取当前脚本所挂载的物体的激活状态
在Empty下面添加立方体Cube

并在脚本ObjectTest中声明Cube变量
public class ObjectTest : MonoBehaviour {public GameObject Cube;// ...void Start(){Debug.Log(Cube.name); // Cube} }Cube变量关联立方体Cube
执行上面脚本,可以发现脚本组件多了立方体选项(之所以是立方体选项,是因为我们在上面代码中声明Cube变量,如果声明的是Plane,拿就是平面选项),选择Cube或者直接将层级窗口的Cube立方体拖过去即可

-
获取激活状态
以前的active状态被启用,而且被分化为activeInHierarchy和activeSelf,为了了解他们之间的区别,我们在空物体Empty和立方体Cube中间加一个空物体MidEmpty

-
将Cube的激活状态打开,MidEmpty的激活状态关闭

并编写如下代码,发现使用activeInHierarchy输出False,activeSelf输出Ture
Debug.Log(gameObject.activeInHierarchy); // FalseDebug.Log(gameObject.activeSelf); // Ture// 也可以设置激活状态(对应的是activeSelf) gameObject.SetActive(false)所以activeSelf为未受父元素影响的激活状态,activeInHierarchy为受父元素影响的激活状态
这里我想到了串联电路,如下图,R1相当于父物体,R2相当于父物体
- 若R1关R2开,两灯都不亮,打开R1,R2就亮了
- 若R1关R2关,两灯都不亮,但是打开R1,R2还是不亮了

-
-
-
通过名称获取游戏物体
// 获取名称为Test的游戏物体 GameObject test = GameObject.Find("Test"); -
通过游戏标签来获取游戏物体
// 通过游戏标签"Test"来获取游戏物体 GameObject test = GameObject.FindWithTag("Test");
-
-
获取组件FindWithTag
获取组件无非是选择器,学过Web的CSS或者Bom操作的话应该很好理解
-
通过泛型获取组件
// 这里是获取当前物体的碰撞组件,如果需要获取其他组件,修改BoxCollider即可 BoxCollider bc1 = GetComponent<BoxCollider>();// 获取当前物体的子物体身上的组件 BoxCollider bc2 = GetComponentInChildren<BoxCollider>();// 获取当前物体的子父物体身上的组件 BoxCollider bc3 = GetComponentInParent<BoxCollider>();
-
-
添加组件
Cube.AddComponent<BoxCollider>(); -
预设体实例化为物体
和上面一样,先声明预设体变量,再运行代码,再预设体中选择需要实例化的预设体
public GameObject Prefab;再通过预设体来实例化为游戏物体
Instantiate(Prefab);// 将实例化的游戏物体指定为脚本绑定的物体的子物体 Instantiate(Prefab, transform);// 将实例化的游戏物体放在时间坐标原点且不旋转 GameObject Ins = Instantiate(Prefab, Vector3.zero, Quaternion.identity); -
销毁游戏物体
Destroy(Ins)
2.2.2. 时间类
-
使用时间类
-
游戏时间戳(游戏开始到当前时刻花费的时间)
Debug.Log(Time.time); -
时间缩放值(倍数)
Debug.Log(Time.timeScale); -
获取FixedUpdate的固定时间间隔
Debug.Log(Time.fixedDeltaTime); -
上一帧到当前帧的时间
Debug.Log(Time.deltaTime);
-
-
通过Time.deltaTime实现Time.time
public class TimeTest : MonoBehaviour {float timer = 0;bool isLog = false;void Update(){timer += Time.deltaTime;if ( !isLog && timer > 3){Debug.Log(Time.time);Debug.LogWarning("欢迎");isLog = true;}} }但是,如下图,会有误差,执行到
Debug.Log(Time.time);时,已经满足timer > 3,但是输出的结果却小于3
这是因为
Time.time包含游戏初始化的时间(如 Awake、Start 的执行时间),而timer是从Update()开始计算的,所以Time.time通常会比timer大。
2.2.3. Application类
-
文件路径
-
游戏数据文件夹路径(只读,打包后加密存储)
Debug.Log(Application.dataPath + "demo.text"); // F:/3D/Unity/Test1/Assets/demo.text -
持久化文件夹路径
Debug.Log(Application.persistentDataPath); // C:/Users/jhq13/AppData/LocalLow/DefaultCompany/Test1 -
StreamingAssets文件夹路径((只读,打包后不加密,如配置文件)
Debug.Log(Application.streamingAssetsPath); // F:/3D/Unity/Test1/Assets/StreamingAssets -
临时文件夹路径
Debug.Log(Application.temporaryCachePath); // C:/Users/jhq13/AppData/Local/Temp/DefaultCompany/Test1
-
-
控制是否在后台运行
Debug.Log(Application.runInBackground); // true也可以在文件 => 生成设置 => 玩家设置 => 玩家 => 来手动勾选是否在后台运行
-
打开url
Application.OpenURL("www.baidu.com"); -
退出游戏
Application.Quit();
2.3. 交互类
这一章相当于前端的方法绑定,对onclick等事件的监听
2.3.1. 键鼠操作
-
鼠标
对鼠标而已,0为鼠标左键,1为鼠标右键,2为鼠标滚轮
-
鼠标左键被按下
if (Input.GetMouseButtonDown(0){Debug.Log("鼠标左键被按下"); } -
鼠标左键被点击并持续按下
if (Input.GetMouseButton(0){Debug.Log("鼠标左键被点击并持续按下"); } -
鼠标左键被弹起
if (Input.GetMouseButtonUp(0){Debug.Log("鼠标左键被弹起"); }
-
-
键盘
键盘对应的就是KeyCode
-
键盘按键被按下
if (Input.GetKeyDown(KeyCode.A)) {Debug.Log("键盘A按键被按下"); } -
键盘按键被点击并持续按下
if (Input.GetKey(KeyCode.A)) {Debug.Log("键盘A按键被持续按下"); } -
键盘按键弹起
if (Input.GetKeyUp(KeyCode.A)) {Debug.Log("键盘A按键弹起"); }
-
2.3.2. 虚拟轴
虚拟轴就是一个数值在-1~1内的数轴,这个数轴上重要的数值就是-1、0和1。
-
当使用按键模拟一个完整的虚拟轴时需要用到两个按键,即将按键1设置为负轴按键,按键2设置为正轴按键。
-
没有按下任何按键的时候,虚拟轴的数值为0。
-
按下按键1的时候,虚拟轴的数值会从0-1进行过渡;在按下按键2的时候,虚拟轴的数值会从01进行过渡。
如果虚拟轴并不一定对应两个按钮,如果对应一个按钮,则为虚拟按键
-
虚拟轴设置
顶部菜单栏 => 编辑 => 项目设置 => 输入管理器

-
代码控制
在输入管理器找到虚拟轴对应的按键
void Update() {// 获取水平轴float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");// Debug.Log(horizontal + ',' + vertical);// 强制字符串拼接,避免数值相加Debug.Log($"{horizontal}, {vertical}"); }轴在-1到1

虚拟按键
if(Input.GetButtonDown("Junp")){Debug.Log('空格'); }
2.3.3. 触摸方法
-
单点触摸
// 判断单点触摸 if (Input.touchCount == 1) {// 触摸对象Touch touch = Input.touches[0];// 触摸位置Debug.Log(touch.position);// 触摸阶段switch (touch.phase){case TouchPhase.Began:break;case TouchPhase.Moved:break;case TouchPhase.Stationary:break;case TouchPhase.Ended:break;case TouchPhase.Canceled:break;} } -
多点触摸
// 判断多点触摸 if (Input.touchCount == 2) {Touch touch1 = Input.touches[0];Touch touch2 = Input.touches[1]; }
3. 组件进阶
3.1. 场景相关
3.1.1. 场景
场景相对于不同的页面,关系如下,且都是一对多的关系,而且场景包含场景管理类和场景类
游戏 > 场景 > 游戏物体 > 组件
-
场景类
-
获取场景信息
// 获取当前场景 Scene scene = SceneManager.GetActiveScene();// 场景名称 Debug.Log(scene.name); // SampleScene// 场景是否已经加载 Debug.Log(scene.isLoaded); // True// 场景路径 Debug.Log(scene.path); // Assets/Scenes/SampleScene.unity// 场景索引 Debug.Log(scene.buildIndex); // 0 -
获取场景游戏物体数组(可用于遍历)
GameObject[] gos = scene.GetRootGameObjects(); Debug.Log(gos.Length);
-
-
场景管理类
-
加载场景
如下图,初始化项目就有应该SampleScene初始化场景,相机、灯光都包含在这个场景中,但是有些时候我们需要用代码切换游戏场景

首先我们在打包游戏时必须勾选要使用的场景,如下图,每个场景都有索引(顶部导航栏 => 生成设置 => 添加已打开场景)

代码切换场景,跳转到1号场景MyScene
// 通过索引 SceneManager.LoadScene(1); // 通过场景名 SceneManager.LoadScene("MyScene");如果报如下图错误

上面报错的意思是没有将场景添加到构建文件,我们可以

-
创建场景
// 创建新场景MyScene1 SceneManager.CreateScene("MyScene1");// 场景数量 Debug.Log(SceneManager.sceneCount); // 2// 卸载场景 SceneManager.UnloadSceneAsync("MyScene1") -
加载方式
-
单场景加载
如果加载场景只用一个参数,那么默认为单场景加载(又叫替换加载),等价于代码
SceneManager.LoadScene("MyScene", LoadSceneMode.Single); -
多场景加载
也可以添加多场景加载(保留该场景,加载新场景,在场景视图中,这两个场景重叠)
SceneManager.LoadScene("MyScene", LoadSceneMode.Additive); -
异步加载
这里需要简单地大家了解一下异步和同步:
-
同步就是代码重上往下执行,这也是代码的默认执行方式,同步的好处就是代码执行顺序的相对固定的
-
但是如网络请求、资源加载等,比较耗费时间的操作对代码执行顺序没有要求,通过异步执行一边让代码继续向下执行,一边做这些耗时操作(如果加载的场景小可能感觉不出来,但是如果场景较大,同步加载会有明显卡顿感)
我们可以通过C#的携程来完成异步操作
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement;public class AsyncTest : MonoBehaviour {AsyncOperation operation;// 协程方法IEnumerator loadScene(){operation = SceneManager.LoadSceneAsync(1);yield return operation;}void Start(){// 调用协程方法异步加载场景StartCoroutine(loadScene());}void Update(){// 输出加载速度Debug.Log(operation.progress);} }
-
-
-
3.1.2. 灯光
-
不同种类的光源
如果场景中的光源没有图标和方向,需要打开场景中小工具可见的设置

-
定向光源
在创建项目时,系统就创建了一个默认的定向光源(平行光)。有光源就会有对应的原因,定向光源的阴影与光源的位置无关,而与光源的旋转角度有关。在自然界中,太阳里我们无穷远且强度够大,可以看作是定向光源。光是门很大的学问,但在这里,我只需要调调参数体验一下,不用太过深究。

-
聚光灯
类似于手电筒

-
点光源
萤火虫,火把,灯泡

-
区域(仅烘焙)
面光,类似于补光板,但区域灯光好像没有效果,这和灯光烘焙有关。

-
-
灯光烘焙
实时光源是非常消耗游戏性能资源的,而在很多场景光都并不需要实时渲染计算;这时候,我们就需要烘焙灯光,烘焙后,就算删除灯光,灯光效果被保留。

开始尝试烘培灯光,在 Unity 中,Contribute GI 是 “贡献全局光照(Global Illumination)” 的意思,是控制物体参与全局光照计算的关键标记。

在照明窗口中开始灯光烘焙。

然后,风扇就开始转了,而且右下角可以看到烘焙进度条。

烘焙完后就成了这个样子,我将光源删除后,平面依旧有光照效果。

其实,烘培就算将需要烘培的游戏物体的纹理重新给计算了遍,相当于对光照进行预处理,在游戏运行时不过多地占用计算资源。烘培后,在场景文件夹下就会生成相应的烘培贴图,删除烘培贴图后,烘培的效果就就不会存在了。

3.1.3. 相机
-
走进相机
和灯光一样,相机也是创建游戏默认存在的物体点击相机,右下角就会显示相机的预览图

-
清除标志
-
纯色(纯色填充)

-
不清除(不填充)

-
天空盒(资源填充,注意,天空盒也有光照)

-
仅深度清除标志与深度

-
深度
深度相当于相机的权重,如果场景中有多个相机,默认情况下,会优先显示深度值高的相机
-
仅深度清除标志
深度高的相机的画面拍摄不到的部分交给深度低的相机(相当于相机图层的叠加,深度高的相机的画面在上面,低的在下面)
-
-
-
剔除遮罩
相机不想拍摄哪个图层就取消对应图层的勾选

-
视野
控制透视相机前面可视区域的焦距矩形的大小,正交相机没有这个属性。

-
剪裁平面
摄像机显示的物体离摄像机的水平距离,如下图,摄像机只显示距离0.3到1000的物体的画面

-
Viewport矩形
-
X和Y
表示相机内容在游戏画面的位置(默认完全显示处理),如图,会在(0.5,0.5)位置。

-
W和H
表示相机内容占游戏画面宽高的百分比,因为X和Y的例子会(默认完全显示处理),所以为了方便对比,我X和Y也设置了值。当然,如果想要时,相机画面想要和游戏画面等比缩小,那么W和H值相等,想要缩小后的画面居中,则需要X+Y需要等于W或H。

-
-
目标纹理
创建渲染器纹理,将渲染器纹理绑定给相机的目标纹理,再将这个纹理作为平面Plane的纹理,发现平面的纹理是相机的拍摄内容(因为平面显示的相机内容 => 相机内容被平面显示 => 显示的内容又被相机拍到…这样就会循环)

-
-
相机的投影种类

-
透视摄像机
透视摄像机有着近大远小的效果,是Unity的3D项目的默认相机。正是因为透视摄像机存在近大远小的效果,所以当两个同样大小的物体到摄像机的距离不同时,我们可以看到物体的大小随距离的增加而减小,因此它们之间有明显的距离感。

-
正交摄像机
正交摄像机没有透视摄像机具有的近大远小的效果。所以当两个同样大小的物体到摄像机的距离不同时,其显示出的大小仍然是相同的,自然也不会产生距离感。这种摄像机一般用于照射平面,常常用在2D游戏和UI的开发上。

-
3.1.4. 音视频
-
音频
-
音频监听
摄像机有一个默认音频监听组件Audio Listener,让我们可以听到声音

但是,场景中只能有一个摄像机可以有Audio Listener组件,否则会报如下错误

-
播放声音
给声音添加Audio Source组件,并绑定MP3音频资源文件,这里可以去爱给网(免费 音效素材 免费下载 - 爱给网)等网站下载免费资源。

3D声音设置

-
脚本控制音频播放
当然,音频在游戏中有背景音乐、角色台词配音、音效等等,具体情况具体使用
还有一个**PlayOneShot()**方法,没有暂停或继续,直接播放一遍,适合比较短,且必须播放完的音频,而且可以同时多次播放。
using System.Collections; using System.Collections.Generic; using UnityEngine;public class AudioTest : MonoBehaviour {public AudioClip music;public AudioSource player;void Start(){player = GetComponent<AudioSource>();player.clip = music; // 开始时就播放音频}private void Update(){if (Input.GetKeyDown(KeyCode.Space)){if (player.isPlaying) {//player.Stop(); // Stop()是停止播放,Pause()是暂停播放player.Pause();}else { //player.Play(); // Play()是开始播放,UnPause()是继续播放player.UnPause();}}} }
-
-
视频
-
平面播放视频
播放视频的组件是Video Player,视频相比于音频的播放,需要实际的载体,所以我们需要借助渲染器纹理来播放,将视频和渲染器纹理绑定到Video Player组件,并将渲染器纹理给平面,就可以在平面播放视频

-
UI播放视频
在UI画布中添加原始图像,在原始图像中绑定渲染器纹理也可以播放视频

-
脚本控制视频播放(UI播放视频获取组件时会有所不同)
public class VideoTest : MonoBehaviour {private VideoPlayer videoPlayer;void Start(){videoPlayer = GetComponent<VideoPlayer>();}void Update(){if (Input.GetKeyDown(KeyCode.Space)){if (videoPlayer.isPlaying){videoPlayer.Pause();}else{videoPlayer.Play();}}} }
-
3.2. 物理相关
3.2.1. 刚体与碰撞
-
刚体
给物体添加 Rigidbody 刚体组件,有如下属性
-
角阻力:旋转时的阻力
-
使用重力:受重力影响
-
Is kinematic:是否符合动力学(是否会受其他离影响,如其他刚体撞击)
-
碰撞检测:设置碰撞检测频率(性能消耗)

-
Constrains:限制位置移动或旋转方向

比如说冻结旋转,则物体从地图边缘掉落时在空中不会旋转

-
-
碰撞
想要物体有碰撞效果,就要给物体加上碰撞器,不同的物体有不同的碰撞器

以场景中的球形碰撞器为例,将碰撞器的半径从0.5改到1再运行游戏,发现物体没有落到地上,而是平面紧挨着绿色的球,说明和平面发送碰撞是碰撞器,而不是物体。
两个物体要碰撞,这两个物体必须都有碰撞体,且其中一个物体有刚体(因为碰撞必须有初始度,没有刚体,就不会受力运动,自然就不会发生碰撞)

-
监听碰撞
这里使用免费资源Procedural fire来进行碰撞实践

创建一个粒子火焰,再为这个火焰添加圆形碰撞器和刚体

编写脚本监听碰撞并控制爆炸
物体有碰撞器,我们可以直接调用爆炸的函数来监听碰撞(其中传过来的参数collision为和物体发生碰撞的物体的信息类,这里是平面Plane对象的信息类)。
public class FireTest : MonoBehaviour {// 创建爆炸预设体public GameObject explosion;// 碰撞检测private void OnCollisionEnter(Collision collision){Instantiate(explosion, transform.position, Quaternion.identity); // 创建爆炸// 最好给爆炸物体添加脚本,让他自己销毁自己Destroy(gameObject); // 销毁自身}// 持续检测private void OnCollisionStay(Collision collision){}// 离开检测private void OnCollisionExit(Collision collision){} }
-
-
是否为触发器
如果碰撞体组件勾选了是触发器,那么物体不能产生碰撞,而是被其他物体穿过

触发器和碰撞器一样也有三个方法供挂载的脚本调用(这里我将紫色的墙重命名为Door,并将脚本挂载到了上面)
private void OnTriggerEnter(Collider other) {Debug.Log("触发器进入");GameObject door = GameObject.Find("Door");if(door != null){door.SetActive(false);} }private void OnTriggerStay(Collider other) {Debug.Log("触发器停留"); }private void OnTriggerExit(Collider other) {Debug.Log("触发器退出"); }-
不同
碰撞方法:传过来的参数collision为和物体发生碰撞的游戏对象的信息类,要通过collision.collider来获取碰撞物体
触发方法:传过来的参数other为被触发物体,other直接获取触发物体
在游戏中,我们常常将触发器物体的Mesh Renderer网格渲染器取消勾选,让触发器不可见

-
3.2.2. 特殊物理关节
-
弹簧
在平面上创建Player胶囊体,和Door立方体,给Door添加Hinge Joint组件,可以在Door顶部看见小箭头,这就是添加的物理关节

如图,胶囊体碰撞门,门会从顶部发生旋转而不会倒,有的类似于狗洞的门(胶囊体碰撞后比较容量倒,这里可以将胶囊体换成立方体或者将胶囊体的刚体的旋转冻结)

-
改变物理关节
如下图,Hinge Joint有Anchor锚点和轴两个属性,Anchor是以物体的原点为中心的百分比值,如图中Y为0.5,那么物理关节位于离物体的原点的50%的Y范围的壁纸,所以物理关节在物体正上面的面中心。将X调为-0.5,Y调为0后,物理关节在物体正左面的面中心。将轴的X改为0,Y改为0后,物理冠军的朝向又变为的Y轴的正半轴方向。

关节改变后,玩家再碰撞门,门就绕左侧的轴转动(类似于真实的门的运动)

还可以通过以下设置让门自动旋转(类似于男生女生向前冲的关卡)

-
-
铰链
创建两个立方体并给这两个物体加上刚体组件,上面的立方体冻结旋转和位置,再加上Spring Joint物理组件,并连接上下面的立方体,则两个立方体被弹簧连接。至于像弹簧的弹力系数,长度等物理属性,可以调节Spring Joint下面的参数。

还有一个Fixed Joint组件可以固定连接,感觉用得比较少,这里就不演示了。
3.2.3. 物理材质
-
在现有平面的基础上,创建一个斜面,复制斜面的旋转给物体,并将物体放到斜面上。给物体加上刚体后,发现立方体因为摩擦力原因静止在斜面上。

创建物理材质并绑定给平面的碰撞器,并调小摩擦力(默认为0.6)。摩擦组合和反弹组合是两个物体发生摩擦和反弹时摩擦力和弹力的计算方式。

发现立方体从斜面滑落。

3.2.4. 射线检测
-
射线检测
了解了透视相机和正交相机的成像原理,就很容易明白,以观察点为端点,可以发生一条射线击中相机的常见中能够展现的任意一个物体。基于这样,我们可以完成3D第人称的指哪走哪(类似于英雄联盟的鼠标点击移动方式)。
void Update() {if (Input.GetMouseButtonDown(0)){// 方式1// Ray ray = new Ray(Vector3.zero, Vector3.up); // 方式2Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit; // 获取射线和物体的交点bool res = Physics.Raycast(ray, out hit); // // 碰撞检测// 如果产生碰撞if (res){transform.position = hit.point; // 设置当前物体的位置为射线和物体的交点}// 多检测(穿透检测)// 这里的参数100是检测100内,1<<10是只检测第10个图层的物体RaycastHit[] hits = Physics.RaycastAll(ray, 100, 1<<10);} }
3.2.5. 粒子、线条与拖尾
-
创建粒子系统

粒子系统组件有以下设置,一般粒子系统多用于特效制作,这里了解一下即可。

选项 功能说明 持续时间 粒子系统单次发射粒子的持续时长(单位:秒)。若 “循环播放” 开启,会在持续时间结束后重新开始发射。 循环播放 勾选后,粒子系统会在持续时间结束后循环发射粒子,无限重复。 预热 勾选后,粒子系统在启动时会预先生成一部分粒子,让效果从视觉上更饱满(常用于需要立即呈现完整效果的场景,如火焰)。 启动延迟 粒子系统启动后,延迟指定时长(单位:秒)才开始发射粒子。 起始生命周期 单个粒子从生成到消失的时长(单位:秒)。 起始速度 粒子生成时的初始速度大小。 起始大小 粒子生成时的初始大小。 起始颜色 粒子生成时的初始颜色。 -
绘制线条
创建空物体并挂载Line Renderer组件。

添加材质,添加Line Renderer组件后,Transform组件不再影响位置,而是受Line Renderer组件的位置选项影响。


还可以代码控制画线
private void Start() {// 设点线段位置// 获取线段渲染器LineRenderer lineRenderer = GetComponent<LineRenderer>();lineRenderer.positionCount = 3;lineRenderer.SetPosition(0, Vector3.zero);lineRenderer.SetPosition(0, Vector3.one);lineRenderer.SetPosition(0, Vector3.down); } -
拖尾
建空球体并挂载Trail Renderer组件,运行后移动物体,物体会留下痕迹,并过段时间会消失,拖尾的其他效果需要在Trail Renderer组件设置,这里就不过多介绍,了解一下即可。

这里放个参数,看看效果

3.3. 动画相关
3.3.1. 动画小试
-
打开动画窗口
依次点击窗口 => 动画 => 动画,打开动画窗口
-
给添加动画中记录的属性,比如我们只进行移动、缩放、旋转

-
添加移动动画

-
在当前视图的时刻可以点击添加关键帧来添加帧,通过移动关键帧,可以粗略的控制运动曲线和改变物体运动轨迹

-
旧版动画
-
设置播放器

选择是否循环播放

代码控制动画播放(既然用代码控制播放,就不要添加默认动画)
void Update() {if (Input.GetMouseButtonDown(0)){GetComponent<Animation>().Play("demo");} }如果报错The AnimationClip ‘demo’ used by the Animation component ‘Cube’ must be marked as Legacy.

-
新版动画
-
新版动画添加了动画控制器

-
动画控制器
如图,我添加了一个demo1动画来上下移动,活跃的动画为橙色高亮,播放时动画块有蓝色的进度条

-
代码控制
“D”控制向右移动(对应demo),"W"控制向上移动(对应demo)
void Update() {if (Input.GetKeyDown(KeyCode.D)){print("demo");GetComponent<Animator>().Play("demo");}else if (Input.GetKeyDown(KeyCode.W)){print("demo1");GetComponent<Animator>().Play("demo 1");} }如果想要默认不播放,可以将默认状态设为空状态

-
-
-
角色动画的使用
-
导入包: Character Pack: Free Sample

-
导入包后,可以在右下角预览动画

-
如图,如果模型显紫色,这是因为材质丢失导致的。在 Unity 中,当模型或预制体的材质无法正确加载(比如材质文件缺失、路径错误,或者项目的渲染管线和材质不兼容)时,就会显示为默认的紫色(洋红色)。
具体到你的情况,大概率是以下原因之一:
- 材质文件未正确导入:这些预制体的材质可能在导入或迁移过程中丢失了,比如缺少
.meta文件(Unity 用来记录资源信息的文件),导致材质引用失效。 - 渲染管线不兼容:如果你的项目使用了 URP(通用渲染管线)或 HDRP(高清渲染管线),而这些预制体的材质是为内置渲染管线设计的,就会出现材质不兼容,从而显示为紫色。
-
我这里就是用的URP(通用渲染管线)

- 材质文件未正确导入:这些预制体的材质可能在导入或迁移过程中丢失了,比如缺少
-
3.3.2. 角色动画
-
创建场景
-
如图,创建一个平面并给平面添加淡蓝色材质
-
拖入素材里Prefab中的任务模型,并挂上新建的脚本PlayerController

-
-
添加动画
-
给Player绑定Animator组件并添加动画器

-
将素材里的动画拖入动画控制器

-
上节中,我们使用了trigger触发控制器,这里并不是临时切换,更适合使用bool控制器,设置好控制器后,创建相互过渡并设置过渡条件

测试

-
-
控制脚本PlayerController如下,这里可以先编写角色动画切换的代码
public class PlayerController : MonoBehaviour {public Animator animator;void Start(){animator = GetComponent<Animator>();}void Update(){// 获取输入float horizontal = Input.GetAxis("Horizontal"); // 水平轴float vertical = Input.GetAxis("Vertical"); // 垂直轴// 旋转Vector3 dir = new Vector3(horizontal, 0, vertical); // 向量// 当用户按下了方向键时if(dir != Vector3.zero){transform.rotation = Quaternion.LookRotation(dir); // 面向向量animator.SetBool("IsRun", true);// 朝向前方移动(因为我已经转向了)transform.Translate(Vector3.forward*2*Time.deltaTime);}// 当用户松开了方向键时else{animator.SetBool("IsRun", false);}} } -
解决人物移动不跟手问题
将过渡持续时间改为0即可(虽然这样会使移动跟手,但是动画切换没有过渡过程,会比较生硬)

3.3.3. FBX导入器
-
关于FBX 导入器
-
在unityd1资源中,我们常常看见如图像预设体和预设体变体的图标,这是Unity 中的 FBX 导入器图标,用于导入 FBX 格式的文件(通常包含模型、动画、骨骼等资源)。

-
FBX 导入器是专门处理 FBX 资源导入设置的入口,你可以在 “Model”“Rig”“Animation”“Materials” 这些标签页中,分别对模型的结构、骨骼绑定、动画、材质等属性进行详细配置,以便将外部创建的 3D 资源(如从 3ds Max、Maya 等软件导出的 FBX 文件)正确导入到 Unity 项目中并使用。
-
-
骨骼
骨骼的类型的旧版对应旧的动画器Animation,泛型对应其他各种模型,如爆炸动画,开门动画等等,人体动画被使用较多,所以被单独分一类,经过骨骼绑定的不同的人体模型可以使用同一个人形动画。

-
动画

-
曲线和帧
-
动画曲线
动画曲线对应着动画的每个时刻,他随动画的播放而改变值,但是动画曲线不影响动画的播放,而是可以在动画的每个时刻取到不同值,比如有一个控制火焰大小的方法,火焰在动画的播放中先变大后变小,我们就可以做出对应的动画曲线,再将值传入控制火焰大小的方法。
改变动画设置后记得“应用”

-
绑定曲线数值
这里需要浮点数与动画的曲线名同名来匹配

-
代码控制输出曲线值
void Update() {// ...// 打印曲线值Debug.Log(animator.GetFloat("Test")); }输出:

-
-
事件
通过动画事件,可以在动画播放的不同时机采取不同的操作,比如在run动画中,角色的脚着地时播放相应的音效

在脚本中添加相应函数
void leftFoot() {Debug.Log("leftFoot"); }void rightFoot() {Debug.Log("rightFoot"); }输出:

3.3.4. 混合动画与分层
-
创建混合树

-
双击打开新建的混合树
当然混合树还可以嵌套混合树

-
动画状态
-
前面提到,橙色的为激活的动画状态,灰色的为未激活动画状态
-
AnyState为任何动画状态,如下图,表示任何动画状态都可以之间过渡到run、idel动画状态

-
-
子状态机
子状态机中橙色的动画状态的作用的返回上一层状态机

-
动画分层
动画默认为图层Base Layer,我们还可以创建其他图层

-
分层权重
为New Layer添加动画wave,并设置为循环播放,运行游戏,发现动画器中的挥手动画和待机动画都在执行,新动画层级下面是灰色的条游戏界面也只有代价动画在执行


这是因为新建的动画分层默认权重为0,层级下面的蓝条也表示层级权重,修改权重,角色在待机的同时就可以挥手(这里记得给跑步动画myRun添加循环播放)

但是我们在运动时,仍然是边站立边挥手,说明跑步动画被遮盖掉了,我们需要将身体动画给Base Layer动画层控制,手臂动画层给New Layer层控制,我们可以通过Avator遮罩来实现

动画层级添加Avator遮罩

3.3.5. 视频动捕方案
千面智娱:https://www.qmai.vip/consult
-
关于千面智娱的使用教程,注册千面智娱的账号后,会有,这里就不再赘述,将视频的识别任务发送后,等待fbx文件生成。

导入模型

-
动画使用问题
如图,千面的模型我们不能直接拿来用,我们的unity小人就想泡在水里一样,我们将骨骼绑定改为人形,再应用即可

-
舞!舞!舞!
要想两个模型一起跳舞,需要重新复制一个fbx模型,动画类型分别为泛型为人型即可。

那么,dancing!

3.3.6. 反向动力学
反向动力学(Inverse Kinematics),简称IK。在现实中,如果想指向某个方向,需要手臂带动手腕再带动手来完成指向,而反向动力学及为手带动手腕带动手臂。
正向运动学(FK):需手动设置每个关节的旋转,最终确定末端骨骼位置。
反向运动学(IK):直接指定末端骨骼(如手部、脚部)的目标位置,系统自动计算中间关节姿态。
-
编写代码
public class PlayerController : MonoBehaviour {public Transform target;// IK写到这个方法内void OnAnimatorIK(int layerIndex){// 设置头部IKanimator.SetLookAtWeight(1);animator.SetLookAtPosition(target.position);// 设置右手IKanimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);// 旋转animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);// 设置右手IKanimator.SetIKPosition(AvatarIKGoal.RightHand, target.position);// 旋转animator.SetIKRotation(AvatarIKGoal.RightHand, target.rotation);} }绑定target

打开动画图层的IK处理

-
效果
-
头部IK指定

-
3.4. 导航
3.4.1. 导航网格
参考:Unity AI Navigation自动寻路 - 知乎
-
创建测试场景
如图,创建楼梯和斜坡两种上升方式

-
添加导航网格
选中物体发现Navigation Static为灰色状态,这是因为在2022新版中,这个方法已经弃用了

在窗口 => 包管理器中可以找到AI Navigation组件

为ground添加父组件,并在父组件挂载组件NavMeshSurface
NavMeshSurface是 Unity 中用于生成导航网格的组件。它表示特定NavMesh Agent类型的可行走区域,并定义了场景中应该构建NavMesh的部分。以下是
NavMeshSurface组件的一些属性介绍:- Agent Type:指定使用该
NavMeshSurface的NavMesh Agent类型。这对于烘焙设置和在寻路期间将NavMesh Agent匹配到适当的表面非常有用。 - Default Area:定义在构建
NavMesh时生成的区域类型。 - Generate Links:如果启用此选项,由
NavMeshSurface收集的对象将在烘焙过程中被视为生成链接。有关更多信息,请参阅链接生成部分。 - Use Geometry:选择用于烘焙的几何体。
- Object Collection:定义用于烘焙的 GameObject 集合。
- Advanced Settings:高级设置部分允许您自定义以下附加参数。
- NavMeshData(只读):定位存储 NavMesh 的资产文件。
点击烘培Bake,如图,蓝色部分即为可以行走的导航网格

- Agent Type:指定使用该
-
导航代理
-
代理设置
如图,可以在导航的代理部分来设置代理的半径,高度,可跳跃高度,可行走坡度。如下图的0.5为代理的半径,2为代理高度,0.75为可跳跃高度,45°为可行走坡度

还可以拖动窗口标签和检查器窗口合并

我们为在场景中创建一个胶囊体当作玩家,并为胶囊体添加组件Nav Mesh Agent组件。如图,胶囊体被一个贴合的胶囊体绿框(Capsule Collider)和一个圆柱体绿框包裹(Nav Mesh Agent)。另外,在Nav Mesh Agent组件中,我们可以进行各种导航设置。

-
移动脚本编写
using UnityEngine.AI;public class MoveController : MonoBehaviour {public NavMeshAgent agent;void Start(){// 获取导航网格代理agent = GetComponent<NavMeshAgent>();}void Update(){if (Input.GetMouseButtonDown(0)){// 发射射线,获取鼠标点击位置Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out RaycastHit hit)){// 设置导航位置为点击位置agent.SetDestination(hit.point);}}} } -
导航代理体验
点击高台,玩家可以顺利从斜坡和楼梯走上去,而且我们可以发现玩家可以从楼梯侧面直接跳上第二级楼梯,因为我们设置的步高为0.75,而我们一级台阶只有0.5左右,而第一级台阶只有0.1左右,所以玩家可以顺利地直接跳上第二级楼梯。

-
3.4.2. 网格链接
-
动态障碍物
在关卡类游戏中,常常在解密完成后,障碍物会移动走,这就是动态障碍物。但是就算我们移开障碍物,如果不重新烘培网格导航,我们还是无法通过(蓝色的导航网格没有连接)

这时候我们就可以使用组件Nav Mesh Obstacle添加导航网格障碍物。

勾选切割后就可以实现移动绿色墙壁时自动切割。移动阈值、静止时机、是否仅在静止时切割相当于是防抖设置,是超过移动阈值或者静止时间,才会重新烘培导航网格(但是这些设置都会对游戏性能有影响)。

-
生成链接
打开生成链接Genernate Link,就可以配置网格链接的掉落高度和跳跃距离(记得要重新烘培)

效果如图

如果增大跳跃距离,还可以在两个平台后楼梯间跳跃

-
自定义链接
如图,创建了两个紫色的方块Position1和Position2,并为Position2添加了组件Off Mesh Link,绑定组件的起始和结束位置为Position1和Position2,并勾选双向和已激活,就可以在两个位置之间来回跳跃了(类似于传送阵);并且,勾选自动更新位置后,在运行程序时,如果Position的位置改变,传送点也会实时改变。

3.4.3. 导航成本
-
导航区域
在导航代理的旁边还有区域部分,不同的区域的颜色不一样,可以发现我们前面的导航网格的颜色都为默认为区域0的颜色。

给导航区域划分的原因就是为了通过给导航设置成本,来智能计算导航路径。通常,我们在不同的地形性质的速度是不一样的,那么时间就是成本,那么我们就划分不同的地形或者区域为不同的导航区域,赋予不同的成本,导航系统会自动计算出时间(成本)较小的路线。

先创建Water导航区域,并设置权重为20;创建一个立方体River来模拟河流,为River挂载组件NavMeshModifier,然后重新设置导航区域为Water

重新烘培导航,并运行游戏,点击River的另一侧,发现玩家的行走路线绕开了River,如果调小Water导航区域的成本,并重新烘焙,则不会绕开。

3.5. UI界面
3.5.1. UI小试
-
用到的UI资源为Fantasy FreeGUI
-
创建画布
创建画布操作流程如下,创建画布后,还会创建一个EvenSystem事件系统,一般我们编辑UI是在2D模式下创建的 ,而且想要在Canvas画布中展示的元素都要是Canvas画布的子元素
创建的画布如图,和游戏窗口的比例是一样的

-
Canvas组件
-
显示图像
创建图像:层级窗口 => 鼠标右键 => UI => 图像
再选择源图片即可将白色的图片背景板换为具体图片

-
渲染模式

-
屏幕空间-覆盖
不用额外的摄像机,先渲染摄像机内3D空间中的画面,再渲染画布覆盖在3D画面上,属于异步拍摄,这样画布内容一定在3D画面之上,摄像机视角方向永远垂直于画布
-
屏幕空间-摄像机
这个渲染模式需要绑定摄像机,而且Canvas永远面向摄像机,摄像机视角方向永远垂直于画布

和“屏幕空间-覆盖”一样,3D空间中的画面和画布的画面是同一个摄像机拍摄出来的,不过不再是异步拍摄,而是同步拍摄,只要3D空间中的物体在空间中比画布离摄像机更近,那么层级就比画布高

-
世界空间
摄像机视角方向不一定垂直于画布,可以旋转
-
-
排序次序
用于设置多个画布的渲染的层级顺序
-
-
Canvas Scaler
-
UI缩放模式
UI缩放模式 说明 恒定像素大小 UI 元素直接以 像素值 渲染,与屏幕分辨率无关 屏幕大小缩放 以 参考分辨率 为基准,根据屏幕实际分辨率动态缩放 UI 恒定物理大小 UI 元素以 物理单位(如厘米、英寸) 渲染,与屏幕 DPI(每英寸像素数) 关联
-
3.5.2. 轴心点与锚点
每个UI元素都有锚点和轴心点两个属性,可以通过如下方式快速预设(黄点为锚点,按住Shift,出现黄点蓝点为轴心,)

-
锚点相当于画布的参考点(原点),轴心点相当于UI元素的参考点,表示UI元素的参考位置。如果锚点和轴心点重合,则UI元素的坐标为(0,0,0)

-
锚点
锚点并不单纯只是一个点,而是由四个点组成
-
锚点位置
锚点位置具体用下面最大x,最小x,最大y,最小y来表示

这些值是用来表示为坐标占画布宽度的比例,乘100%就是百分比(如果理解不了就算了)

再放一张百分比的图方便理解和上面的图数据差了一点

-
UI元素离锚点的位置
放两张图,自己体会(注意这里算的是UI元素中心点,而不是轴心点)

-
屏幕适配
-
情况1:锚点汇聚一点
在不同屏幕下,图标离锚点位置不便
-
情况2:锚点分别在画布屏幕的四个点
在不同屏幕下,UI元素离锚点的位置不变,导致图片被拉伸,由因为锚点和画布是比例坐标,所以图片是被等比拉伸的,从而达到不同屏幕的适配
-
-
-
轴心点
上面也简单介绍过,轴心点是参考点,一般用于坐标,旋转,缩放的计算
3.5.3. UI控件
-
文字使用
文字使用分为新版和旧版

下面是旧版和新版的默认文本效果(白色为新)
、

旧版和新版属性板如下,可以调属性熟悉一下

-
旧版想设置一段文本的几个字样式,需要用html标签,有兴趣可以试试
-
使用新版要导入TMP Essentials

-
-
按钮的使用
按钮中默认有文字,如果不需要可以删除(新版和旧版按钮的差别也就只有文字不同),按钮自带图片主键,可以选择按钮图片背景

Interactable表示是否开启交互,下面的各种颜色则是设置按钮不同状态下的样式

点击过渡,将过渡方式改为sprite交换就可以把不同状态的按钮背景从颜色改为图片
点击可视化,可以展示按钮间的转跳逻辑(箭头,运行游戏后用鼠标,W,S或者↑、↓来选择按钮)

-
循环选择
可以选择导航属性为explicit(明确的),这样就可以设置明确的跳转逻辑

如果要循环选择(第一项和最后一项的循环),可以分别这样设置

-
添加事件
-
先添加事件
-
将脚本绑定在Canvas画布中
-
给事件绑定脚本
public void ButtonClick() {Debug.Log("onClick"); }
绑定成功

-
-
-
文本输入框
与按钮操作类似,这里省略大部分l类似的内容说明
-
和按钮相比起来,文本输入框多了一个提示文字PlaceHolder

这里简单讲一下,角色限制值的输入框字符限制,内容类型则是选择输入框类型,其他属性试一下就知道了

文本输入框可以绑定三个交互情况下的处理函数,和上面按钮处理函数使用基本相同

但是,需要注意的是,文本输入框需要创建一个对象来存储输入框里面的值,而且不同版本的文本输入框有些使用不一样
-
旧版
using UnityEngine.UI; // ... public class demoTest : MonoBehaviour {public InputField inputFied;// ...// 打印每次输入的值public void TextChanged(){Debug.Log(inputFiled.text);} } -
新版
using TMPro; // ... public class demoTest : MonoBehaviour {public TMP_InputField inputFied;// ...// 打印每次输入的值public void TextChanged(string str){Debug.Log(str);} } -
编写代码获取每次输入的值
然后层级组件就会新增InputField,点击Canvas,将InputField拖入文本输入框

在通过和按钮一样的事件绑定步骤后,即可绑定事件处理函数

-
-
-
选择
-
勾选框
如图,创建了两个勾选框

-
设置二选一
为两个勾选框Toggle添加组件Toggle Group,并将两个Toggle的Group属性绑定同一个Toggle Group即可

-
-
下拉框
如图,创建了下拉框,需要注意的是下拉框有一个子元素模板Template,这个模板是用来通过配置数据

除了在检查器窗口调整选项,我们还可以用代码对选项进行操作,如下图,通过代码为下拉框通过代码新增一个选项
// ... using UnityEngine.UI; public class DropdownTest : MonoBehaviour {void Start(){// 获取下拉组件Dropdown dropdown = GetComponent<Dropdown>();// 获取下拉框的选项List<Dropdown.OptionData> options = dropdown.options;// 在下拉框的选项中加入新选项options.Add(new Dropdown.OptionData("中国"));// 将原来的下拉框覆盖dropdown.options = options;} }运行结果

-
添加精灵图
模板默认结构如下

为Item添加图像,将图像调到需要的位置,将图像绑定项图像

最后再为精灵图绑定相应图片即可

-
-
-
滚动
-
滑动条
滚动条如图,隐藏Handle Slide Area,即可呈现血条样式

-
滚动视图
其中,Content为真正的内容区域,所以需要在滚动视图中展示的内容,直接展示到Content即可

滚动视图的原理和Mask遮罩组件有关,不激活Mask组件,则Content所有区域可见

-
-
面板
面板其实就是image图像,用来打组布局、适配屏幕用的,类似于H5的div

3.5.4. 常用UI组件
-
Mask
如图,白为父,黑为子,给父元素添加Mask组件,则子元素溢出父元素部分被被挡住了,不显示

-
Content Size Fitter
在很多情况下,文字的内容长度是不固定的,如果文本内容的宽度固定,高度自适应文字的内容,我们可以通过Content Size Fitter实现这样的效果(随着文本变化,文本区域宽度不变为垂直适应,反之高度不变为水平匹配)。

-
Layout Group
-
Vertical Layout Group
Horizontal Layout Group和这个组件使用类似,只不过布局方向不一样,他们相当于css的flex布局,可以调属性试试

-
Grid Layout Group
Grid Layout Group对应CSS的栅格布局

-
