IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity3D学习笔记 -> 正文阅读

[游戏开发]Unity3D学习笔记

Unity3D学习笔记

3D的场景与操作

3D场景与2D场景:

在这里插入图片描述

3D场景中多了以下元素:

视图导航器Gizmo,网格,天空盒,光照,Z轴。均可以通过场景的顶部栏进行调整

快捷键

Alt+左键:更改视位和角度;

右键:更改角度;

鼠标中键:更改视位;

方向键:视位前后左右移动;

滚轮:视位前后移动;

Alt+右键:视位前后移动;

Q键:切换成移动视位模式;

方向

X轴正向:东方;

Y轴正向:上方;

Z轴正向:北方;

快捷定位游戏对象

层级栏中选中游戏对象,然后鼠标移到场景栏按下F,即可将视角定位到指定游戏对象。

摄像机:

摄像机在层级栏中作为一个游戏对象存在,负责拍摄游戏最终画面,我们在左下角Game栏看到的画面就是主摄像机拍摄的画面。

做个比喻的话,场景栏是导演看到的,游戏栏是摄影师看到的。

在这里插入图片描述

选中摄像机,GameObject->AlignWithView,强制摄像机位置和视角移动到当前场景栏位置与视角。

摄像机属性中的Projection选项能控制摄像机显示方式,是透视模式还是正交模式,正交模式是2D情况下的默认镜头。

全局光照GlobalIllumination

观察场景中的游戏物体,创建的时候是纯白的,但是经由光照的影响,它会变成其他颜色。

Window->Rendering->LightingSetting可以生成当前场景的光照表单,点击GenerateLighting生成该场景的光照文件,在Scenes文件夹下作为SimpleScene的同名文件夹被存储。回到LightingSetting,InviromentLighting中拖动IntensityMultiplie(光照强度因数)来改变光照强度。InviromentReflection中拖动IntensityMultiplie(光照反射强度因数)来改变光照反射的强度

在这里插入图片描述

3D物体的操作

原生3D物体

指Unity系统自带的一些简单3D建模(PrimitiveObject),白模,可用于简单的3D测试,它们的大小都与Unit单位长度有关。

四个基本物体:用途:度量和占位(模型未出而逻辑先行)

在这里插入图片描述

? 和两个水平和竖直的平面(由于另一面不需要被玩家看到,故不需要渲染)。

在这里插入图片描述

物体的操作

Q:移动视位 W:移动物体 E:旋转物体 R:缩放物体

多选物体情况下的操作

当一次多选了几个3D物体,移动,旋转和缩放操作便需要区分轴心(Pivot)和中心(Center)了,Pivot情况下,每个被选中的物体根据自己的轴心位置进行移动,旋转,缩放;Center情况下,所有被选中的物体根据它们共同的几何中心进行整体旋转。

增量移动操作

最简单的移动方式是计算两物体间距并手动赋值。增量移动操作可以确保手动拖拽的误差,按住Ctrl并拖动即可。

Edit->GridAndSnapSettings->IncrementSnap设定步长,可以让物体每次移动固定步长,保证两物体能严丝合缝而不缺漏或多余。

顶点对齐

顶点(Vertex)属于建模术语,不是指代一个四边形就有四个顶点,将场景栏的渲染模式切换成ShadedWireframe,可以查看模型的所有顶点

在这里插入图片描述

我们选中一个立方体,按住V可以按照顶点拖动它的整体,Unity不是建模软件,不要想着拖动顶点来改变物体形状。

点面重叠,这样也能保证两个物体严丝合缝。

在这里插入图片描述

网格与材质

网格

网格(mesh)用于定义一个物体的形状,网格是怎么定义一个物体的形状的:

1,所有物体都有自己的表面;

2,将表面分割为若干个三面体,成为一个面;

3,记录每个面的顶点(Vertex)和法线(Normal)等数据;

4,运行游戏时,由GPU渲染每个面的数据,显示为物体;

在这里插入图片描述

材质

材质(Material)用于规定一个物体的表面如何渲染,包括颜色,纹理,受光影响,突起/凹陷特征,一个好的材质可以让物体看起来不违和。在Assets文件夹下新建Materials文件夹,用于存放材质文件,新建材质文件(Material),这里面的参数用于调整这个材质的表现形式,例如Albedo(反照率)可以调整物体表面的颜色。给游戏对象的MeshRenderer组件赋值为新建的材质文件。

在这里插入图片描述

在这里插入图片描述

贴图

几种重要的贴图:

1,反照率贴图(Albedo):用于表现物体的表面颜色。

2,金属度贴图(Metallic):用于表现物体的哪些部位更有金属光泽。

3,法线贴图(NormalMap):用于表现物体表面的凹凸细节。

4,镜面贴图(Specular):用于表现镜面反射。

通过给Albedo赋予贴图(Texture)的方式定义物体表面显示的图片,这个图片应该是美术人员设计好的:

在这里插入图片描述

在这里插入图片描述

组件栏中的MeshFilter用于从模型文件中读取Mesh数据,绘制出物体的网格数据,得到形状。

而MeshRenderer用于将物体的形状显示出来,贴上表面材质,所以需要为它指定一个材质文件。

没有MeshRenderer的物体就没有实体,两组件缺一不可。

而且美术人员配套给出Model+Material+Texture

在这里插入图片描述

我们可以发现任何一个材质的预览都是以球的形式出现的,这是因为物体的光照运算这一属性也被包含到了材质文件当中,使用球来做材质预览可以清楚的看到光对该物体每个面的影响,各种光照条件下的颜色表示(材质球)。

在这里插入图片描述

法线贴图用于定义一个物体的凹凸度:

贴图可以由美术人员绘制,而法线贴图是建模软件通过美术人员的雕刻自动生成的。

在这里插入图片描述

无法线贴图的铁块:

在这里插入图片描述

有法线贴图的铁块:

在这里插入图片描述

还可以通过调整它的金属度和光滑度来进行设置。

着色器

着色器是一种程序,一种算法,它渲染了画面上每一个物体的最终显示效果,通过材质给出的各种贴图,各种参数,加上环境光照的影响最终计算出一个物体的显示效果。如图,蓝色框选部分比较亮,而红色框选部分比较暗,这是因为着色器综合光照的强度,角度,颜色,以及物体的反照率贴图,法线贴图,金属度,光滑度从而计算出了物体表面的不同部位的不同颜色。当物体表面与光线方向垂直时,采光率最高;当物体表面与光线方向平行时,采光率最低。编写着色器语言可以达到不一样的美术效果。

在这里插入图片描述

除此以外,着色器还用于表示材质与顶点的对应关系,当我们将一个材质赋予一个物体的时候,着色器会根据模型的顶点参数,生成材质的显示办法。如图,正方体的面数明显小于球体的面数,但却需要6张材质来着色,而球体只用了1张。并且着色器还计算出球体的哪个面显示材质的哪一部分

在这里插入图片描述

每个材质文件都会综合一个着色器算法,初始都是标准着色算法(Standard),除此以外还有其他着色算法可供选择,或者自己写一个。

在这里插入图片描述

模型与资源

资源的导入

我们可以将Unity项目中的各种资源打包,也可以将外部资源导入,资源的格式:*.unitypackage。

导入方式:将要导入的资源文件拖到Assets文件夹下完成导入,或右键项目栏,点击ImportPackage进行导入。

导出方式:右键项目栏,点击ExportPackage进行导出。

导出资源的标准:

Meshes/Models 模型;

Textures 贴图;

Materials 材质;

Prefabs 预制体;

Scenes 样例;

外部模型

Unity支持的模型文件:

3dsMax (*.max *.3ds)

Maya (*.ma)

Blender (*.blend)

Wavefront (*.obj)

模型被导入时转换形式变成FBX (*.fbx) 文件,是Unity的标准格式**(仅限Mesh网格;材质和贴图额外生成)**。

在这里插入图片描述

材质的标准保存格式:Material(*.mat)

各种贴图的标准保存格式:(*.tga)

贴图->材质->模型->3D对象

如果外部模型资源包含了预制体,则直接拖过来使用便可,如果不包含,则可以自己通过上述步骤拼接模型。

多个模型拼接而成的一个大模型被称为组合模型

物体的运动

类似于2D中学过的,3D物体的运动也是需要脚本来控制的,方法基本相同

transform.Translate(x,y,z)让物体移动固定长度的方法3D中同样适用。

物体朝向的更改

3D情况下要想让一个物体朝向另一个,显然比2D中复杂,定义了两个对象Ammo和Target:

Transform target = transform.Find("/Target").transform;
Vector3 face = transform.forward;
Vector3 direction = (target.position - transform.position).normalized;
float angleX = Vector3.SignedAngle(face, direction, Vector3.right);
float angleY = Vector3.SignedAngle(face, direction, Vector3.up);
transform.Rotate(angleX, angleY, 0);

由于这个过程比较复杂,Unity有LookAt API来使用:

Transform target = transform.Find("/Target").transform;
transform.LookAt(target);

值得注意的是,LookAt指定朝向的默认方向是z轴。也就是说,使用LookAt的话物体的z轴朝向会正对目标。所以当物体的正面不是z轴正向的话,LookAt就不起作用。

默认情况下,三个轴的对应关系:

X轴:红轴:右right

Y轴:绿轴:上up

Z轴:蓝轴:前forward

原则上来说,模型应该在建模软件中被调整好后再导入Unity,被导入的模型应该有以下标准:

(Axis)模型默认朝向Z轴方向;

(Pivot)模型的轴心应该位于模型底部的中心(方便定位);

(Size)尺寸不应当再进行改变,模型大小应当基于真实比例大小(1Unit = 1meter)

练习:设计一枚追踪导弹

思路:目标自动向一个方向移动,子弹永远朝向目标,匀速移动

//目标
//Update内
float step = 2f * Time.deltaTime;
transform.Translate(0, 0, step);
//导弹
//Update内
Transform target = transform.Find("/Plane").transform;
transform.LookAt(target);
float step = 2.4f * Time.deltaTime;
transform.Translate(0, 0, step);

在这里插入图片描述

天空盒

我们在游戏栏和场景栏中看到的游戏背景是一种叫天空盒的材质,它的Shader方式为Skybox/6Sided。这个着色器下的材质需要六张正方形贴图来规定天空盒的六个面:Up,Down,Left,Right,Front,Rear。这6张图片尽量大,2048x2048 或 4096x4096都可。整个游戏都是在天空盒内部进行的。

在Window/Rendering/LightingSettings中可以将天空盒的材质拖入。

在这里插入图片描述

天空盒的手动创建

首先将六张贴图准备好,必须让六张图可以无缝衔接。将六张贴图的WrapMode改为Clamp(紧凑模式),新建天空盒材质并选择6Sided着色器模式。将图片拖入即可使用。除了六面型天空盒,还有其他种类的天空盒。

在这里插入图片描述

动画

一个物体的运动有两种实现方式,一个是代码,另一个就是动画(Animation),在Assets文件夹创建Anim文件夹存储动画文件,创建动画(Animation)。

将Inspector窗口改为**Debug(深度开发)模式,给Animation勾选Legacy,这个模式用于播放单一的动画文件。另外一种状态机动画(Animator)**适合播放复杂动画,例如人物移动,跑跳等。

在这里插入图片描述

将动画文件拖给游戏对象就可以实现挂载,游戏对象就可以根据动画的指示做出反应。

WrapMode中规定了动画的几个播放模式:Once,Loop,PingPong;

动画编辑

在Window/Animation中找到动画编辑器菜单,把它拖到方便的地方,选中要用编辑器操作的游戏对象,点击AddProperty,加入position,可以通过添加关键帧的方式,从时间角度控制物体的运动。

在这里插入图片描述

左边框信息视图,右边时间视图,使用滚轮缩放,鼠标中键拖动。

加入position,进入录制模式/编辑模式(红色圆圈),一切修改都必须在录制模式下才能自动保存。创建两个关键帧,修改当前的position信息,两个关键帧拥有不同的位置信息则动画建立完成,可以预览,退出录制模式,动画保存成功。

在这里插入图片描述

关键帧:在某一帧时游戏对象的状态,行为规定好后,动画在关键帧间的行动都会由插值算法自动计算,可以极大节省工作量。关键帧由菱形方块表示。

插值算法:给定两个时间点的两个值,通过不停计算两个值中间的值,得到平滑的位置曲线的算法。

动画曲线

使用Animation制作的移动动画不是匀速的,它有一个加速减速的过程,这个过程由动画曲线(Curves)来表示。曲线上的点表示所对应参数对应时间的值。

在这里插入图片描述

快捷键:

ctrl+滚轮:横向缩放

shift+滚轮:纵向缩放

鼠标中键:按住拖动

选中某一参数:只显示此参数

选中某一参数按下F键:放大观察这个参数的整个曲线

在曲线上双击:添加关键帧

选中这条曲线的左右顶点(关键帧),右键打开这个关键帧物体的移动方式。将前后两个关键帧的BothTargents(左右切线)改为Linear(线性模式),就可以将曲线变成平滑的。Free(拖动式),Constant(常量)阶梯式,Weighted(加权拖动式)。Free和Weighted模式下会显示贝赛尔曲线手柄,能够微调曲线。

在这里插入图片描述

动画的父子关系

在这里插入图片描述

建立一个直升机,上面的螺旋桨是它的子对象。为它创建动画,可以看到在加入组件的窗口里有它的子对象可以调控

在这里插入图片描述

加入子对象的rotation属性就可以编辑子对象的动画了(Legacy),将螺旋桨旋转角度的起止设置成0~359度,曲线设置成线性,螺旋桨动画制作完成。

动画的回调方法

可以规定在动画的哪一帧触发回调函数,为此我们需要准备一个脚本和一次动画。脚本内建立了一个回调函数,动画为小球掉到地上反弹。

在这里插入图片描述

//回调函数
public void Drop()
{
    Debug.Log("PONG!", this);
}

在这里插入图片描述

在时间轴和动画曲线的中间位置右键可以添加动画回调函数,意思是在动画运动到此帧的时候触发对应函数,这个函数在被回调的时候可以传参(有些参数不能),并且脚本必须要被挂载到和动画相同的游戏对象上

在这里插入图片描述

使用API来播放动画

进入之前使用的小球弹跳脚本:

//Update内
Animation anim = transform.GetComponent<Animation>();
if (!anim.isPlaying)
{
    //anim.Play(clipName)	播放指定动画,缺省则为默认动画
    anim.Play("Ball");
}

动画状态机

状态机(Mecanim)是一个计算机术语,它用来区分和控制一个事物或系统的不同状态,比如控制玩家行走与跑动之间的切换。

一个人物模型最基本有以下几种状态:

静止(Idle) 走(Walk) 跑(Run) 游泳(Swim) 攻击(Attack)

Animator

Animator用于可视化编辑动画状态机,在动画文件中新建AnimatorController,将它挂载到游戏对象上,用于控制这个游戏对象的状态机。双击AnimatorController可以进入状态机编辑器,将它调整到合适位置。

快捷键:

鼠标中键/Alt+左键:拖动窗口

鼠标滚轮:缩放窗口

F:完全显示所有状态方块

在这里插入图片描述

状态方块的属性:Motion规定了当前状态下物体播放的动画,Transitions规定它有哪些过渡。

在这里插入图片描述

右键状态方块可以新建状态过渡(Transition),用于不同状态之间的切换,Idle -> Walk 代表可以从Idle状态直接切换为Walk状态。右键灰色的状态方块SetAsLayerDefaultState可以将这个状态设置为默认状态,设置为默认会在Awake的时候进入该状态。

动画绑定

添加jump动画,将它拖给到动画控制器里的jump状态的Motion属性内,动画中勾选LoopTime表示循环播放,不可以改用Legacy模式。

状态参数

状态机的切换宏观上调控是依照玩家的输入进行的,微观上是一个个状态参数在调控,在Animator编辑器的左侧边栏中可以建立状态参数。建立walking参数,选中Idle->Walk的过渡,将HasExitTime(定时触发)取消勾选,Conditions中添加walking参数,如果walking为true,则执行这个过渡;选中Walk->Idle的过渡,将HasExitTime取消勾选,Conditions中添加walking参数,如果walking为false,则执行这个过渡。状态参数的值可以是float,int,bool,trigger;

在这里插入图片描述

ExitTime

HasExitTime表示这个过渡在起始点动画执行xx秒或执行xx次后进行过渡,选中Walk->Idle的过渡,勾选HasExitTime,Settings中ExitTime为动作执行次数,执行这个动作x次以后自动进行过渡,如果Conditions有判定的话,ExitTime结束后进行Conditions的判定

FixedDuration被勾选表示进行x秒动画后过渡,没有被勾选表示进行x轮动画后过渡。

下图代表:Walk动作执行3s后进入Conditions判定,如果此时walking为false则过渡到Idle状态,过渡时间为0.1s;

在这里插入图片描述

注意,如果3s后进入Conditions判定后判定没有成功,那么这个动作会永远持续下去,所以不建议同时使用HasExitTime和Conditions双重判定。

练习:方块的前进和跳跃

脚本中可以将Animator当作组件传入,也可以直接调整状态参数,创建两个动画jump和walk,两个状态参数,将它们在animator中设置好:

Idle -> Walk:walking=true时触发

Walk -> Idle:walking=false且本次walk执行完毕时触发

Idle -> Jump:jumping=true时触发

Jump -> Idle:jumping=false且本次jump执行完毕时触发

代码

//Update内
//animator.SetBool是设置参数值方法
//animator.GetBool是获取参数值方法
//animator.GetCurrentAnimatorStateInfo(0).IsName("Name")是获取当前状态名的方法
Animator animator = transform.GetComponent<Animator>();
animator.SetBool("walking", Input.GetKey(KeyCode.W));
animator.SetBool("jumping", Input.GetKey(KeyCode.Space));

状态机行为

状态机行为(StatesBehaviour)就是在状态内才会触发的脚本。选中Animator中的一个状态方块,点击AddBehaviour为它添加一个状态机行为。

在这里插入图片描述

在这里插入图片描述

状态机行为脚本默认重载了几个状态机函数供我们使用:

OnStateEnter:进入状态时触发该函数一次

OnStateUpdate:物体处于该状态时每一帧触发该函数一次

OnStatesExit:退出该状态时触发该状态一次

模型动画

模型的动画由美术人员给出,unitypackages里包含了模型需要的动画,格式为(*.fbx)。外部导入的动画应用于模型上,配上Animator,基本上可以完整刻画出一个模型的完整形象。

学习一个模型动画设计的基本步骤

1,查看模型的文档排版,弄清存储方式

2,将模型的预制体放出来或进入写好的场景

3,找到animator,学习状态机的设计

4,尝试修改状态参数,理解调用方式

5,学习脚本,理解内部逻辑

6,尝试自己写接口使用该模型

场景搭建

游戏推进的基底就是场景,所有游戏中进行的活动都是记录在场景中的,更高级的比如地形破坏,场景互动,昼夜交替也是场景搭建中要考虑的。

搭建场景时的推荐排版:

在这里插入图片描述

这样设计的目的是方便观察,对比需要的模型材料。

正交视图与透视

点击Gizmo中心的方块可以切换正交视图(Isocate)和透视视图(perspective),正交视图下三视图模式更方便场景的布置。

在这里插入图片描述

在这里插入图片描述

地形系统

Unity自带了地形编辑器,在新建3D物体有一项Terrain地形选项,可以创建一个1000x1000的地形,新建Terrain文件夹,将创建好的地形文件放入其中。

在这里插入图片描述

Terrain的设定栏有五个按钮,从左到右

CreateNeighborTerrains:增加相邻地形

PaintTerrain:绘制地形

PaintTrees:绘制树

PaintDetails:详细绘制地形

TerrainSettings:地形设定

进入地形设定,从上到下

BasicTerrain:基础设定

Tree&DetailObjects:树和细节组件

WindSettingsforGrass:控制植被摇晃的风设置

MeshResolution:网格设置

HolesSettings:山洞,坑洞设置

TextureResolution:贴图设置

Lighting:光照设置

Lightmapping:光照烘培设置(高级光照设置,减少资源消耗)

进入MeshResolution,将网格长宽改为100x100,够用。Terrain物体的pivot在它的一个角上,不在中心

绘制地表

在Terrain的五个工具中选择PaintTerrain进入地形绘制界面,下面第一个下拉栏的六种工具从上到下:

RaiseOrLowerTerrain:升起或抬高地形

PaintHoles:绘制山洞

PaintTexture:绘制贴图

SetHeight:抬高地形到固定高度

SmoothHeight:平滑高度曲线

StampTerrain:盖章式绘制地形

在Terrains目录下新建一个TerrainLayer(地形层)资源,设定好它的反照率贴图和法线贴图,进入PaintTexture中,TerrainLayers控制地表材质,放入刚建好的TerrainLayer;可以对此材质进行详细设计。TillingSettings可以设置此贴图的面积,面积太小贴图重复度就很高,面积太大贴图会不清晰。

在这里插入图片描述

在这里插入图片描述

多添加几个TerrainLayer,以供绘制。默认情况下,地形的材质为第一个。选中想要的layer和笔刷,就可以在地形上绘制了。

在这里插入图片描述

在这里插入图片描述

植被

选中Terrain对象的PaintDetail工具,这个工具用来进行地形上的细节设计,比如花草,石头。通常来说我们应当使用模型来制作花草,但是花草太多了,如果使用模型的话会给GPU造成极大负担。在一些游戏中,花卉和石头拥有建模,而杂草使用2D贴图代替,有的游戏的花草与石头都用2D贴图。

在Details里的EditDetails可以加入一些修饰地图细节的贴图或网格,点击AddGrassTexture添加一个花草用的2D贴图,打开一个界面:用于设计这个花草的一些数值,比如最大最小高度宽度,噪波(草地颜色改变的幅度,系统以当前噪声为seed不断生成随机数的方法,我们可以人为给它一个变换率)和两个渐变的颜色。BillBoard选项用来设置这个贴图是否会跟着玩家的视角改变朝向,保证玩家永远看到草地的正面

在这里插入图片描述

选好以后,Inspector中下面的settings可以改变刷子的大小,每次生成花草的系数,拉高:每次生成的花草变多。

在这里插入图片描述

在这里插入图片描述

Terrain自带了种树功能,PaintTrees功能可以种树。和PaintDetails类似,它也是将树以笔刷的形式放置在地形上,树是比较大型的游戏对象,所以不能使用2D贴图。为此PaintTrees需要获得树的预制体,然后动态创建树到地形文件中。

将树的预制体导入,然后使用笔刷种树即可。实验证明挂载到地形中的树不会触发自己的预制体所带的脚本

在这里插入图片描述

在这里插入图片描述

造山与挖坑

PaintTerrain内有两个工具,RaiseOrLowerTerrain和SetHeight,前者用来拉高或降低地势,后者用来设定地势的高低为固定值。Unity地形中,地形的最低高度为0,如果想要挖坑,就必须先抬高整体地势。先将整个地形抬高4米,然后使用SetHeight将高度设为比4小的值就能挖坑了。

在这里插入图片描述

Canvas层

3D项目中的UI与2D中类似,新建一个Canvas层用来存放UI物件。与此同时也会建立一个EventSystem。将Canvas的RenderMode改为ScreenSpace-Camera,然后将主摄像机的游戏对象拖给它。PlaneDistance表示摄像机与此Canvas层的距离,默认为100Unit,当这个值过小,UI就会将游戏遮住。利用这个特点,我们可以制作不同层次的Canvas。

在制作UI时,最好将视角切换成2D显示模式。在这个模式下,将摄像机对准Z轴方向。

Text,Image,Button等2D效果在3D中依旧通用。

Image类型贴图注意:Default型的贴图是无法被当作Image的源图像的,需要进入那个图像里**将TextureType改为Sprite(2D and UI)**才能使用。

RawImage类型:拥有Image类型的功能,并附带了切割图片(UV Rect)的功能。

SortOrder:用来控制多个Canvas层条件下各个Canvas的先后顺序。

在这里插入图片描述

在Canvas层中,谁最后渲染,谁就在最上层:

在这里插入图片描述

在这里插入图片描述

CanvasGroup组件

可以为任何UI对象添加CanvasGroup组件,可以用来控制该组件和其子级的透明度,可互动性等,点击AddComponent添加该组件。

在这里插入图片描述

Alpha:不透明度

Interactable:是否可互动(按钮等)

BlocksRaycasts:是否阻挡射线

IgnoreParentGroups:是否忽略父级CanvasGroup的调控

TextMeshPro

TextMeshPro是Unity内置的3D字体生成器,需要一个SDF字体库。Window栏里选择TextMeshPro/FontAssetCreator,点击Import TMP Essentials会自动生成资源到Assets目录下。在FontAssetCreator中引入ttf字体库,名字不可有中文。将CharacterSet改为CuntomCharacters,将游戏中需要的所有字体都写进去,否则无法使用。

在这里插入图片描述

在这里插入图片描述

完成后,save到字体目录下。右键层级3D Object可以新建TextMeshPro类型字体。将刚新建好的SDF字库引入,就能显示字体了。如果字体不在SDF库中,会显示方块。Inspector中可以调整字体的各种属性。

在这里插入图片描述

在这里插入图片描述

TMP文本是游戏当中的物体,支持position,rotation,scale,material等一系列3D对象的属性,也包括文本框独有的边框,字体大小,字间距等属性。UI,也就是Canvas层中也可以使用TMP文本。

FontAsset:字库;

FontStyle:粗体/斜体/下划线;

FontSize/AutoSize:文字大小;

VertexColor/ColorGradient:颜色;

Spacing:文字间距/行间距/段落间距;

Alignment:对齐方式;

Anchors

在这里插入图片描述

UI对象中的组件RectTransform中的Anchor属性,与2D中一样用来规定UI的锚点。它规定了当屏幕分辨率发生变换的时候,该UI对象以屏幕的哪个地点为原点进行移动,这个值是按照百分比算的,最小值不能超过最大值。

Min X 左 Max X 右

Min Y 下 Max Y 上

AnchorPresets中可视化定点:并且还能规定对象朝哪个方向拉伸。被拉伸的图像,其宽高度也会随着屏幕自动缩放。

在这里插入图片描述

布局器

对于一个内容了很多UI对象的父级对象,可以为它添加布局器组件来调整。添加组件/Layout/VerticalLayout或HorizontalLayout

组件中的属性调整后都是可视化的。

在这里插入图片描述

在这里插入图片描述

Overlay模式

Canvas层有3个模式,最常用的是Overlay模式和Camera模式。Overlay模式下的UI对象永远在游戏屏幕上方,类似于视频中的水印。游戏中HUD和玩家状态都显示在Overlay的Canvas层。

在这里插入图片描述

HUD

HUD代表HeadUpDisplay抬头显示,就是跟随玩家或游戏对象移动的UI对象。一般是血条或等级文本。

在Canvas中添加一个Image,子对象中添加一个文本。Image轴心设为屏幕左下角。新建脚本HudForObject:

[Tooltip("要跟随的移动目标的transform")]
public Transform follow;

void Update()
{
	//将目标的位置转换成屏幕位置
    Vector3 sp = Camera.main.WorldToScreenPoint(follow.position);
	//HUD跟随
    transform.position = sp;
}

对于想要添加HUD的对象:

为它新建一个子节点,位置为它的上方,这个子节点用于定位hud的位置。将这个子对象拖给hud。

在这里插入图片描述

在这里插入图片描述

尝试拖动对象,观察它的HUD是否跟着自己移动。在实际项目中,HUD要思考的细节更多。并且一个游戏是允许多个Canvas的,要让HUD所在的Canvas的SortOrder比其他UI靠后,才能保证HUD不把UI叠加。

练习:文本显示游戏时间

新建Canvas层,在其中新建Text,使用 (分:秒) 的时间格式。挂载脚本

在这里插入图片描述

脚本:

using UnityEngine.UI;

public class TimeCtrl : MonoBehaviour
{
    private float startTime;
    private Text timingText;

    void Start()
    {
        timingText = GetComponent<Text>();
    }

    void Update()
    {
        //在按下Space后开始刷新游戏时间
        if(Input.GetKeyDown(KeyCode.Space))
        {
            //定义开始时间点
            startTime = Time.time;
			//不停调用刷新文本函数
            InvokeRepeating("UpdateTime", 0.1f, 0.1f);
        }
    }
    private void UpdateTime()
    {
        //时间累加器
        float index = Time.time - startTime;
		//转化时间为string的分秒形式
        //Format用于将数据以特定格式方便的转换成string
        string str = string.Format("{0:00}:{1:00.0}", index / 60, index % 60);
		//更新时间
        timingText.text = str;
    }
}

在这里插入图片描述

用户输入进阶

在写代码时如果只使用Input.GetKey()和Input.GetMouse()的话,用户便无法在游戏内进行键位的更改,另外如果用户使用其他游戏设备,例如手柄,游戏就不能玩了。为此,虚拟键应运而生,以下脚本中仍然可以控制游戏对象移动。

[Tooltip("移动速度")]
public float speed = 5f;

void Update()
{
	//Fire虚拟键对应鼠标左键
    if (Input.GetButton("Fire1"))
    {
        transform.Translate(0, 0, speed * Time.deltaTime);
    }
}

Input.GetButton(string) Input.GetButtonDowm(string) Input.GetButtonUp(string)

默认虚拟键映射表(部分):

虚拟键键盘鼠标摇杆
Fire1Left CTRL左键Fire1
Fire2Left ALT右键Fire2
Fire3Left Shift中键Fire3
JumpSpaceJump

所有虚拟键可以在Edit/ProjectSettings/InputManager中查看和修改。

另外,Unity提供了另外一套用户输入API,扩展性,兼容性更好:UnityEngine.InputSystem,需要安装插件才能使用。Input和InputSystem是二选一的,不能同时使用。

轴输入

通过观察映射表,发现操控前进和左右移动的按键Horizontal和Vertical,但是控制移动的按键最少有四个,此时需要轴输入了

void Update()
{
    //获取轴输入
    float h = Input.GetAxis("Horizontal");
    float v = Input.GetAxis("Vertical");
	//移动
    transform.Translate(h * speed * Time.deltaTime, 0, v * speed * Time.deltaTime);
}

Input.GetAxis()得到的是一个在[-1,1]区间内的值,如果使用键盘的话,这个值会在短时间内从0变成1或-1,如果使用手柄,这个值会随手柄输入的值发生变化。

InputFlag

在代码中写入的InputAPI当中的函数,归根结底是判断一个值是不是变为了true,比如Input.GetBotton(“Fire1”),如果此时玩家按下了Fire1键,Input便会将底层的FIre1键的InputFlag置为true,所以如果两个被激活的脚本中都有Input.GetBotton(“Fire1”)语句,那么当玩家按下Fire1时,这两个脚本都会得到Fire1的InputFlag被置为true这一事件。这样设计可以避免脚本间冲突。

InputFlag在下一Update到来前会被清理。如果下一Update中又被触发,则继续将InputFlag置为true。建议在Update中进行所有的Input调用。

物理系统

球在空中不动是因为它没有刚体物理属性。

在这里插入图片描述

为球添加物理刚体后落到地面被拦住,是因为球受到重力作用,且球和地板都有碰撞体。SphereCollider和MeshCollider。全程由物理系统计算得出结果,没有使用脚本。

在这里插入图片描述

碰撞体

碰撞体分为三类:

StaticCollider 静态碰撞体 房屋,地面等 总是保持静止

RigidBodyCollider 刚体碰撞体 可运动,受力的作用

KinematicRigidBodyCollider 运动学刚体碰撞体 不受物理学约束,无质量无速度的碰撞体(较少使用)

物体+Collider = 静态碰撞体 StaticCollider

物体+Collider+RigidBody = 刚体碰撞体RigidBodyCollider

物体+Collider+RigidBody+IsKinematic = 运动学刚体碰撞体KinematicRigidBodyCollider

碰撞体的几种形状:

在这里插入图片描述

BoxCollider 盒状碰撞体

CapsuleCollider 胶囊状碰撞体

MeshCollider 网格碰撞体

SphereCollider 球状碰撞体

TerrainCollider 地形专用碰撞体

WheelCollider 车轮专用碰撞体

**每种碰撞体都有自己的形状参数可以设置,一个物体可以挂载多个碰撞体。**在制作游戏时,一个物体的碰撞体往往都是使用这种模拟近似的形状来规定的,差不多支持运动逻辑即可,过于精细的碰撞体不会让玩家游戏体验提升多少,反而加大了内存开销,MeshCollider就是完全贴合物体网格的碰撞体。

一般做法:

静态物体:MeshCollider

动态物体:BoxCollider/CapsuleCollider/SphereCollider (PrimitiveCollider)

无关环境物(花草、远景):无Collider

物理材质

两个物体接触时,会进行弹性,摩擦力的计算。PhysicsMaterials用来规定这两个参数。下图为默认情况下(物体没有给定物理材质)物体的物理材质

在这里插入图片描述

DynamicFriction:当物体移动时它的摩擦系数(动摩擦系数)

StaticFriction:当物体静止时它的摩擦系数(静摩擦系数)

Bounciness:物体的弹性系数

FrictionCombine:两物体相遇时此物体的摩擦系数取两物体间的什么值(组合方式)

BounceCombine:两物体相遇时此物体的弹性系数取两物体间的什么值(组合方式)

组合方式的优先级:Average<Minimum<Multiply<Maximum

也就是说如果两个相遇的对象使用了不同组合方式,最终采用的方式根据优先级大小给定。

刚体的运动

在物理系统中,以下参数能让物体运动:

力Force 速度Velocity 冲量Impuse 加速度Acceleration 角速度AngularVelocity 扭矩Torque

像之前一样使用transform.Position移动的方法是不物理的。正确的移动方法是获取物体的刚体组件并使用它的函数。不使用移动方法规定物体该到什么位置,而使用物理方法让物体自己移动到一个位置。

练习:让盒子在力的作用下移动

实现思想:使用物理更新函数,一般使用FixedUpdate来施加作用力。为盒子添加刚体,脚本:

//获取组件
Rigidbody rb;
//Start内
rb = GetComponent<Rigidbody>();

//根据输入的键位加入力
private void FixedUpdate()
{
    float v = Input.GetAxis("Vertical");
    //施加一个10N的推力
    rb.AddForce(0, 0, v * 10);
}

在这里插入图片描述

但是盒子一直是以翻转的形式移动的。原因是摩擦力太大了

运动学刚体KinematicRigidBody是不可以被AddForce的

摩擦力

物体在地面上移动时,由于重力和地面不平整,会受到来自地板的阻力,这个力就是摩擦力。可以通过改变重力,物体质量,地面和物体摩擦系数来更改摩擦力

给地面和盒子添加冰面材质,它们的动静摩擦系数都是0.1。

在这里插入图片描述

可以看到物体在得到一点力后就能驶出很远,说明摩擦力小了。

根据 f t = m v,只要给定力,受力时间,物体质量,就可以求出物体的移动速度。

无摩擦力情况下,1N的力施加到1kg的物体上1s时间,物体的速度就是1m/s

FixedUpdate

FixedUpdate是物理更新,相对于Update帧更新,它的更新较为严格,默认情况下规定每0.02秒更新一次

private void FixedUpdate()
{
    Debug.Log("FixedUpdate " + Time.deltaTime);
}

在这里插入图片描述

我们知道FixedUpdate方法每0.02秒调用一次,那么在内的AddForce方法就是每秒调用50次,如果AddForce(1,0,0),那么一秒执行50次的意思是:每0.02秒施加1N的力,让物体加速0.02m/s。宏观上施加了1s的推理,微观上推了50次,每次1N,作用0.02s。

FixedUpdate实际上是每次内部物理信息更新前的一次回调方法。Time.fixedDeltaTime代表物理更新间隔,一般是0.02秒。除此以外,Time.time是游戏已进行的时间,是一个折算后的时间,如果需要真实的时间,需要调用Time.realtimeSinceStartup,这个是真实的系统时间。

单线程

Unity中的所有方法都是单线程进行的,C#中使用Thread.CurrentThread.ManagedThreadId可以获取当前线程的ID

using System.Threading;

void Update()
{
    Debug.Log("UpdateThread=" + Thread.CurrentThread.ManagedThreadId);
}
private void FixedUpdate()
{
    Debug.Log("FixedUpdateThread=" + Thread.CurrentThread.ManagedThreadId);
}

在这里插入图片描述

可以看到不管是Update中的线程ID还是FixedUpdate中的线程ID,都是1。它们不会并发执行,所以不用考虑互斥,重入之类的情况。

帧调度算法

Unity内部的循环调度伪代码:

Unity_Main_Loop()
{
	While(true)
	{
		检查是否执行Update()
		检查是否执行FixedUpdate()
		...
	}
}

各个调度算法应该尽快执行完毕,避免卡顿

InputFlag

当我们在FixedUpdate中加入一些按键按下或弹起的判断时:

if (Input.GetButtonDown("Jump"))
{
    rb.AddForce(0, 5, 0, ForceMode.VelocityChange);
}

发现有时会执行有时不会执行。而当Upda内如果写了很多东西导致卡顿时,这个判断又会被执行很多次。

这是因为InputFlag的改变是Update来控制的,如果Update的间隔比FixedUpdate要小,InputFlag被置为true后,还没等到FixedUpdate来检测InputFlag是否为true,先到的下一个Update已经把InputFlag置回false,导致FixedUpdate不能判断到InputFlag。而如果Update太卡导致间隔比FixedUpdate要大时,Update间多次调用的FixedUpdate会重复检测同一个InputFlag,导致一次按键多次调用的事情发生。

所以在FixedUpdate中,不应使用Input获取事件输入,应当都写在Update中:

GetButtonDown/Up GetKeyDown/Up GetMouseDown/Up

AddForce的方式

rb.AddForce(1,0,0)默认是ForceMode.Force模式,也就是直接加力,另外还有以下几种方式:

private void FixedUpdate()
{
    //F * t = m * v 	力
    rb.AddForce(1, 0, 0, ForceMode.Force);

    //a * t = v     	加速度
    rb.AddForce(1, 0, 0, ForceMode.Acceleration);

    //P = m * v     	冲量
    rb.AddForce(1, 0, 0, ForceMode.Impulse);

    //v = v         	速度增量
    rb.AddForce(1, 0, 0, ForceMode.VelocityChange);
}

不管使用什么方式,归根结底都是要改变物体的速度和方向

另外,

AddForce是世界坐标系中的移动;

AddRelativeForce是物体自己的坐标系的移动。

练习:实现盒子的加速减速

我们已经知道给物体加速的方式,给物体减速的方式也很多,可以使用反向加速实现,使用摩擦力实现,使用刹车实现。反向加速容易让物体倒退,摩擦力可控性太低,我们选择刹车方式。

使用虚拟键前后左右控制移动,使用空格键控制刹车。

private void FixedUpdate()
{
    //横纵向移动
    float axisV = Input.GetAxis("Vertical");
    float axisH = Input.GetAxis("Horizontal");

    if(axisV != 0)
    {
        rb.AddForce(0, 0, axisV * 10);
    }
    if (axisH != 0)
    {
        rb.AddForce(axisH * 10, 0, 0);
    }

    //减速判断
    if (Input.GetButton("Jump"))
    {
        //获得物体速度
        Vector3 velocity = rb.velocity;
        //获得物体的移动方向
        Vector3 direction = velocity.normalized;

        //定义减速度
        float slowingSpeed = 20f * Time.fixedDeltaTime;

        //物体速度是否大于减速度(防止倒退)
        if(velocity.magnitude > slowingSpeed)
        {
            //向反方向加速
            rb.AddForce(direction * (-1) * slowingSpeed, ForceMode.VelocityChange);
        }
    }
}

Velocity.magnitude代表物体的速率(速度向量的绝对值)。

扭矩

扭矩Torque不是力,而是力与力臂长度(作用点与转轴之间的距离)的乘积,单位:Nm。让一个物体转动的物理学方式,就是给这个物体添加一个扭矩。

在盒子脚本中:

private void FixedUpdate()
{
    if (Input.GetButton("Fire1"))
    {
        //让盒子以自己为坐标轴进行旋转
        rb.AddRelativeTorque(0, 10, 0, ForceMode.Force);
    }
}

如果盒子的坐标轴没有发生旋转,可以尝试将窗口中的Global坐标系改为Local坐标系。

如果将物体和地面的摩擦系数调为0,根据角动量守恒定律,物体会一直旋转下去。但是刚体组件中有AngularDrag角速度衰减属性,只要它不为0,角速度还是会衰减。

在这里插入图片描述

角速度

角速度AngularVelocity,衡量一个物体转速的量,单位是弧度/秒,每秒转一圈的角速度:angularVelocity = 6.28 弧度/秒。

通过以下代码可以打印输出物体的角速度:

if(rb.angularVelocity.magnitude > 0.1f)
{
    Debug.Log(rb.angularVelocity);
}

在这里插入图片描述

可以看到当角速度达到6.8的时候就不会继续增加了,这是因为有maxAngularVelocity的限制,这个值默认是7,可以修改。

//Start内
//将最大角速度限制为2Π,每秒一圈。
rb.maxAngularVelocity = Mathf.PI * 2;

角速度的增加也有四种方式:

private void FixedUpdate()
{
    //F * t = m * v 	扭矩
    rb.AddRelativeTorque(1, 0, 0, ForceMode.Force);

    //a * t = v     	角加速度
    rb.AddRelativeTorque(1, 0, 0, ForceMode.Acceleration);

    //P = m * v     	角动量
    rbAddRelativeTorque(1, 0, 0, ForceMode.Impulse);

    //v = v         	角速度增量
    rb.AddRelativeTorque(1, 0, 0, ForceMode.VelocityChange);
}

一般使用第一种和第四种。

能量衰减

物体不会一直运动下去,它会受到摩擦力,阻力等因素的影响。为了模拟这种情况,RigidBody的属性中集成了线速度衰减(Drag)和角速度衰减(AngularDrag)功能:

在这里插入图片描述

物理碰撞

3D中两物体相遇,会根据对方的碰撞体形态来计算碰撞事件,而不是物体本身的形状。

碰撞回调:

private void OnCollisionEnter(Collision collision)
{
    Debug.Log("CollisionEnter! name: " + collision.gameObject.name);
}

刚体碰撞体->静态碰撞体、刚体碰撞体->刚体碰撞体 这两种情况都会发生碰撞。所以如果要发生碰撞,必须有刚体参与。

值得注意的是,MeshCollider+RigidBody不会产生碰撞。但如果必须使用MeshCollider进行碰撞的话,勾选Convex即可,但是这样对内存的开销巨大。最好使用近似模拟。

CCD连续检测

一个刚体碰撞体和一个静态碰撞体使用物理方式运动发生碰撞时,如果速度过快,容易穿过对方而不发生检测。

如果物体速度过快,导致物体的上一帧和下一帧都不在物体内部,也就是一帧的间隔内穿越了物体,这样系统就不会判断两物体相碰撞。

上一帧:

在这里插入图片描述

下一帧:

在这里插入图片描述

在RigidBody的CollisionDetection中可以修改物体的碰撞检测算法

Discrete:离散的 Continuous:连续的

在这里插入图片描述

基于扫掠的CCD碰撞:不断检测前进路径上是否有障碍物,如果有,下一帧发生碰撞。

推算式CCD碰撞:计算两帧的中间状态,判断是否应该碰撞。

越薄的墙壁越容易发生穿透。

组合碰撞体、子物体碰撞体

一个物体可以有多个碰撞体,使用多个碰撞体来模拟一个复杂形状的碰撞体,是Unity推荐的方法。MeshCollider开销过于巨大。

对于一个组合对象,即有子物体的游戏对象,碰撞体可以交给所有子对象,刚体可以交给父级,这样也是可以发生碰撞的。

在这里插入图片描述
在这里插入图片描述

碰撞触发条件脚本挂载到父级即可触发。无论碰到头还是身体都会触发。子物体挂载脚本是不会触发碰撞事件的

物体的质心

rb.centerOfMass可以查看一个物体的质心所在位置。给一个有刚体,碰撞体的盒子挂载脚本:

Rigidbody rb;

void Start()
{
    rb = GetComponent<Rigidbody>();
    Debug.Log("Center of mass:" + rb.centerOfMass);
}

在这里插入图片描述

当地面和物体的摩擦系数太大,而推力又足够大时,物体会发生翻转而不是平移,这是因为摩擦力和推力不在同一水平线上,两个力共同作用给物体施加了一个扭矩,导致物体翻转。

解决方法:修改物体的质心,使其与物体所受摩擦力在同一水平面上。

//质心默认以物体的轴心为坐标系,且默认坐标为0,0,0
//修改物体质心到物体底面,让其变为"不倒翁"
rb.centerOfMass = new Vector3(0, -0.5f, 0);

可以观察到不管摩擦系数多大,只要力足够大,物体总会平移不会翻转。

对于多碰撞体的情况(不管是一物体有多个碰撞体,还是多个子对象都有碰撞体),默认情况下质心的位置在多个碰撞体的几何中心的平均值点,Debug输出的这个点的坐标是相对于其轴心坐标系的。

练习:跳一跳

步骤:

1,加入场景,人物,跳台:

在这里插入图片描述

2,给玩家加入刚体,脚本,改变质心,测试跳跃:

在这里插入图片描述

public class PlayerCtrl : MonoBehaviour
{
    Rigidbody rb;
    public Vector3 jumpspeed;
    
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.centerOfMass = new Vector3(0, 0, 0);
    }
    
    void Update()
    {
        if(Input.GetButtonDown("Jump"))
        {
            rb.AddForce(jumpspeed, ForceMode.VelocityChange);
        }
    }
}

防止人物在跳跃结束后站不稳的方法:

1,添加BoxCollider加大底面碰撞体体积。

2,将质心修改到底面的不倒翁。

3,增加摩擦系数制动。

3,蓄力:

void Update()
{
    if(Input.GetButtonDown("Jump"))
    {
        timeVal = Time.time;
        
    }
    if (Input.GetButtonUp("Jump"))
    {
        float chargeTime = Time.time - timeVal;
        if(chargeTime < 0.6f)
        {
            chargeTime = 0.6f;
        }
        else if(chargeTime > 3f)
        {
            chargeTime = 3f;
        }
        jumpspeed.z *= chargeTime;
        rb.AddForce(jumpspeed, ForceMode.VelocityChange);
    }
}

4,蓄力效果:

当按下Jump时,游戏记录当前台子和玩家的scale,弹起时,将台子和玩家的scale归位。

当按住时:

if(Input.GetButton("Jump"))
{
    scaleTime += Time.deltaTime;
    if (scaleTime <= 3f)
    {
        transform.localScale -= new Vector3((-1) * scaleTime * 0.0004f, scaleTime * 0.0004f, (-1) * scaleTime * 0.0004f);
        boardA.transform.localScale -= new Vector3((-1) * scaleTime * 0.0004f, scaleTime * 0.0004f, (-1) * scaleTime * 0.0004f);
    }
}

在这里插入图片描述

5,胜负判定:

private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.name == "Floor")
        {
            Debug.Log("Failure!");
        }
        else if(collision.gameObject.name == "BoardB")
        {
            Debug.Log("Win!");
        }
    }

6,UI:

Overlay模式的Canvas界面,添加一个Panel,里面添加一个Text;当游戏胜利或失败时,执行改变文本,SetActive。

新建UIManager脚本,挂载到Canvas上:

using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    public static UIManager Instance;

    public GameObject UI_Panel;
    public GameObject UI_Text;

    void Start()
    {
        Instance = this;
    }

    public void OnLose()
    {
        UI_Panel.SetActive(true);
    }
    public void OnWin()
    {
        Text text = UI_Text.GetComponent<Text>();
        text.text = "胜利!Space键重启";
        UI_Panel.SetActive(true);
    }

修改Player的脚本:

private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.name == "Floor")
        {
            UIManager.Instance.OnLose();
        }
        else if(collision.gameObject.name == "BoardB")
        {
            UIManager.Instance.OnWin();
        }
    }

脚本实例化:

当一个脚本中的参数或函数被很多脚本调用时,可以考虑全局实例化此脚本,被实例化的脚本可以在任意其他脚本中使用它的参数和函数。实例化方法:

public class XXXX : MonoBehaviour
{
    //实例名与脚本同名
    public static XXXX Instance;

    void Start()
    {
        //让此实例指向此脚本
        Instance = this;
    }
}

只要此脚本被初始化,其他脚本就可以调用此函数:

XXXX.Instance.YY = yy;
XXXX.Instance.YYY();

7,重新开始的方法:

使用重新载入场景的方法让游戏重新开始:

using UnityEngine.SceneManagement;

//Update中
if(isJumped && Input.GetButtonDown("Jump"))
{
    SceneManager.LoadScene("SampleScene");
}

物理关节

物理关节Joint,又称 J 点,用于将一个刚体连接到另外一个刚体。Unity中的四种物理关节:

FixedJoint 固定链接 用于将一个物体钉死在另一个物体上。

HingeJoint 铰链链接 将物体固定在一个轴上,使其可以转动(门,钟摆)。

SpringJoint 弹簧链接 将一个物体用弹簧链接到另一个物体,它们之间可以伸长缩短。

CharacterJoint 人物关节 让一个物体绕一点旋转(膝盖,脖子)。

FixedJoint

FixedJoint用于固定链接某物,为一个盒子添加FixedJoint组件。

在这里插入图片描述

ConnectedBody表示它链接的那个刚体,如果没有,就表示此物体被固定在原地。

BreakForce表示破坏力,如果物体受到的力超过这个值,FixedUpdate就会断开。 Infinity表示无穷大

BreakTorque表示破坏扭矩。

在这里插入图片描述

如果此时在盒子边添加一个平台并将平台的RigidBody拖给盒子的ConnectedBody,盒子就作为平台的一部分随它运动,两物体相对静止。当两物体需要轻松分离时,FixedJoint是个不错的选择。

HingeJoint

铰链链接可以规定一个物体的哪个轴被固定,整个物体可以绕着这个轴旋转。

在这里插入图片描述

EditAngularLimists可以查看与当前旋转轴相垂直的面

Anchor用来修改轴在物体当中的坐标,是个相对位置,以轴心为坐标原点

Axis用来修改物体绕哪个轴旋转

ConnectedAnchor用来表示此物体链接的物体的轴心坐标,未规定则是自己的轴心。

在这里插入图片描述

修改轴向为(0,0,1),可以模拟跷跷板的效果。

勾选UseLimitsk可以设置铰链链接的角度限制,也能让此铰链能有一些反弹。

铰链弹簧:可以将铰链变成自动归位的铰链

勾选UseSpring启用弹簧,可以设置弹簧的拉力,衰减和归位位置。

模拟开门:

在这里插入图片描述

铰链马达:

勾选UseMotor可以启动这个铰链的马达功能,此时不要勾选UseLimit。

Motor设置中可以修改这个铰链的目标速度,受到的力。FreeSpin可以控制这个马达是否会制动。

练习:制作一个单摆钟

1,建立模型碰撞体,材质,刚体,铰链关节,限制角度

在这里插入图片描述

2,新建质心子对象,将其放到单摆顶部,在脚本中将质心位置设置为它的localPosition。

public Rigidbody rb;

void Start()
{
    rb = GetComponent<Rigidbody>();
    //找到子对象
    //本地坐标为相对于轴心的坐标
    rb.centerOfMass = transform.GetChild(2).localPosition;
}

3,向质心施加一点扭矩

void Update()
{
    if(Input.GetButtonDown("Jump"))
    {
        rb.AddRelativeTorque(10, 0, 0, ForceMode.VelocityChange);
    }
}

在这里插入图片描述

触发器

触发器Trigger用来检测碰撞体之间的碰撞,Collider+IsTrigger可经由物理系统检测两物体是否发生碰撞。

实验:拥有Collider+RigidBody的小球和拥有Collider的墙壁相撞

结果:小球被撞,停下,球和墙的OnCollisionEnter打印输出碰撞发生。

实验:拥有Collider+RigidBody的小球和拥有Collider+IsTrigger的墙壁相撞

结果:小球穿过墙壁,两物体的OnCollisionEnter均没有打印输出。

实验:在先前基础上给墙壁增加OnTriggerEnter判定

结果:小球穿过墙壁,墙壁的OnTriggerEnter检测到小球

private void OnTriggerEnter(Collider other)
{
    Debug.Log("Other: " + other.gameObject.name);
}

说明物理系统检测到了它们的碰撞,但是物理系统不接管这个碰撞后发生的事情,触发器Trigger用于检测碰撞,但不需要计算物理。

触发器的事件函数:

OnTriggerEnter:发生接触

OnTriggerStay:接触中

OnTriggerExit:脱离接触

并且发生碰撞时,双方的OnTrigger都能检测到这个碰撞:给小球添加OnTriggerEnter但不要勾选IsTrigger

在这里插入图片描述

双方都检测到了碰撞。

触发器的用法

当我们需要检测玩家或物体是否进入了某个地点,某个门,这时我们只需要检测物体是否发生碰撞,而不需要计算碰撞发生之后的物理,就可以使用触发器:

在这里插入图片描述

在门的中央创建一个方形碰撞器+IsTrigger,当物体穿过门框但没有与门框发生碰撞也能触发Trigger。

在这里插入图片描述

不同类型的碰撞体和触发器可以在同一个物体上使用,空物体上也可以使用碰撞体

PhysicsDebugger

在Window/Analysics可以找到PhysicsDebugger(物理调试器),通过它可以一目了然当前场景中的碰撞器,触发器,刚体等物件,也会醒目的标出哪些物件是没有物理的。

在这里插入图片描述

TimeScale

TimeScale是游戏时间流速控制器,它可以控制整个游戏的时间流速Time.time。例如:

Time.timeScale = 1f 正常的时间流速

Time.timeScale = 0.5f 正常时间流速的一半

Time.timeScale = 0f 时间静止(Time.time不再流动)

测试:小球的脚本:

public class Ball : MonoBehaviour
{
    public float moveSpeed;
    
    // Start is called before the first frame update
    void Start()
    {
        Time.timeScale = 1f;
    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetButton("Jump"))
        {
            transform.Translate(0, 0, moveSpeed * Time.deltaTime);
        }
    }
}

将Time.timeScale改为0.5f:小球明显变慢

将Time.timeScale改为0f:小球不再移动(Time.deltaTime永远为0)

打印输出Time.time:

在这里插入图片描述

打印输出Time.realtimeSinceStartup:

在这里插入图片描述

开始游戏后经过的真实时间是不受影响的,而且:

Time.time流速减小,Update照常运行,Time.deltaTime被缩短

Time.time流速减小,FixedUpdate运行速度减慢

Time.time流速减小,Animator动画的速度减慢

值得注意的是,Time.timeScale是全局的,它能让全局的时间流速变慢。如果需要让某个动画的速度不受全局时间流速影响:

_animator.updateMode = AnimatorUpdateMode.UnscaledTime;

场景管理

Unity的文件中,游戏是以场景打包的 *.unity,内部包含了游戏对象、组件、各种资源的引用。游戏做的比较大的时候,需要多个场景的切换。在Create菜单中就可以新建场景Scene。Hierarchy层级栏中显示的是当前场景中的信息。场景文件中本身不包含资源,它引用了资源

加载场景

Unity中有两种场景加载方式:LoadScene(同步加载场景)和LoadSceneAsync(异步加载场景),需要库:UnityEngine.SceneManagement

我们拥有多个场景:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tItvV9sz-1637307327222)(E:\Paintings\UnityProject\test3D\pic\image-20211118204236330.png)]

首先需要将所有需要加载的场景都放到BuildSettings(发布设置)里:File/BuildSettings

在这里插入图片描述

可以通过脚本控制场景的切换:

using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            SceneManager.LoadScene("MenuScene");
        }
    }
}

点击鼠标左键就可以将场景切换到MenuScene了。必须是已经被添加到BuindSettings的场景才能被加载到

也可以:

SceneManager.LoadScene(1);

前提是MenuScene的索引序号是1;

在这里插入图片描述

场景销毁

默认的,从场景A切换到场景B后,场景A的所有游戏组件,组件都会被销毁。对象在被销毁时会回调一个方法OnDestroy(),我们可以重写它。

//gameManager中
private void OnDestroy()
{
    Debug.Log("GameManager Destroyed!");
}

切换场景:

在这里插入图片描述

但是有些对象包含了一些重要性的全局数据,如果需要保护这些数据,就需要保护一些对象在切换场景时不被销毁,DontDestroy可以要求Unity在切换场景时保护一些东西不被销毁。

DontDestroyOnLoad(obj);

//GameManager中
void Start()
{
    DontDestroyOnLoad(this.gameObject);
}

在这里插入图片描述

在运行游戏后,可以发现GameCtrl被提拔了,它变成了全局场景DontDestroyOnLoad的东西。在设计游戏时要规定好要放到DontDestroyOnLoad下的对象,不能把对别的场景下某个对象的引用放到DontDestroyOnLoad,切换场景后全局对象引用的对象会被销毁,报错。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-20 18:43:41  更:2021-11-20 18:43:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 23:38:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码