本笔记是学习自夏村散人韩老师的unity教程https://www.bilibili.com/video/BV1B7411L74W,自己的一些记录和整理
一、基础
1.1 unity编辑器
右边栏是组件栏,物品、地形、相机等全都是对象GameObject,每个GameObject有自己的组件component,基本上所有物体都包含transform组件用于缩放和旋转。地形,相机等特殊物体可能有自己特殊的组件。
1.2 地形系统
创建地形
笔刷(依次是抬高降低、空洞、纹理、平台高度、平滑)
纹理
1.3 标准资源包
18年后的版本需要自己下载,可以在官网下载
[https://assetstore.unity.com/top-assets/top-download?q=Standard%20Assets&orderBy=1](https://assetstore.unity.com/top-assets/top-download?q=Standard Assets&orderBy=1)
也可以直接在编辑器windows->assets store
报错可以参考这个教程
https://blog.csdn.net/zhang2333333333/article/details/105000621
1.4 脚本
脚本也作为一种component,可以添加c#脚本,其中自带的start和update函数就是生命周期,会自动调用,其中start在GameObject初始化时调用一次,update每帧调用一次。public成员可以在脚本完成后通过unity编辑器拖拽给物体作为参数传入。
下面介绍一些脚本相关的类(组件):
Transform:可以对应一个unity中的GameObject
具体的类和方法可以看帮助
点击后打开api页面可以搜索
其中,manual是以教程的形式,api只是单纯的用法和参数介绍
如果没有代码提示,是由于默认编辑器没有设置的原因,参考
https://blog.csdn.net/LiYAnErr/article/details/105137157
以下是一个镜头跟随脚本demo
1.5 发布
若要选择窗口等模式,如下:
1.5.1制作游戏欢迎页面
1.unity中一个页面scene其实就是一个关卡,因此欢迎页可以视为一个关卡,该关卡啥都没有,只有一个脚本,按下某个键就开始游戏
scene名字随便起,比如叫welcome
2.脚本
脚本名字不能随便起,叫GameManager(也不是不能随便起,只不过叫这个的话可以拥有一个特殊的图标)
3.将脚本应用到物体上,理论上可以应用给场景默认自带的光照或者相机都可以,但是标准的应该是在该场景新建一个空的GameObject,并将脚本拖拽给他
4.调整发布场景编号
1.5.2 非全屏
二、动画
2.1 场景动画
unity支持外部动画模型导入(比如maya和3dmax),成为模型动画。当然也可以在unity中制作简单的动画,称为场景动画
2.1.1 录制
选择动画片段保存位置
点击录制按钮
2.1.2 多段动画的状态机转换
可以添加多个动画片段
添加多个动画片段后,默认只会播放第一个
如果需要组织多个动画片段,就需要animator controller
调整状态机使其能够转换到另一个动画片段
效果就是动画连着播放,然后在最后一个动画循环
除了鼠标操作状态机,也可以通过脚本进行特定条件下状态机的转换,如下创建一个转换条件c
有Float, int, bool, trigger类型,最后一个是触发器,也就是脉冲,触发完一次会自动回到初始状态等待下一次脉冲
然后添加一个脚本,指定触发条件
将脚本拖拽给cube,即可自动创建出animator controller这个组件
效果:停在第二个动画反复循环,按下空格(并当当前动画放完之后)才进入第三个动画
若需要按下空格时强制跳转到第三个动画,如下取消勾选
2.1.3 event
event事件,用于在某个特定时间点执行某个脚本中的函数
首先添加一个函数,这里是销毁,我们的目的是在最后一个动画结束后销毁
然后在最后一段动画(缩放的某个地方添加一个event),填入函数名
2.2 骨骼动画
2.2.1 space robot kyle资源包和配置
unity官方提供的一个资源包,可以在商店搜索下载
将rig调整为类人骨架humannoid,记得点击apply
这个骨架有个好处是可以容易的实现动画重定向(为一个骨架设计的动画可以移植到另一个骨架上),点击apply后configure按钮亮起,点击可以看到如下界面
这只是一个模型,拉取到场景中没有动画效果,我们可以从标准资源包如下位置拉取一个预实现看看效果
2.2.2 骨骼动画交互效果说明
对比我们可以发现预实现的人物有animation controller
我们可以为自己的机器人模型也添加同样的controller
人物随之可以摆动,但没有键盘交互效果
不难发现,预实现的人物对象中还有一些脚本
第一个脚本有[RequireComponent(typeof (ThirdPersonCharacter))],也就是包含了第二个脚本
所以我们只需要把第一个脚本拖拽给自己的模型
我们通过右边的capsule将框架调整到如上图所示大约包裹身体,此时运行如下图所示
这是由于地面检测原因,将其调大即可
2.3 曲线
曲线是用函数的形式让时间和某个变量关联起来,即value = func(time),func可以是二次函数或者别的函数,value的值便会发生变化,可以用这个值影响动画
比如:机器人在跑步的时候如果想让他吹一个泡泡,泡泡随时间变大变小
新建一个场景,添加机器人,新建一个animation controller,新建一个空状态,为状态添加一个人物跑步动作
并拖拽给机器人,机器人可以跑步
为了更好的演示,将机器人取消连接根节点(应该就是不转换为世界坐标),便可以原地跑步
接下来添加曲线,在controller的parameters中添加参数,参数名很重要,然后点击motion,unity会自动定位这个run动作的位置,点击它
点击edit便可以编辑属性值
找到曲线,添加,
曲线的名字必须跟parameters中某个变量的名字一样,点击曲线并选择一种曲线
选完后一定要点击一下下面的apply
下面来创建泡泡,新建一个GameObject命名为泡泡,并作为头部的子物体
点击transform中的reset,球体便会将本地坐标变为头部位置(和头部重合)
再将泡泡细微调整以下
新建脚本,设置机器人的动作(其实是设置机器人里面泡泡的动作)
这里的bubble当然也可以直接设置为public,然后再unity中直接将泡泡拖拽赋值。因为这个脚本最终会拖拽给机器人,泡泡是机器人的子物体,所以可以设置为private然后通过transform的Find函数获取子物体。
完事,运行,可以看到机器人跑步的过程中泡泡变大变小
2.4 动画层
动画层就是多个动画叠加,能同时展现,比如边走路边开枪
2.4.1 动画层的添加
新建场景,添加机器人,新建animition controller,添加新状态,添加走路动作
默认有个动画层,现在新建一个层比如叫ArmUp 抬手
新建状态Jump,添加motion JumpUp
运行发现并没有叠加动画,因为第二个动画没有添加权重
需要注意的是,blending采用的是override模式,所以ArmUp权重越大,手抬得越高,但是BaseLayer中的walk速度变慢了!!!
而如果采用addtive,则是叠加,整个人都被抬起来,效果就会变得走起路来一震一震的
2.4.2 遮罩mask
如上所说,如果要某一部分不受影响怎么做呢?手臂抬起来,但是同时也要正常走路,就需要mask遮罩
新建avatar mask,并打开
如下图所示将下半身的活动屏蔽
为该层的动作选择一层遮罩
这时候运行,就可以看到手抬到了最高(权重为1),walk也正常走路
2.4.3同步Sync
Sync按钮的意思是当前层不使用自己的动画状态机,而是用别的层已有的动画状态机。举个例子:走路,如果人物被击中那么走路的姿势就不太一样了,而其他的逻辑一样(如果其他逻辑比较复杂),那么就可以在其他层使用Sync,方便逻辑的复用
2.4.2 脚本方式叠加层
很多时候需要通过脚本的方式完成更加复杂的动画层逻辑,比如和鼠标交互
下面示例是按下鼠标左键,增加armUp的权重,最后增加到1
新建脚本
将脚本拖拽给机器人,并将一开始的ArmUp层权重设置为0,运行就可以看见效果了
一开始手放下,鼠标长按慢慢抬起,松开又慢慢放下
2.5 逆向运动学:注释动画
前面的场景动画和骨骼动画都属于前向运动学,也就是说每一个骨骼节点的动画效果其实是一层一层推出来的,比如抬起股关节,再抬起膝关节那么就表现为抬腿动作。
但有些情况下需要逆向推导,比如已知前面有个苹果,手指要碰到,那么肘关节和肩关节怎么转才显得比较自然呢?又或者已知一个方位(比如看像鼠标移动的位置),那么逆推头和腰的转向。这种,依靠终端节点(指关节)逆向推演其他节点如何配置的过程就叫做逆向运动学(IK,全称inverse kinematics)
下面的示例就展示机器人的注视点跟随鼠标移动
新建场景、animator controller,添加状态和motion为idle(一种悠闲自然站立,身体轻轻摇动的动作)并拖拽给机器人,新建脚本
1 | void OnAnimatorIK() |
在动画层勾选IK Pass,表示允许使用IK方式确定角色姿态
效果:机器人目光跟随鼠标,实际上是相机和鼠标形成的射线和机器人水平面的交点
SetLookAtWeight这个函数可以有多个参数,使得身体,头,眼睛都运动起来
anim.SetLookAtWeight(0.5f, 0.3f, 0.6f, 0.2f);
2.6 逆向运动学:末端节点动画
前面提到,除了注视动画,还有逆向计算手触碰物体时手肘等节点的动画,当然脚也一样。这就是另一种:末端节点动画。
脚本如下
1 | using System.Collections; |
新建一个target小球
为其制作小球掉落再弹起的动画
新建空GameObject作为辅助结点的位置(手臂,膝盖的位置)并放入机器人内部
运行
(脚朝内是小球的方向问题,将小球绕y轴转180即可)
点击isHand切换成手也类似
可以看出hint节点的位置就是中间节点(手肘,膝盖)触碰的位置
2.7 子状态
我们已知animator controller可以新建一个状态,unity支持新建子状态,一个状态包含多个子状态,也就是将这几个子状态封装成一个整体。比如有一个状态是 idel->walk->run->idel,也就是将这几个动作封装成一个状态。
2.7.1 创建子状态
新建场景,放置机器人,新建animation controller,新建状态,新建子状态
让idle拥有motion为humannoid idle,make transtion使得idle指向子状态,双击子状态
发现又是一个类似的controller界面
创建walk,run,jump动作并连线,最后指向base layer的idle动作
这样就完成了类似一个函数栈的效果,返回base layer就可以看到下面的效果
将这个controller拖拽给机器人,并使得原地运动,运行
就可以看到一连执行了一串动画,你可能说不用子状态也行,直接全部写外面,当然可以,又不是编程新手了,封不封装都可以的,如果觉得某几个动作都是固定连续发生,可以视为一个动作,就封装吧
2.7.2 带条件转换
当然也可以指定触发条件
添加trigger类型的参数
新建脚本
1 | void Update() |
将脚本挂接给机器人,运行,默认idle,只有按下空格才依次进入:walk->run->jumpup
同样的可以选择立即触发
2.8 blend tree
混合树,和transition类似,可以在多个动画片段之间进行叠加和转换,比如走路过渡到跑步。区别在于transition是一个状态到另一个状态,blend tree可以多个状态作用于角色,各动画可以有自己的权重,然后进行叠加。
2.8.1 创建blend tree
新建场景,添加机器人,新建Animator controller,创建BlendTree状态
双击打开,对BlendTree右键可以新建motion,这里建3个
添加motion如下:分别是猫腰走(蹲走),正常走和跑步,同时这里修改了参数名称
在右上角可以看到blend默认是一维的1D,将controller拖拽到机器人身上,运行,运行过程中可以不断调整Speed,就可以发现在三个动作之间平滑的转换动作了。
因此1D的混合树实际效果和transition差不多,只是可能更直观一些。
2.8.2 direct类型
下面我们将1D改成Direct
从每个动作右边栏的变化可以看出,Direct是可以为每个动作添加自己的权重的,我们可以建立3个参数分别赋值给三个动作
运行,我们可以在运行的过程中调整各自到合适的权重,运行起来有点奇怪,因为是叠加
我们可以对它归一化
这样我们就可以自由组合出:猫腰跑,慢跑(或者更像竞走)之类的动作
2.8.3 二维笛卡尔坐标
是以x y轴的二维布点方式,点和点中间的空白通过插值进行。
这里我们xy轴设置的好,可以当成俯视图来进行布点,因此轴的意义定义很重要
运行并调参,向左向右跑步或者走路
简单的做一个脚本进行交互
1 | using System.Collections; |
拖拽给机器人,运行,按下wad,和左shift,自己体验吧
3. 图形渲染
3.1 全局光照明效果
光照分为方向光(新建场景时默认那个太阳就是方向光),点光源,聚光灯和面积光
前三种都属于直接光源,也就是对物体表面产生了直接的影响,用于实时光照,计算量小
为了渲染出更加真实的效果,面积光可以进行预计算,这种计算量很大,通常用于静态渲染,因为物体移动了就会破坏预计算的效果。
当我们创建一种光照的时候,是没有阴影的,如果需要阴影可以自己打开
soft会模糊阴影的边缘。
对于预计算的光照,结果会被烘焙成light map保存,在window->rendering->lighting中可以看到auto general这个框框,如果打上,会对场景中勾选了static的物体进行自动烘焙,当然,为了开发时的性能,可以关闭,在发布之前进行一次烘焙。因此,对于不动的物体,我们最好设置为static,可以得到光照效果的优化。
如下图,可以看到设置成static后,绿的的物体的阴影有一点点的绿色,其他物体也一样,更加富有表现力,如果没有static的物体,是不参与这种晕染的
3.2 材质,着色器和纹理
光影计算中很重要的三个概念:material,shader,texture
新建一个材质,可以看见材质中包含了shader和uv纹理贴图等各种信息
着色器:像素着色器是负责渲染材质颜色的程序片段,顶点着色器就负责材质表面纹理,法线贴图(粗糙程度),反射率等东西。默认是标准
rendering mode:Opaque是不透明,cutout可以选透明度,fade是消隐可以实现淡入淡出,transparent是透明可以设置透明通道
albedo是基本的颜色,点击圆圈可以选纹理
cutout透明,可以调节透明度
fade,可以根据alpha通道(RGBA中的A)逐步消隐掉物体,跟多的用于魔法效果
transparent也一样,不过会保留光照信息,更多的用于玻璃效果
metallic可以控制金属效果
smoothness可以控制平滑程度,更加接近镜面
normal map保存了法线信息,用于法线贴图
height map 和法线贴图类似,需要和nomal map结合使用,normalmap只是效果改变了,而heightmap使得几何体实际表面受到影响
当然上述所有效果都是基于standard的shader,unity其实自带了许多比如卡通效果,手绘或者简笔画效果等着色器
3.3 摄像机设置
可以建立多个摄像机(比如很多游戏的小地图其实是个顶视角摄像机),创建方法和创建普通GameObject一样。
默认主相机main camera会被覆盖,是根据depth覆盖的,主相机的depth是最小的-1,后面建立的相机依次是0,1,2…
如果要多个相机并存,只需要缩小其他摄像机的宽高。因为原理是这样的:其实相机覆盖,就是有一个宽度和默认相机一样的视图直接覆盖了底部的…(我觉得unity是不是有点随便??)
3.4 剔除
也就是不渲染相机看不到的地方
点击右下角的bake,unity会对static物体进行剔除(消隐),注意一定要是static的
除了视野外,还有遮挡,试试创建一个巨大的物体挡住相机并重新bake一下,发现也挡住了
当把物体去除后需要重新bake,否则会维持原效果
3.5 后处理效果PostProcess
又叫做全屏幕处理效果,在真正渲染到屏幕之前对后备缓存里的图像处理。可以添加一些比如色调啊之类的。
1.为摄像机添加后处理层
Component->Rendering->Post-Process Layer
如果没有这个组件,先在package manager下载
Layer选择PostProcessing,没有就自己新建自定义一个
2.在场景中添加后处理体
是一个大大的绿色cube,不过多了box collider组件
而Profile就是具体的后处理方法,直接新建,双击定位,打开
双击后如下图添加一个,
3.6 探针
预处理只能用于静态物体,对于非静态物体,如果需要效果优化,可以使用探针。
探针是在场景中提前布点,如果运动物体移动到了探针区域内,就进行渲染效果优化。
3.6.1 光照探针
建立如下场景
为框框中的两个物体添加左右移动动画,接下来添加探针组
这些黄色的点就是探针,场景复杂的时候可以添加的密一点(探针组属性页面可以看到add probe选项,添加探针)
想改变探针布点位置或者添加探针都要先点上图那个允许修改,下面运行,可以看到物体移动时会检测受影响的探针
移动的物体左边有轻微的晕染绿色,右边有轻微的晕染红色效果
3.6.2 反射探针
需要注意的是开启全局光照烘焙,最好一直都开(虽然很吃电脑性能),否则看不到反射效果,将圆变为透明度反射球体
运行,透明球效果比较生硬,不会改变
点击添加多个反射探针
运行,发现移动时反射球会改变了
不管是反射探针还是光照探针,其实就是预处理渲染信息存储在探针中,等到运行的时候将附近各探针的存储的信息进行一个叠加融合作为当前运动到某个位置时的渲染信息,从而提高场景的真实感。
3.7 视频播放
选中墙壁平面,component->video->video player,视频播放的原理是改变了墙壁的材质..
也可以选择URL
当然,以上不涉及到视频的控制,但有时候我们需要对视频进行控制。比如空格暂停
新建c#脚本
1 | using System.Collections; |
3.8 粒子系统
用于廉价而实用的特效,比如爆炸,烟雾,沙尘。会比直接贴多帧图效果好很多
直接就能看到效果,雪花往上飘
我们制作别的效果只需要编辑参数,比如制作火焰
右边很多选项卡的
基本选项卡:start speed 往上升的速度慢点,start color颜色调为火焰色
shape选项卡:angle是往上升的时候的散发度,调为0,radius是出生点那个圈的范围大小,适当调小,使得火焰更集中
随时间的函数选项卡:
可以为其添加子GameObject橙色点光源就可以照亮,也可以将火焰的纹理拖拽给它,是图片的形式,背景要透明,类似布告板,虽然是图片,但是面向摄像机,效果就好像立体的。
当然这样做出来的效果都比较简单,标准资源包有一些预实现,可以拉出来看看,做的很完备,像什么爆炸呀,火焰呀,沙尘暴,烟雾之类的
4. UI
UI不是指3D的物体,而是游戏开始界面的按钮呀,图片呀,文字之类的,当然游戏里面也有。
新建GameObject->UI->Text/Image/Button
为Text新建脚本,内容是一个函数,当函数调用时改变文本
选中button,属性框onclick点击+号,选择Text控件,并选择刚刚写的函数
canvas有个属性叫render mode 可以选择是delay永远显示在屏幕空间,还是camera某个相机的屏幕空间,还是世界空间(虽然是二维的,实际上变成了3D的物体)
5. 物理系统
5.1 物理仿真基础
collider:碰撞检测
regid:刚体(比如重力,或者其他力)
新建如下场景,为墙壁添加材质并画好颜色
给小球添加rigid body
运行,小球会自由落体
在collider里可以看到材质,这个材质不同于普通材质,叫做物理材质,类比生活来说,这个应该叫做“材料”,而普通材质应该对应“涂料”
添加物理材质
拖拽给球体可以自动复制material,双击可以设置摩擦力和弹跳,弹跳为0~1
运行,可以看见皮球效果
为小球添加一个恒力(固定方向的力)
这四个力参数依次是世界空间,局部空间,世界空间力矩,局部空间力矩
可以把小球靠近一点墙,在掉落过程中撞到地面,运行
发现坐标轴会来回转动,这是因为小球落地后会有摩擦和滚动效果(是的添加材质后就自动带有这些功能),导致局部的坐标一直随着滚动变化
可以在rigid body属性中进行约束,阻止滚动
5.2 仿真子弹效果
制作根据蓄力发射子弹的效果,蓄力时间越长射的越远
5.2.1 预制件
将小球直接拖拽到unity资源管理器窗口即可让某个GameObject及其附带的所有属性称为一个可复用的预制件
新建脚本,脚本完成的功能是:利用子弹的恒力组件来施加蓄力效果
利用射线查询功能计算生成的子弹方向
1 | public class fire : MonoBehaviour |
如果子弹偏了是因为预制件复制的子弹默认恒力不是z轴的10
5.3 关节结构
joint可以约束一些物理运动
使用空物体作为约束体
建立两个空物体并分别赋予hinge和spring的joint属性
hinge joint
hinge joint就是类似肘关节这样的关节,绕轴转,或者理解为能挂在空气中的挂钩/钉子
hinge自带rigid body,其中的connected body是要约束的物体
新建一个cube并赋予rigid body属性,放在hinge joint附近位置,拖拽进入connected body
spring joint
空间中的弹簧,能够悬挂物体,操作都类似
悬挂物体后,可以对物体进行子弹打击,尤其是弹簧悬挂的物体,效果很有趣
破坏joint
joint有个break force属性,就是说收到多少力的时候断开约束,我们可以给hinge一个较大的断开力比如1000,spring joint设置为30
运行,就能发现spring joint悬挂的方块被子弹一打击就掉落,而hinge悬挂的方块需要一定的蓄力才能打掉
脚本,破坏事件可以通过脚本捕获到,我们写一个脚本捕获打击的力度
这里直接在unity控制台打印
1 | private void OnJointBreak(float breakForce) |
运行
当然可以试试使用UI空间在屏幕空间右上角记录分数等简单游戏
5.4 碰撞事件
比如我们玩游戏时遇到的反弹或者突然加速效果,本质都是检测到碰撞之后施加特殊的物理效果。新建一个cube并赋予透明材质
cube有个属性叫isTrigger
也就是触发碰撞,如果勾选,那么碰撞时物体不会碰撞而是会穿透物体,适合做一些水面效果。
下面我们模拟一个加速带效果:当穿透这个半透明物体时给子弹进行一个赋力,使子弹加速
首先给子弹添加tag,方便我们控制所有子弹
新建脚本
1 | private void OnCollisionEnter(Collision collision) |
运行,碰到半透明方块时就会弹起
6. 人工智能
6.1 自动寻路
先建立如下场景,胶囊体模拟小人
同时为小人添加NavMeshAgent属性
接下来我们编写一个脚本让小人移动到指定位置,需要用到一个AI库中的NavMeshAgent属性
1 | using System.Collections; |
将脚本挂接到胶囊身上,可以看到有个public的参数,也就是目标,我们可以创建一个空GameObject作为目标
现在运行还不会自动寻路,还需要一些预计算:
也就是说,所谓的自动寻路算法需要对现有场景进行烘焙(预计算),那么我们就需要将一些不动的物体设置为static属性(地面,障碍物等),如果障碍物没有被静态烘焙,那么会直接穿过去(也就是没有绕过障碍物进行寻路)
运行,就能自动寻路了,除了避障之外,还会自动寻找最短的路径
修改脚本,实现点击移动到鼠标指定位置
1 | void Update() |
运行点击鼠标左键可以看到效果
台阶跳跃
当我们点击高台的时候发现不能跳上去,只能寻路到离高台最近的位置
方式1:设置set height
属性框有个设置跳跃高度,每次设置完都要bake
当我们看到bake后线没了就说明能跳上去了
方式2:使用off mesh link连接
如果觉得方式1的跳跃不够真实,或者想实现部分游戏里的传送效果(参考马里奥大炮,lol娱乐模式大炮,远程跳跃或者不能直接连接),off mesh link就是这样建立两个物体之间的曲线
下面我们实现直接从地面跳跃到蓝色物体
场景中建立跳跃的起点和终点(cube,终点最好是看得见的..方便演示)
并给其中一个跳跃点添加off mesh link组件
拖入起点和终点
移动的障碍物
对于移动的障碍物,没有办法设置static,需要通过nav mesh obstacle
在场景中新建一面墙,我们都知道如果不配置static的话运动路线穿过墙的话就会直接穿过去。因此我们为墙添加nav mesh obstacle组件
运行,就能避开这个非static的物体了(虽然这个墙并没有为其添加animation,可以自己添加)
说一下carve属性,carve属性能让动态物体也加入烘焙当中
勾选以后我们可以看到bake时这面墙周围也有禁止触碰的区域了
我们都知道bake是提前计算一些复杂的渲染效果,运行时就可以不再计算,那非static的物体勾选这个carve有什么用呢?那就是针对动态生成但生成以后静止的物体
综上:
static:静止物体
非static:移动物体
非static但carve:动态生成但生成以后静止的物体
6.2 敌人巡逻
通常游戏里的敌人都有巡逻逻辑,固定的移动某些空间点,遇到玩家后就会追赶,玩家离开范围后回到自己的巡逻路径
在场景中建立敌人,并建立几个巡逻路径点,左上角的为敌人,右下角的为玩家,用一个父GameObject来管理路径点,路径点位置大致如下
下面通过脚本使其能按规定移动到各个巡逻点
1 | using System; |
关于第34行的那个stopping distance是误差范围判断,其实是nav mesh agent提供的一个误差判断,在脚本挂接到敌人后,还需要调整stopping distance的属性值,最好大于0,以为地形可能有高有矮之类的,存在误差,寻路算法可能没有办法完全达到
6.3 敌人视野
下面实现敌人发现玩家的功能
视野功能的本质是collider
创建一个空物体作为敌人的子物体(视野),添加box collider并调整到合适的视野大小,勾选isTrigger仅作为触发器使用,可以避免物理碰撞
编写脚本,注意脚本是给这个EnemySight的
1 | using System.Collections; |
需要注意,collider碰撞检测,当然要求player持有rigid body属性,因此要注意给玩家和敌人添加rigid body属性,除此之外还要勾选isKinematic,表示不受动力学影响(比如被墙壁撞开后受了推力,直接被撞开到地图外)
6.4 攻击与追踪
我们将要修改敌人的脚本逻辑,使其除了巡逻功能外还能拥有追踪和射击动作
整个过程如下:1敌人巡逻,2视野发现玩家,3掉头朝向玩家,4开枪射击,5玩家脱离视野,6敌人追踪,返回2直至追不上,返回1
1 | using System; |
射击函数
1 | private void Shoting() |
制作子弹和预制件
子弹不要太大,调整缩放
子弹不受物理系统的碰撞影响,勾选isTrigger
为了方便子弹不受重力影响,取消勾选gravity
给子弹一个往前的恒力,局部坐标z为5
为子弹编写脚本
1 | private void OnTriggerEnter(Collider other) |
挂接给子弹,然后创建预制件并删除默认子弹
将子弹预制件作为参数拖拽给Enemy脚本中的bullet参数
现在运行可以看到敌人发射子弹打死玩家了,但是敌人还不会追踪,且射击函数让寻路功能暂停,所以视野捕捉到玩家后就会停下
追踪函数
1 | private void Chasing() |
运行,可以看到敌人可以巡逻,追踪,射击,重新巡逻
7. 音频
7.1 音频基础
可以直接将音频文件拖拽到场景,会自动建立一个GameObject,附带Audio Source组件,其中的AudioClip属性就是这段音频,而output可以指定一个audio listener,audio listener是音频接收器来模拟人的耳朵,添加音频时默认为main camera添加一个audio listener组件
3D音效:spatial blend拉成3D就可以,运行时镜头的位置决定了声音方向在哪里
mute:暂时静音
Bypass Effects:忽略音效(音效和音量是不一样的)
Play On Awake:自动播放
Loop:循环播放
Priority:优先级0~256
Volume:音量
Pitch:声音播放频率(倍速)
Stereo Pan:声音方向,只对2D声音有影响,3D情况下只跟位置有关
Reverb Zone Mix:决定多少输出信号给回声区域
Doppler Level:多普勒效应
https://baike.baidu.com/item/%E5%A4%9A%E6%99%AE%E5%8B%92%E6%95%88%E5%BA%94/115710?fr=aladdin
Spread:3D音源的发射角度
Volume Rolloff:距离和音量的函数
Max Distance:声音传播最大距离
7.2 混音器
对多个声音进行控制
可以通过不同的组来控制不同的音源(属性中的output对应这里不同的组)
运行,就可以发现多个组对应的音源同时播放了,可以编辑各个组的音频文件的权重,达到混音效果。
父group的调整会影响他的所有子物体
SMB按钮的意思是
S:solo单独播放
M:mute静音
B:bypass取消音效
当我们调整到觉得适合的混音效果时,可以点击Snapshots,保存当前的参数快照
Views:也是可以保存多份,但是保存的是group可见性的自由组合,比如第一份屏蔽了effect,第二份屏蔽了background
Mixer也可以有层次关系:一个混音器控制另一个混音器,比如我们想玩家点空格的时候一种音效,点回车的时候另一种音效
首先拖拽两个音源shoot和explode模拟开枪和跳跃,并管理在父controller下(译为玩家控制音频),创建一个audio mixer对应这个控制音频(这里名字是SoundEffect),两个音频都将output设置为SoundEffect
接下来添加一个组PlayerSound,再将刚才的SoundEffect混音器作为Music混音器的子混音器,并选择组为PlayerSound
当然,交互需要脚本
1 | public class Controller : MonoBehaviour { |
挂接给controller并将对应的音源GameObject作为参数传递进去,运行,便可以发现既有背景音乐,也有子物体(用户操作时的音源)了
7.3 音效
音效和音频不一样,音频是一段mp3,音效是比如回音,空旷,人声之类的
新建场景,拖入背景音乐音频,并创建一个混音器(这里叫AudioEffectDemo),混音器添加两个group分别作为音乐和音效
将music作为音频的output,运行测试music的参数是否能调整声音大小
下面对effect组点击Add添加音效,unity为我们提供了许多内置音效,这里选择SFX Reverb(回声)
回声需要知道来自什么的回声,所以我们给effects添加一个Receive,给music添加一个send
有了接口后,点击music,在send里选择effects的Receive接口,就可以把音频发送给接收者
注意,音效是有处理顺序的,必须先接收,再回声,因此要调整顺序如下图
运行,还不行,需要点击这里调整回声强度
如果回声效果不明显,可以调整SFX Reverb的参数,比如Room房间大小和Reverb回声强度
除了回声效果,unity也提供了许多其他音效,比如低通滤波器(可以过滤低音保留高音)等等,自己可以尝试,但是要注意顺序,music要先低通再send回音,否则回音接受到的是原音
脚本控制
以上,都是在unity编辑器中设置的,那如果需要动态设置呢?,比如在游戏场景中人物可以调整某个收音机的音量之类,因此可以暴露混音器设置给脚本
因为master可以控制所有子音频,所以我们暴露master的控制脚本,注意是对着Volume点右键而不是对着Attention点右键
完成之后这里会出现一个Exposed Parameters,也就是自定义参数
新建脚本,这里通过键盘上下键来控制音量
1 | using System.Collections; |
脚本挂接到main canera,mix audio作为参数
运行,通过键盘↑↓调整音量大小
至此音效介绍完毕
8. 网络
2019以后的unity版本现在这里点击一下安装
8.1 双玩家连线
双玩家连线:这种模式下,服务端其实也是其中一个客户端(也是一个unity程序),也就是说,客户端操作一个小人,服务端也有操作小人的功能。但是还是必须先启动服务端。
多玩家连线:这种模式下,服务端通常只做数据的接收、转发和广播,本身并不具有客户端的功能,但是会存储客户端的列表,操作对应的数据是单播还是广播
- 添加网络管理器
新建场景,添加一个第三人称预实现小人,添加一个空object,为空物体添加network manager和network manager HUD组件
- 为人物添加网络实体和网络广播
为小人复制出预制件
对小人添加 networkIdentity组件,表示该小人是网络实体
勾选下面这个选项,由于在网络游戏中,不宜传输太多数据,比如人物可以在自己本地可以控制的,一个客户端只有一个玩家。只需要向网络广播自己的一些信息即可,因此就可以勾选下面这个选项。
那勾选了本地操作之后,怎么向网络广播自己的位置信息呢?
为小人添加network transform组件
就可以向网络中发送自己的transform了
完成之后,点击apply all可以应用到对应的预制件
现在运行并不是真正的网络数据,还需要修改人物脚本,进入这个脚本
打开可以看到MonoBehaviour,这是单人游戏的意思
需要改成如下:网络游戏
这个类就可以调用一些联网功能
我们对其修改,目的是:人物动作只在本地,但是一些位置等信息需要向网络广播
保存,apply all后就可以删除场景中的小人了,咱们只保留预制件,小人交由服务端生成
\3. 服务端生成预制件
刚刚说了小人应该在服务端生成而不是一开始就在客户端中
\4. 运行
现在一切已经就绪,那么我们怎么模拟联机呢,当然可以找两台同一wifi/局域网下的电脑,也可以本机模拟,我们直接build一下作为一个,然后编辑器中运行作为另一个(当然你也可以build后运行两个)。总之无论如何,打开两个就行了
需要注意的是,必须先启动服务端,再启动客户端连接它
我们可以看到成功联机了,但是只有位置联机了,动作没有改变(A端自己的小人会在B端移动,但不会在B端做动作)
unity为网络游戏优化的规则就是默认不传输所有数据,至于位置信息是因为我们添加了network transform组件才进行传输
怎么样才能让动作也作为网络数据传输呢?动作其实就是动画animation,到这里就明白了:network animation,是的
踩坑:预制件的修改
但是要修改预制件的东西,最好先将预制件拖入场景中变为实体,改动完再apply all,再删除场景中的GameObject
因为参数通常是传入GameObject,如下
如果只有Player预制件没有生成一个Player的GameObject实体,那么这里会发现没有Player可选
将上述所有修改完成再apply all,删除场景中的实体即可
重新build,联机,发现动作也有了,延迟取决于网络!!!!
8.2 联网子弹
准备工作:单机子弹和发射
下面制作子弹,先创建一个球体,属性如下
并作为预制件,将场景中的子弹删除
编辑player脚本,使其可以发射子弹
1 | void Fire() |
这时候已经完成了单机发射子弹了,可以运行测试
下面继续制作联网子弹
将子弹预制件拖拽到场景编辑,添加network identity和network transform
由于我们不希望子弹的位置自动传输,因此将send rate设置为0
apply
将子弹预制件纳入到网络管理器的生成列表种,使得子弹由网络生成
需要注意的是,由网络生成的列表必须带有[Command],而有Command的函数必须是以Cmd开头
新建血量脚本
我们想做一个功能是当血量为0时,重新生成一个玩家,我们知道现在Player是在服务端生成的,但是生成后是交给客户端来对这个Player执行
unity提供了一个由服务端调用,客户端执行的函数
为子弹建立脚本,碰撞检测和调用血条减少(总的来说就是造成伤害)
1 | private void OnTriggerEnter(Collider other) |
这里说明一下,为什么没有用if(other.gameobject == “player”),因为这个碰撞检测可能对player有效,对enemey也有效,后期可能对其他的物体也有效,那么这样设计是比较麻烦的,unity提供的这种消息机制就可以让所有的物体都接收,然后尝试调用TakeDamage方法,如果物品没有这个方法就不作为,如果物品有这个方法就调用。
将脚本拖拽给bullet预制件,将health脚本拖拽给player,运行
8.3 NPC
NPC就是共同的敌人,(或者在某些游戏中不是敌人,总之共同的就对了)
- 重新制作预制件
拖拽一个小人到场景中,重命名为NPC,并修改材质使其看起来有区别于enemy和player
重新制作预制件并命名为NPC预制件,并将场景中的小人删除
- 为NPC添加网络管理器
新建空物体,添加network identity组件,勾选server only(NPC应该设置为仅由服务端生成,不受客户端控制)
- 动态随机生成多个NPC脚本
1 | public class NPCSpawner : NetworkBehaviour |
拖拽给生成器gameobject
填入参数
- 注册到网络生成器列表
- 为NPC添加血条效果
现在运行可以正常生成NPC但不会收到伤害,因为Health是客户端的脚本功能
修改如下
运行,可以联网打怪
9. 时间轴
9.2 创建time line
先来看看如何创建一个时间轴创建一个timeline
9.1 和动画的异同
timeline是引擎后来提供的一个新功能。相比于animation,timeline可以认为是控制场景的整体动画,既可以对单个物体也可以控制多个物体协同运动,同时可以播放声音,粒子效果,摄像机跟踪等等。而animation只可以控制单个物体,且该物体的多个动画片段需要animation controller协调进行叠加或者混合,多个物体的动画需要各自的animation。可以理解成timeline是横向的,一个object控制多个物体同时播放动画,animation是纵向的,每个物体能在自己的时刻播放自己的动画。
动画轨道
新建一个空物体,添加Playable Director组件,另一种方式是window->timeline->create(这种方式就类似于animation)
如下图操作,拖拽两个物体进入animation轨道,由Director这个timeline物体的timeline组件控制
在各自的轨道上创建自己的animation,可以像animation一样录制,也可以如下直接植入预制动画片段
需要注意的是:录制的话,对动画中需要变动的transform属性进行右键add key
录制完后,可以对某一段动画进行封装成一个完整的片段
除了动画轨道,还可以有音频轨道和控制轨道
音频轨道audio track
和动画类似,创建音源,创建轨道,添加声音片段,可以融合
控制轨道 control track
可以动态生成一个物体
自定义轨道Playeable track
可以接收自定义脚本,脚本类需要集成PlayableAsset
下面实现一个UI空间Text显示当前轨道的播放状态
1 | using System.Collections; |
简单来说,timeline绑定了Text类型的控件,能够允许这个控件在某段时间生效,至于代码可能是固定写法,日后再探究
创建UI空间Text并拖入,运行
9.3 角色动画
拖拽一个第三人称小人预实现,取消脚本和animator controller,运行,此时小人相当于静态模型,添加timeline,动画依次为idle->walk->run,运行,此时发现有两个问题
1.角色不能掉头,即使调整场景中的小人rotY为180,但是运行的时候还是会背对相机,这是因为角色的transform已经交给目前出于激活状态的timeline控制,需要在这个地方调整
2.动画被重置,尤其是walk->run,walk已经走了一段距离,但是run的时候会自动先回到原点,而不是接着walk的位置跑。右击run片段选中如下选项
3.动画过渡太僵硬,回顾之前animation的融合,timeline也提供了非常方便的过渡功能。只需要将两个动画片段交叉即可自动过渡。
细节:由于动画片段的独立性,由于速度不同或者其他问题,导致自动融合中会出现比如walk->run时出现滑步的现象,自己对位置做一些微调即可
可以像animation一样进行动画的叠加,比如在动画1的时候添加转身,遮罩(身体的某个部位做不一样的动画)
如果动作部位冲突又没有遮罩的话,后面的轨道会覆盖前面的轨道
9.4 脚本控制
通常timeline都是达到某个场景触发,下面我们模拟脚本控制timeline的触发,新建空物体并为其创建脚本如下
关于第8行,其实写public也行,只是提一下有这么一种写法,既是private又可以在unity编辑器中暴露参数,像button一样传入参数
运行,依次按下QE
9.5 Cinemachine
转场动画中经常见到的多摄像机动画。比如在游戏开始前,相机先展示敌人和一些特殊道具的位置,随后镜头才给到主角。其实就是Cinemachine在多个摄像机之间的转换。
timeline对多摄像机有很专业的动画调控支持
第一次使用需要先安装一下
- 新建cinemachine track
添加后发现多了一个track,新建它
cinemachine其实是采用了虚拟摄像机来模拟多摄像机效果
- 为主摄像机添加cinemachint brain组件
\3. 新建虚拟摄像机
如下,可以创建一个简单的虚拟摄像机,unity也为我们提供了许多具备一定功能的摄像机,这里我们选择一个freelook:这个虚拟摄像机允许我们角度自由的观察一些物体。
再建立一个带轨道的推拉摄像机:这个虚拟摄像机可以对一个物体前后推拉达到远近变化观察的效果
- 调整虚拟摄像机达到预期效果
咱们目的是让推拉带轨道摄像机沿着设定的轨道移动,并时刻注释红色物体,自由摄像机注释蓝色物体。并在timeline中设置为先观察蓝物体,再观察红物体的效果
推拉摄像机的路径设置如下
推拉摄像机参数设置如下:其中Path offset设置成0表示摄像机严格按轨道移动
设置完成后,推拉摄像机就可以在轨道上找到最近的点去观察红色物体了
另外,这里为了方便看到效果,要让红蓝物体在动画开始时自动运动(之前设置了要按下QE)
- 调高main camera的优先级
自己可以对比观察一下,是这样的,camerachine所谓的虚拟摄像机并不记录画面,而是将虚拟摄像机看到的画面传输给camera(类似于无人机,无人机在飞,拍到的画面是发送到操控者的屏幕上,无人机自己并没有保存照片的功能)。
如果不调高main camera的优先级,那么会显示虚拟摄像机开始的位置观察的画面,因为虚拟摄像机只有第一帧的画面,后面的画面不记录不更新,而是发送给了main camera,因此我们看到的就是虚拟摄像机似乎没动。所以我们应该一直看main camera,要将main camera的优先级调高,覆盖其他摄像机。
- timeline控制两个camerachine
运行观看效果
10. 2D游戏
unity新建工程的时候,如果想要创建二维工程,需要在创建时点击2D
长这样
可以看见工具栏有个2D视图,其实只要换成3D就变成3D工程了,所谓2D游戏不过是把z轴去掉
10.1 精灵
2D游戏中的纹理默认是精灵类型,2D游戏就是由一个个的图片(或者说纹理或者说精灵)+移动+脚本制作的
将一张图片拖拽到unity,可以看到默认是精灵类型
如果图片本身就具有alpha透明通道,那么图片可能会不显示(透明嘛),可以如上图去除透明通道,记得点击apply
拖拽两个物体进入场景,发现被覆盖,可以手动调整sorting layer,下面的order in layer表示即使是同一层也有遮挡顺序
点击三角形,点击add layer,并添加自定义层,下面的优先级高于上面
编辑好后,将场景中的物体应用到各自的层,坦克就不会被背景遮住了
当我们拖拽子弹后,发现是一连串的
但我们知道这其实是多帧连续播放的动图,unity给我们提供了一个非常简便的切割方式
其中slice提供了很多切割模式
automatic是自动切割,采用了最小包围盒算法
size是指定长和宽的像素
count是指定几行几列
完成后记得点击apply
此时再拖拽到场景,会自动提示创建animation
创建完动画后运行即可看到效果
多个物体批量调整大小
创建发射子弹脚本
1 | using System.Collections; |
挂接到子弹,子弹会自动添加rigid body组件,还需要手动添加一个box collider组件用于碰撞检测,同时还需要把重力取消
接下来完成坦克生成子弹,因此需要制作预制件,并reset位置(到时候作为坦克的子物体)
新建发射脚本
1 | using System.Collections; |
挂接给Spawner发射器,并将子弹预制件参数拖拽进去,运行
颜色:
可以调整color属性
光照:
2D游戏默认没有光照,因为材质默认不会受到光照效果,可以手动添加光源,并添加材质,选择漫反射
将材质拖拽给2D物体,就可以看到光照效果,如果没有,注意光源是否离得太远(调整z值)和角度(这个要注意,比如点光源默认rotX是90,建议切换成3D视图来调整)
10.2 瓦片地图
tilemap,是为了2D游戏经常需要的一种格子排列布局
但是这样很费时间,于是有了瓦片地图功能
- 新建瓦片地图tilemap
除了tilemap,下面还有六边形等
记得将这个tilemap图层覆盖在background之上
- 调出瓦片调色板tile palette
- 拖拽到调色板
用笔刷刷出连续的效果
点击edit可以调整调色板上的瓦片效果,可以用多个物体组装成一个瓦片,鼠标框选后作为一个整体再使用笔刷
10.2.1 碰撞体
可以为tilemap中的每一片瓦片里的每一个方块都添加碰撞体
这时候我们可能需要进行一些碰撞体的融合
添加这个会自动添加rigid body,如果是地形的话,不受重力影响,最好直接设置成静态
tilemap允许使用碰撞组合
10.3 角色控制
下面我们学习2D下的游戏角色控制
- 先添加资源包,只需要添加这几张图片就行
如果打开sprite editer就可以发现都切割好了
- 拖拽idle动画(一定要记得是拖拽到场景而不能是Hierarchy),起名Player作为角色,修改为actor层,设置tag为Player,添加capsule collider 2D并调整大小到包裹小人范围,添加rigid body2D组件,不希望旋转所以锁定旋转轴
- 动画修改
不多说了,编辑ide的状态机,添加walk run jump 并创建三个参数,添加转换条件
speed:速度,Ground:是否触碰地面,vSpeed:纵向跳跃速度
idel->walk:速度大于0.01 反之亦然
walk->run:速度大于0.1,反之亦然
最后状态机如下图
添加对应的Motion(如果没有的话就先将几个png拉入场景,因为这个不是从资源包弄出来的动画,而是从png里面通过sprite editor切割出来的)
- 创建player脚本
这个脚本比较复杂,但目的只有一个,就是通过按键改变animator动画中这几个参数,剩下的不用理会,动画都做好了。
1 | using System.Collections; |
脚本挂接给小人
这里为了方便我们先选Everything,如果游戏做的细的话可以是地面Terrain和一些被认为地面的一些层
添加脚本中提到的用于碰撞检测的子物体
运行发现有延迟,应该把转换调成立即转换
相机跟随
这个比较简单的一种做法是让main camera作为Player的子物体
当然这里由于子弹碰撞后销毁Player及其子物体导致撞到子弹后报如下错误,这里先不管他(解决方法是要么碰撞时判断不销毁相机,要么摄像机的移动不采用这种方式,而是脚本移动相机的transform的position)
单方向碰撞检测
通常这种2D游戏都是可以从下面穿上去的,unity为我们提供了这个功能
选中两个tilemap并添加如下组件
勾选如下两个选项
运行
11. 天空盒
12. 忽略碰撞检测
部分物体是不需要碰撞检测的,unity提供了非常方便的选项供我们忽略而不需要在脚本中判断。
首先我们添加一个tag专门标识忽略碰撞检测的物体比如IgnoreCollider
如下图
矩阵少一半,说明不支持单向碰撞检测(A可以碰撞B,B不可以碰撞A),因此如果有其他复杂的需要仍然通过脚本实现,只推荐勾选图片中的一个,除非确定某物体完全不受碰撞