虚幻提供许多编辑器
蓝图与C++
虚幻支持的文件内容
| 分类 | 类型 |
|---|---|
| 3D | fbx/obj |
| 贴图 | png/jpge/bmp/tga/dds/exr/psd/hdr |
| 声音 | wav |
| 字体 | ttf/otf |
| 视频 | mov/mp4/wmv/avi |
常规使用的文件格式一般都支持
显示UE的资产
UE推荐的文件分类文件夹命名
设置文件夹颜色,帮助分类
光源
光源的移动特性
光照模式
间接光照是真实世界的光照重要组成部分
构建光照可以获得间接光照缓存(光的颜色贴图),这样运行时直接使用该贴图即可
当你对场景进行构建的时候,会进行光照收集(光量子收集),收集这片区域每个点上的光亮强度,记录光的颜色。当移动物体在区域内移动的时候会把颜色贴到物体表面上从而产生照亮效果。
当物体在场景中移动的时候,其实就是在光照贴图中找对应区域的颜色,然后把颜色贴到物体表面上去(给人感觉像是照亮罢了)
蓝图时虚幻4中的一个可视化编程语言。具备传统编程语言的特点,同时具备简单、易用、易理解的特性
种类
Actor(演员)
在目标文件夹(一般是新建名为BluePrints的文件夹)中右键,新建蓝图即可
在编辑器视口中,按住鼠标右键拖动
在编辑器是口中,按住鼠标左键拖动可拖出节点或执行线
按住alt键再点击连接线,可删除连接线
安卓ctrl键再点击连接线,可重新选择目标节点
读取蓝图的时候,从白色执行线的起点开始,顺着执行线一个蓝图节点一个蓝图节点的读取即可
事件类型
创建的蓝图对象直接拖入到场景中并不会被渲染出来,需要添加网格组件才可以被被渲染出来
关闭每帧执行的tick Begin Play(事件开始运行的执行 先于 Tick每帧调用)
想创建一个朝固定方向移动的球体
AddActorWorldOffest节点,设置参数日志可以提供运行时态的程序信息,并进行输出 通过查看日志来查看对象状态,以反馈数据内容
比如查看玩家具体速度数值、根据数值异常查找bug、判断逻辑是否执行等
打开日志窗口
在日志窗口中,可以右键清除日志
选中注释,点击键盘del即可删除
功能类似构造函数 只要蓝图对象被构建、调用,则该函数就会执行
当你在编辑器窗口拖动时,虚幻会认为你要重新构建该物体,所以物体的构造函数会被执行
通过图片上的设置,可以关闭拖动时重新构建调用构造函数
一般将调整信息逻辑放入到Construction图表中,只要在场景中物体被移动,Construction就会执行,物体会自己重新设置自己
在编辑器模式中拖拽蓝图对象会调用Construction,但是在真正的独立发布软件上使用代码控制对象不会执行Construction,也即发布后的软件上只在最开始的时候调用一次Construction
选中目标变量,将其拖入到蓝图的视口中,可以选择使用get/set方法
直接在视口中敲击 +、-、*、/、%即可进行对应运算
基本数据类型中,字节型、整型、浮点型均支持常规数学运算,即加减乘除
执行顺序是事件开始->设置Age的值->Print1->Print2
Age的初始值为5
当执行到*设置Age的值*时,根据针脚指针往回推导,开始计算,最后设置Age的值为40
当执行到*Print1*时,直接获得setAge的值为40
当执行到*Print2*时,根据针脚指针往回推导,开始计算,最后获得输出值为215
Print2时,Age的值已经被改变了 所以Print1和Print2的输出并不相同
整型数据的自增自减
常见流程控制
判断是否成年
允许逻辑执行先进行分割,但不是并行关系,而是次序关系,最上层位于运行上端,以此类推 序列支持多个次序执行线并存,通过增加引脚来增加次序执行线 序列中的逻辑执行针脚是按照次序一次执行 目的是来帮助开发者将逻辑进行划分,可以将逻辑分成几块,而不必非要串联到一条逻辑线上,达到清晰逻辑结构的目的
Do节点用来控制当前执行逻辑线进行连接后,视口可以通过输出逻辑针脚将逻辑执行权向下进行传递,主要用来控制执行权通过次数
Do N可以用来制作开枪,比如枪管只有20发子弹,只能发射20发;有的武器只能发射一次,则使用Do once
Do Once节点中的Start Closed表示初始是否是非激活状态,勾中表示一开始Do Once节点就是失效的不会执行后续操作
Do Once,最开始是激活状态,所以会输出字符串1Do Once之前已经执行过一次了,所以不会执行后续操作Do Once节点,使之重新进入激活状态Do N,设置Do N节点的N为5,所以点击五次3,才会使Do N节点失效Do N节点的次数与DoOnce节点基本相同,但是可以通过添加新的引脚,增加独赢的输入逻辑,只要任意逻辑输入针脚有调用,则整个节点被关闭
通过调用重置输入针脚重置整个控制节点
这里A、B、C中任意一个被执行了,另外两个都不会再次执行,除非重置整个
Do Once MultiInput节点
节点取入执行输出并在两个执行输出间切换
节点同时有bool变量输出,可以用来判断A何时被调用(执行A返回true)
例如:第一次被调用执行了A,则第二次被调用执行B,然后再是A,然后再是B
实际运用:开关灯
按照从First Index开始,到Last Index结束,调用逻辑输出引脚Loop Body,调用次数为First Index到Last Index差值
Index输出参数引脚将输出从开始到结束的所有整数,当整个节点执行完毕后,调用逻辑输出引脚Completed
常用于叠加计算,或多次执行某个动作 等价C++代码中的
for关键则
输出5 6 7 8 9 10 Complete
与ForLoop相同,用于将单词执行逻辑进行循环
但添加了Break输出引脚,如果被打断则直接停止循环,跳到输出逻辑引脚Completed
使用for循环计算1~100的和,当和超过1000的时候停止计算
当和超过1000时,分支语句Branch为True的针脚向后执行,Break掉ForLoopWIthBreak,随后直接输出计算和1035
用来控制开启和关闭的执行逻辑
Enter输入逻辑进入
Open引脚被调用后,Enter引脚流入的执行逻辑才会从Exit引脚流出
Close引脚被调用后,Enter引脚流入的执行逻辑将被阻挠,无法流出
Toggle从开和关之间进行往返
Start Close设置节点开始状态,勾选后开始状态为关闭
最开始
Start Closed为选中状态,所以Gate处于关闭状态,点0无反应
点按钮键盘1之后,Gate打开,再点击0执行输出Toggle
点按钮键盘2之后,Gate关闭,再点击0,无输出
点按钮键盘3之后,Gate如果原本关闭则变为打开,如果原本是打开则变为关闭,再点击0,根据状态判断是否输出
输入逻辑节点将按照顺序或随机方式从右侧输出引脚进行输出执行逻辑
Reset 重置整个节点,使之回复到起始状态
Is Random 勾选引脚,按照随机的方式从右侧输出引脚选择输出执行逻辑
Loop 勾选引脚,按照循环方式从右侧输出引脚输出执行逻辑
Start Index 设置第一调节用节点从右侧那个引脚进行输出
只要不勾选
Loop,则每次执行MultiGate会把逻辑引脚依次全部进行一次输出(不会重复输出)
如果
Is Random和Loop都没勾选,则多次点击键盘按钮会输出一次Out 0,一次Out 1,然后MultiGate关闭
如果只勾选了Is Random,则多次点击键盘按钮1会随机输出一次Out 0或Out 1
如果只勾选了Loop,则多次点击键盘按钮1会循环输出Out 0,Out 1
只要特定值Condition为true,则whileloop节点将会输出一个结果。在循环的每个迭代中,他会查看器输入bool值的当前状态,一旦为false,则停止循环
等价C++的while
SumValue初始值为0,每次都SumValue = (SumValue + 1) * 2,当SumValue小于100的时候执行循环体,大于100就跳出循环,最后输出SumValue的值为126
当逻辑输入针脚被调用时,将会按照给定的值Selection进行右侧选择引脚输出
功能等价于C++的switch
如果值为1,打印执行值为0对应的第一个Print
如果值为2,打印执行值为1对应的第二个Print
如果值为3,打印执行值为2对应的第三个Print
如果不为1、2、3,打印执行Default对应的Print
Delay节点,延时操作
当延时任务未完成时,再次调用节点,忽略调用
尽量不要将Delay节点让如Tick中
Delay只能在事件表中使用
Duration延时动作持续时间
Retriggerable Delayer与上面的Delay的节点一致,只是如果延时任务没有完成,再次调用节点,则延迟动作重新计时
不停按键盘按钮4和键盘按钮5可体会
Delay和Retriggerable Delayer的差别
面向对象的特性为封装、多态、继承
我们创建编写的蓝图,称之为蓝图资产或蓝图模板
模板是用来产生真实对象的,同时模板也是一种数据类型,一种复合数据类型,主要是用来描述对象的结构和行为,这也被称为抽象
蓝图变量框添加的变量,称之为成员变量
成员变量是抽象为对项目模板而添加的,只能通过蓝图的实例化对象才能有效访问该成员变量
成员变量描述了抽象的对象的属性信息
在制作触发器的时候,Box Collistion是不容易看见的,这个时候给触发器一个Billboard组件,该组件仅提供一个显示作用,方便查找
这里场景编辑器就是前面说的关卡编辑器,写急了
函数:一段在一起的,可以做某一件事的程序,也称作方法。函数是面向对象中将逻辑进行封装的重要表现
编写函数的目的:将逻辑单一化、清晰化、明确化、复用化,加强整个结构的可阅读性
编写函数可以提高工作效率,函数本身可以用来处理数据也可以辅助获得数据
将属性和函数编写在模板中,体现的就是封装
函数中不可调用
Delay,因为函数执行是一瞬间的,执行完就没了
蓝图中函数允许多个返回值
如果执行线最后没有连接到返回节点中,也不会报错,会返回对应数据类型的默认值
编辑器调用功能一般用于测试功能
函数细节有个扩展按钮,点击后里面有个常量框,类似C++的const void funcion() const,表示函数中对对象属性的值不会有更改
只在函数中有用的临时变量
Event事件只能在事件图表中进行构建,无法在函数、宏内进行构建
事件如函数一样可以在外部被调用,支持输入参数列表编写(函数中可以调用事件)
添加自定义事件
与Event Begin Play节点很像
事件没有输入引脚
类似C#的Event,红色节点
ChangeSpeedEvent表示事件触发后蓝图对象所执行的操作
蓝灰色节点ChangeSpeedEvent表示广播事件触发
宏本身进行的是节点替换,过量使用宏会导致程序的节点量膨胀
宏的优点:
所有的对象类型变量使用前均需要通过
IsValid节点进行安全检查,增加程序的稳定性
IsValid节点接受一个对象型(Object)数据
事件调度器是蓝图中按照观察者模式(订阅)而添加的。观察者模式是较常使用的的一种设计模式,它具有高效低耦合特性
在整个设计链上,采用有动态状态改变时主动发出通知,从而达到订阅特性
观察者模式应当构建对象,应该将对象想象为观察者和被观察者
graph LR
A[分发器] -->B(订阅者A)
A --> C(订阅者B)
A --> D(订阅者C)
A --> E(订阅者D)
观察者是希望接受被观察者的响应,所以响应逻辑(动作)一般写在观察者身上
事件应该在下希望接收通知的对象身上,调度器写到发出通知的人的身上
比如:A想知道B的工作是否完成,如果每帧都去询问无疑会增加许多无效计算浪费性能
这个时候使用观察者模式,B是被观察者,A是观察者。B工作完成之后发送事件,A接收到事件做出对应逻辑(动作)
创建事件调度器
注册监听
解除绑定
解除绑定的具体做法
Of Class通过给定的ui想模板将场景中与之匹配的对象进行查找,并返回查找到的所有对象
With Interface 通过给给定的接口将场景中与之匹配的对象进行查找,并返回查找的所有对象
With Tag 通过给定的标签将场景中与之匹配的对象进行查找,并返回查找到的所有对象
蓝图中的结构体只有属性,没有函数
创建结构体
设置结构体参数
使用数据结构
使用make 结构体名称可创建结构体对象
某些有穷序列集的所有成员,例如:性别(男女)、月份(1,2,3,4……)、星期(1,2,3……)、学历等
创建枚举
设置枚举
枚举配合Switch使用,做分支判断处理
创建数组、map、set
这里需要先定义属性的数据类型,再设置其属于array、map或set
数组的循环遍历
获得数组中index序号位置的值
Get方法有两种获得值的方式:复制、引用复制:完全隔离,获得到的数据与原数据没有关联
引用:实际上获得的就是数组中的对象,对获得的对象进行操作会改变原数组的对象
Add方法分为两种:Add Unique和普通的纯Add
Add Unique:添加的值数组中存在,则不添加
Add:不管容器中是否存在,都添加到数组末尾
查看数组的API方法的小提示
| 方法名 | 作用 |
|---|---|
| Add | 添加单个数据到数组末尾 |
| Add Unique | 如果数组中没有与数据相同的值,则加入到数组中,否则不加入 |
| Append Array | 将另一个数组添加到数组中(不考虑重复性) |
| Clear | 清空数组并清除内存空间 |
| Contains | 检查数组中是否存在给定值 |
| Find | 查找数组中是否包含某一个元素,包含返回对应位置索引index,不包含返回-1 |
| Insert | 插入值到数组中(指定位置) |
| Last Index | 获得最后元素的值 |
| Length | 获得数组长度,常用来判断获取index是否合法 |
| Remove Index | 提供一个有效下标index,删除该index |
| Remove Item | 提供一个数组中包含的数据,删除该值 |
| Resize | 设定数组长度,超过填充默认值,小于删除多余值 |
| Shuffle | 随机数组中的值(洗牌) |
| Swap | 将数组中已有的两个位置的值进行交换 |
使用Make Array可以在运行过程中动态创建数组
物理引擎通过为刚体赋予真实的物理属性的方式来计算运动、旋转和碰撞反馈
游戏引擎中的物理引擎的主要目的是为了解决物体在空间的状态信息
常规的物理引擎遵循物理定律,按照给定的算法,进行模拟物理运动。所以在没有多元因素影响的状况下,物理引擎的计算结果是一致的,与现实世界基本一致
虚幻使用的是 PhysX3.3的物理引擎驱动物理仿真以及碰撞计算
物理引擎模拟物理计算,增加游戏的代入感,是的玩家与场景之间能够进行基于物理的交互(碰撞,发力等)
物理引擎本身处理了物体与物体之间的交互关系
所有的物理运动以及碰撞都有PhysX管理,在游戏中模拟物理将有助于提高每个场景的沉浸感
碰撞响应和追踪响应(射线)构成了虚幻4引擎在运行时出冷碰撞和射线投影的基础
碰撞检测和射线检测
虚幻中的碰撞交互分为三种
产生物理交互的必备条件:刚体(包裹外壳),如果希望加入物理引擎运动,还需要开启物理模拟
虚幻引擎中,物理碰撞添加的途径有两种:
静态网格分为简单碰撞和复杂碰撞
打开网格编辑器,查看复杂碰撞和简单碰撞
可以看到绿色的框就是物体的相似接近于桌子的形态,这就是虚幻的K-DOP包裹
添加简单碰撞
虚幻提供三种简单的形状碰撞器:球体、胶囊体、盒体
虚化允许一个物体具备多个碰撞器
使用简单的形状制作成复杂的物理碰撞,是常用的设置物体物理碰撞的方法,因为简单的物理碰撞计算效率最高
从GIF中可以看到,如何删除物体的碰撞体积,如何使用简单的形状(球盒胶囊)制作物体的碰撞体积
一个模型可以存在多个碰撞框
K-DOP是包围体的一种,是K离散导向多面体(K discrete oriented polytope)的缩写(K是轴对齐平面的数字)。它抓取轴对其的平面,将其尽力推向离网格体最近的位置
大概的思想应该是:在物体外围包一个球,然后尽量把球往里面捏,向物体的内轮廓靠拢
自动凸包包裹也属于简单包裹,同过程进行计算获得包裹数据信息。需要调整凸包顶点的最大数量。
凸包数量:决定了包裹物体需要使用的凸包个数,越多越精确,消耗也越大
最大外壳定点数:每个凸包最大允许使用多少个顶点
凸包精致度:使用多少模型面做计算参考,数量越大精度越大
基于物体的形体轮廓(mesh三角面)进行计算
使用凸包
设置凸包精细度
这里可以看到最后生成的碰撞模型是由四块组成的,刚好与设置的凸包数量的值相同
同理可以修改*最大外壳定点数*来试试,该值越大,每一块凸包的就越精细顶点数目越多
会按照模型的顶点和线的关系去生成碰撞关系,模型越复杂生成的模型碰撞效率越低
在静态网格的细节面板设置中,可以开启复杂碰撞
复杂碰撞与其他物体进行运动交互的复杂度太高,所以物体开启复杂碰撞就不能开启物理模拟
从表现来看就是:如果椅子开启复杂碰撞,则用球打击椅子,椅子不会倒,只会进行判断是否触碰
前面说的,在查询时可以使用复杂碰撞检测
虚幻中使用物体类型来描述碰撞关系。虚幻只提供了少量的物体类型,我们可以在碰撞设置中进行添加
虚幻允许我们额外添加18种碰撞物体类型
踪迹类型用来相应和射线之间的交互关系。虚幻中默认提供两种踪迹类型
也可以自定义添加其他踪迹类型,虚幻最多允许额外添加18个踪迹类型
在
Object Channels中添加自定义碰撞类型每加一个物体可以选择该物体与其他所有物体的交互关系 在
Trace Channels中添加自定义踪迹类型
每加一个踪迹类型可以选择该物体与其他所有物体的交互关系
还是同样的面板中,关于物体碰撞检测的设置
虚幻引擎会优先检查碰撞启用设置,再进行物体之间的碰撞类型判断
也就是说如果碰撞启用设置为No Collision,即使两个物体的物体类型是阻挡关系也不会产生阻挡效果
虚幻中的碰撞响应分为忽略、堆叠、阻挡,与之对应的事件我们尽可以获得到
我们可以调整组件的碰撞预设,或是自定义碰撞预设,以达到响应碰撞的不同结果
堆叠事件
堆叠事件分为进入和退出堆叠,在虚幻中希望获得堆叠事件通知,需要满足以下条件
撞击事件
撞击事件的必要条件
虚幻中的物理追踪是在引擎运行状态下,允许使用线或者形状与场景物体产生交互,并将交互结果进行反馈,以达到动态产生物理交互响应的目的
射线检测
射线检测分为三种方式:
检测的方式区别是用来筛选目标
检测分为单检测(只能扫描到一个目标)和多个目标检测(返回多个目标)
样条线检测类型
多检测:被射线指向的所有人都会返回
单检测:只获得射线指向的第一个对象
LineTraceByChannel中需要设置Start(起点坐标),End(终点坐标)
为了方便调试将Draw Debug Type改为持久,方便测试射线是否符合要求
这里GetActorForwardVecotr乘以100和1000表示线段的起点是Actor前面1米到10的范围是检测范围
将方块的碰撞类型设置中对于射线的检测改为重叠,则不会被射线检测到
指定与哪些对象类型的物体进行交互,这里需要从
Object Types拽出执行线再使用make array,否则创建数组类型不对
LineTraceByChannel一一对应
LineTraceForObjects一一对应
形状检测
与射线检测大同小异
从物体前面1m~10m的范围内,发射半径为2m的球
在虚幻中,力的作用方式分为两种:
区别:
只有开启了物理模拟的物体才受力的作用
从物体质心开始发力
从给定的一个点开始给物体发力具体的请百度 这个言语我不会描述
效果 用途:一个墙,你给他中间偏上的一个位置发一个横向力
如果使用
AddImpulse,则力作用在质心上,会让物体向前平移 如果使用AddImpulseAtLocation,传入打击点,从物体到打击点会连城一条线,配合力的方向进行旋转平移
因为一直有作用力的存在,所以移动速度会越来越快
同样AddForce也有一个AddForceAtLocation,作用与冲力的相似
科普(无关物理材质):PBR渲染材质的一种渲染方式
物理材质给物体,作用在物理引擎中,描绘了物体表面的物理特性,例如:摩擦力,密度等信息
物理材质可以在物体的内容浏览器的物理中进行添加,在物体的碰撞设置中进行设置
双击添加的物理材质,可以进入物理材质的细节设置
这里需要注意的是物理属性中的表面类型
在物理材质中,我们可以设置物体的物理表面类型,用于区分物体的表面主动响应逻辑交互方式。例如:子弹打到木制的墙面和打到延时发出的声音是不同的,我们可以通过使用物理材质表面类型来区分碰到的物体表面是什么
在项目设置中,可以添加物理表面类型,虚幻允许添加最多62哥自定义表面类型
根据物理材质区分射线检测到的对象
不可开启复杂碰撞检测
随机种子会生成一个队列,每次调用随机种子的时候就是从队列的0、1、2、3…取值,所以相同的随机种子队列相同,随机的值也相同
为了让食物小球不是凭空生成,需要为其添加从无到有的动画效果,这个时候引出TimeLine
TimeLine节点只能在事件图标中打开,双击TimeLine节点可以进入细节面板
时间膨胀可以做子弹时间
勾选自动播放后,无须连接执行线就可以执行
没有勾选自动播放的话,需要连接执行线才能启动timeline
选中关键帧,右键即可设置关键帧相关信息,比如设置贝塞尔曲线等
TimeLine还可以用于向量、颜色、事件,上面只展示了用于浮点数,其他的可以通过百度了解
纯蓝图制作的贪吃蛇游戏
在游戏运行中,即使我们没有向关卡(场景)中添加一些东西,在运行的时候虚幻引擎也会自动添加一些东西在场景中。这些就是为了完成虚幻的一套GamePlay框架
包括GC(垃圾回收)、MetaData、Relection(反射)、Ediable、Class Default Object
演员/个体
不是所有的Actor都看得见,有些只是做逻辑处理并不做显示功能
组件
关卡蓝图
一些时候关卡蓝图甚至是空的,关卡蓝图一般做快速验证比较合适
多逻辑状态可以分为多个Actor去写
逻辑简单、静止不动等简单对象使用普通Actor
建议将显示(View)、控制(Controller)和模型(Mode)分开处理
键盘鼠标的输入事件先给PlayerController,再扩散到其他的Actor中
大多数逻辑
| 位置 | 功能 |
|---|---|
| Component | 功能、能力 |
| Actor | 个体本身的逻辑 |
| APawn-APlayerController-APlayerState | 主角肉体-灵魂-状态 |
| APawn-AAIController-APlayerState | AI肉体-灵魂-状态 |
| AAIController-BehaviorTree-Blackboard | AI灵魂-行为树-数据 |
| AGameMode-AGametate | 游戏规则-游戏状态 |
| UGameInstance-USaveSame | 全局游戏实例-游戏存档 |
| USubsystem | 游戏逻辑组件 |
框架(Framework)——指其约束性(框),也指其支撑性(架)。是一个基本概念上的结构,用于去解决或处理复杂的问题
框架是整个或部分系统的可重用设计,表现为一组抽象构建及构建实例间交互的方法;另一种定义认为,框架是可以被应用开发者定制的应用骨架
前者从应用方面而后者从目的方面给出定义
虚幻提供一个名为GamePlay的框架。虚幻的设计者抽象出了多种角色,在开发过程中需要沿着设计者给定的身份进行游戏设定编写。这些虽然约束了设计,但是一定程度上加强了开发的稳定性
虚幻抽象出的角色:游戏模式(GameMode),游戏状态(GameState),玩家(DefaultPawn),玩家状态(PlayerState),玩家控制器(PlayerController),用户界面(HUD)
抽象的角色,在游戏中扮演不同的角色,从事不同的工作任务
GameMode和GameState主要负责游戏中的相关信息。GM主要负责管理游戏的规则
GM的主要任务就是构建和实现游戏的规则,并将游戏中的其他角色进行注册:默认玩家、HUD类、玩家控制器、观众类、GameState类、PlayerState类
游戏规则及获胜条件这样的内容。它仅存在于服务器上(联网游戏中)。它一般在游戏过程中不会有太多数据改变,并且它一定不应该具有客户端需要的临时数据
每个关卡都应该有每个关卡自己的规则,也就是一个关卡一个GameMode
GameState主要负责游戏中共享信息的记录,游戏的信息可以通过GameState进行存储和同步。
主要存储共有数据,比如比赛时间。私有数据并不能存在这里
包括:
GameState是虚幻为进行游戏信息记录的载体。包括像关联玩家的列表、分数、象棋游戏中的象棋位置或者在开放世界游戏中已经完成的任务列表。GameState存在于服务器和所有客户端上,可以自由的进行复制来保持同步
默认玩家,不管任何游戏,都应当存在一个默认的玩家,这是肯定的!即使是最开放的游戏,也需要存在一个上帝视角进行游戏的指令发布,在虚幻中默认玩家扮演的就是这个角色。
只有继承了Pawn的对象才能成为默认玩家
默认玩家需要通过控制器进行控制,它们可以轻松地接收输入,并且可以执行各种各样的类似于玩家的动作
控制器,是负责管理玩家的Actor类型对象。主要目的是将真实玩家的意愿进行收集,最后汇总转投给虚拟角色。这个过程就是游戏中的玩家交互过程。
PlayerController(玩家控制器)是Pawn和控制它的人类玩家间的接口。PlayerControlller本质上代表了了人类玩家的意愿(行为树的运算)
其他还有AIController等
PlayerState是游戏中的一个参与者的状态,比如人类玩家或者模拟人类玩家的机器人。作为游戏一部分存在的非人类玩家AI没有PlayerState。PlayerState中适合包含的示例数据有:玩家姓名、分数、多人在线竞技游戏中的级别、多起模式中玩家是否在夺旗……
所有玩家的PlayerState在所有机器上都存在(和PlayerController不同),并且可以自由地进行复制来保持同步
玩家数据和玩家操控角色数据是两个不同的数据点
玩家数据:等级、分数(记录在Player State) 角色数据:高矮胖瘦、速度等(记录在Pawn 角色上)
HUD是一种“平头显示器”,或者说是二维的屏幕显示信息。在很多游戏中都很常见,比如:生命值、弹药量、准心十字等,每个PlayerController一般都具有一些这样的信息
HUD是用来进行简单UI绘制的入口,一般在游戏开发中均更换为UMG为用户界面开发主要手段
这些都是单例模式
GameMode分为GameMode和GameModeBase
GameMode更适用于网络游戏,增加了针对联网的API接口和处理
GameModeBase也可以联网,但是少了一些东西罢了
GameMode配置
可以新建Pawn或Character(角色),将其设置到GameMoe的默认pawn(Default pawn)中,再启动可以发现不同点
使用GameMode
切换渲染相机是哪个Actor上的(吃鸡的第一视角和第三视角的切换,类似这种)
按前面的贪吃蛇来举例:
比较老版的UI系统,与第四代UI系统相比HUD没有控件的概念
组件:提供功能
控件:提供交互
首先需要构建GameMode,并设定到当前关卡,再在GameMode中注册HUD
直接右键、新建蓝图、找到HUD即可
HUD只有一个,并且HUD就分为两部分:绘制和操控
Receive Rraw HUD函数是一个逐帧调用函数
返回值Size X和Size Y是当前屏幕的宽高,以屏幕左上角为原点
Draw Text在渲染文字的时候,会缓存一张文字贴图(每帧都计算文字大小是很耗费资源的),所以设置文字Scale变得很大的时候,会出现像素锯齿,因为这其实也算是放大图片大小
这里的Size X和Size Y不可缓存起来,因为游戏运行时窗口尺寸可以改变,所以缓存的值不一定具备通用性
简单使用图片
draw simple Texture
Draw Texture
所谓UV就是一个图片横向和纵向无线重复平铺,设定UV左上角坐标和UV的长宽可得一个纹理
Rotation:旋转角度(顺时针,绕锚点) Rot Pivot:锚点(值域0~1.0)Tint Color:叠加色,颜色相乘得出计算结果
Blend Mode:贴图与场景的融合方式半透明:会将选择贴图的透明通道值会被忽略
不透明:透明通道丢失,原本透明的变成黑色
遮罩模式
Additive:线性减淡
调制:贴图颜色与北背景相乘,类似正片叠底
透明度合成
需要新建材质文件
Draw Material Simple和Draw Material
创建的材质球需要修改材质域为User Interface才能在UI上被渲染出来
使用材质的好处:写动画逻辑
写材质逻辑
做材质运算(挖空图片、异形进度条等)
绘制材质的三角形绘制方式
Draw Rect和Draw Line
两个矩形,一个表示当前血量,一个表示最大血量
四条线,在最大血量的上下左右作为边框
一个文字:血量值比最大血量值
制作完毕
在HUD中绘制顺序的先后决定了覆盖关系
HUD默认自身是不接受键盘输入事件的,本身HUD就不应直接响应用户的输入事件而是由其他模块传递给HUD
Enable Input节点的作用就是给物体一个能够接收输入事件的组件
获得鼠标在指定区域的:进入事件、点击事件、退出事件
SetbEnableClickEvent),也要展示鼠标光标(SetbShowMouseCurse)Receive Draw HUD来绘制可见区域(Draw Rect)和不可见的触碰区域(Add Hit Box)场景使用3维坐标系,UI使用2维坐标系
例如:英雄联盟中英雄是3维坐标的对象,它的血量需要设置在2维的UI中,这个时候就需要坐标转换
使用
Project将Actor的坐标转换到屏幕坐标中通过
All All Actor Of Class找到场景中的BP_SnakeHead对象
通过Project节点将其坐标转换成屏幕坐标
将获得的屏幕坐标设置到UI上,就能将文本与对象关联起来了上面的
Deproject和Project只能在Receive Draw HUD中使用,其他地方使用报错
另外还有两个工具库函数Project World to Screen和Project Screen to Wolrd
用IsDraw表示当前鼠标是按下还是释放,记录按下位置和当前位置绘制矩形,释放不再绘制
用ClickPos这个Vector2D变量存储鼠标按下的坐标
将场景对象的三维坐标转换成UI的二维坐标,再判断是否在框选的矩形范围内即可
这里要使用的是Project World to Screen和Project Screen to Wolrd坐标转换函数,因为这里的判断明显不在Receive Draw HUD中,所以不能使用Deproject和Project
但是虚幻提供Get Actors in Selection RectangleAPI,完成上述操作
Class Filter:选择目标(如果选择Actor,则所有Actor对象都会被选中)
First Point:矩形起始点
Second Point:矩形终点
Actor Must bt Fully Enclosed:矩形是否需要完全包含住Actor
创建UMG文件
添加UMG到玩家视口中(将UI作为变量存储,方便以后更新等操作)
删除UMG
细节面板
Visibility包含五个参数
visible 可见Collapsed 不可见,并且不占位Hidden 不可见,但是占位Not Hit-Testable (self & all children) 可见,但是点击无反馈,该设置会影响子类Not Hit-Testable 可见,点击无反馈,该设置不影响子类白色的图片和黑色的进度条,从右侧的ZOrder可以的知道两个层级都是0;如果层级相同,那么渲染的顺序根据左侧树形结构图从上到下的顺序先后进行渲染,后渲染的会覆盖先渲染的,也就是同层级下面会覆盖上面的
UMG蓝图不支持键盘监听事件,键盘输入一般在控制器、人物、关卡蓝图中使用
我们覆写父类的函数OnKeyDown来监听键盘事件按
设置UI界面可以获得焦点(UI没有焦点是不触发监听事件的)
在当前UI构造的时候立即获得焦点
重写OnKeyDown方法监听输入事件
新手村(学)
核心骨干-掌握引擎(用)
江湖高手-懂得引擎(研究)
坐镇大神-超越引擎
行业大佬-创造引擎(创造)
什么是虚幻C++
学会虚幻C++的标志
为什么使用C++开发GamePlay
可以不学虚幻C++吗
需要学习虚幻C++的人
虚幻C++学习要点
GamePlay的C++编写
GamePlayFrameWork中Module-UBT-C#
UCLASS等和#include "*.generated.h"都是为HUT提供信息来生成响应的C++反射代码PublicDependencyModuleNames:PrivateDependencyModuleNames:DynamicallyLoadedModuleNames:PublicIncludePathsPrivateIncludePathsPublicIncludePathModuleNamesPrivateIncludePathModuleNamesPublcAdditionalLibratiesPublicSystemLibraryPathsPublicDelayLoadDLLSPublicDefinitions + PrivateDefinitionsTarget| 状态 | Engine | Game | 其他 |
|---|---|---|---|
| Debug(调试) | Debug | Debug | 必须在编译器上加-debug参数才能反射查看代码更改 |
| DebugGame(调试游戏) | Release | Debug | 适合只调试游戏代码 |
| Development(开发) | Release | Release | 运行编辑器发射查看代码更改 |
| Shipping(发行) | Release | Release | 无控制台命令,统计数据和性能分析 |
| Test(测试) | Release | Release | 启用了一些控制台命令,统计数据和性能分析 |
| 目标 | ||
|---|---|---|
| 空白 | 不带编辑器的一个独立可执行版本,需要提前打包烘焙内容资源 | |
| Editor(编辑器) | 直接在编辑器里打开游戏项目 | |
| Client(客户端) | 多人联机项目,生成客户端版本,需要提供Client.Target.cs文件 | |
| Service(服务器) | 有人联机项目,生成服务器版本,需要提供Server.Target.cs文件 |
| 类型 | 前缀 | 说明 |
|---|---|---|
| Level/Map | L_ | 关卡 |
| Buleprint | BP_ | 常规蓝图 |
| Material | M_ | 材质 |
| StaticMesh | S_ | 静态网格 |
| Skeletal Mesh | SK_ | 骨骼网络 |
| Texture | T_ | 纹理 |
| Particle System | PS_ | 粒子系统 |
| Widget Blueprint | WBP_ | 组件蓝图 |
| ... | ... | ... |
void ADemoGameModeBase::BeginPlay()
{
/**
* 代码创建Actor
* 获得世界(GetWorld()),调用SpawnActor方法生成对象
* AActor::StaticClass()提供类模板
* FVector::ZeroVector (0,0,0)坐标
* FRotator::ZeroRotator (0,0,0)旋转角
/************************************************************************/
GetWorld()->SpawnActor<AActor>(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
}
AActor::StaticClass():合成函数。旨在将操作类作为一个参数进行传递(传递模板)。虚幻中除了使用模板构建对象外,还增加了映射关系,可以将类作为对象构建依据,这可以方便的将类体提供给蓝图使用
C++本身是编译型语言,编辑器中看到的文件类并不是真正的C++类,只是编译后产生的映射文件
Actor的创建分为三种方式,三种方式创建的Actor生命流程有细微差距,基本流程为
组件是在BeginPlay方法之前构建的
Destroy(),调用自身的Destroy进行强制消亡操作
/**
* bNetForce 是否强制网络同步删除
* bShouldModifyLevel 主要是用来控制先删除actor再修改关卡,还是先修改关卡再删除actor,默认是true即先修改关卡(修改关卡指吧actor移除出场景)
*/
bool AActor::Destroy( bool bNetForce, bool bShouldModifyLevel )
{
// It's already pending kill or in DestroyActor(), no need to beat the corpse
if (!IsPendingKillPending())
{
UWorld* World = GetWorld();
if (World)
{
World->DestroyActor( this, bNetForce, bShouldModifyLevel );
}
else
{
UE_LOG(LogSpawn, Warning, TEXT("Destroying %s, which doesn't have a valid world pointer"), *GetPathName());
}
}
return IsPendingKillPending();
}
bNetForce 是否强制网络同步删除
bShouldModifyLevel 主要是用来控制先删除actor在修改关卡,还是先修改关卡在删除actor,默认是true即先修改关卡(修改关卡指吧actor移除出场景)
执行Destroy()只是标记该对象在场景中不存在,并不是从内存中删除。所有对象都是托管的,所以并不是说调用了之后对象就从内存删除
// InLifespan 延迟删除时间 单位秒
void AActor::SetLifeSpan( float InLifespan )
{
// Store the new value
InitialLifeSpan = InLifespan;
// Initialize a timer for the actors lifespan if there is one. Otherwise clear any existing timer
if ((GetLocalRole() == ROLE_Authority || GetTearOff()) && !IsPendingKill())
{
if( InLifespan > 0.0f)
{
GetWorldTimerManager().SetTimer( TimerHandle_LifeSpanExpired, this, &AActor::LifeSpanExpired, InLifespan );
}
else
{
GetWorldTimerManager().ClearTimer( TimerHandle_LifeSpanExpired );
}
}
}
设置延迟删除
// 虚函数
virtual void Destroyed();
当对象被删除时(非内存删除)进行回调函数
子类重写overide该函数,来完成自定义事件
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason);
对象被彻底清除时回调,回调会进行删除类型通知,子类重写之后定义自己需要做的事即可
借助全局变量GEngine子还真调用函数AddOnScreenDebugMessage即可完成输出
void UEngine::AddOnScreenDebugMessage(
uint64 Key,
float TimeToDisplay,
FColor DisplayColor,
const FString& DebugMessage,
bool bNewerOnTop,
const FVector2D& TextScale);
// 该代码会直接在屏幕上输出一个红色的test文本
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("test"));
消息从上到下(一般而言)key值从0开始,依次增加。如果key值为-1则添加文本消息,如果不为-1就修改对应key值的文本消息
如果key为-1,则添加新的信息,不会覆盖旧的信息
如果key部位-1,则更新现有信息,该方法效率更高
bNewerOnTop当key为-1时有效,直接添加到队列最上层
// If we don't have a platform-specific define for the TEXT macro, define it now.
#if !defined(TEXT) && !UE_BUILD_DOCS
#if PLATFORM_TCHAR_IS_CHAR16
#define TEXT_PASTE(x) u ## x
#else
#define TEXT_PASTE(x) L ## x
#endif
#define TEXT(x) TEXT_PASTE(x)
#endif
TEXT("test")
这里的TEXT是虚幻中用来构建非对象型字符串的关键宏,构建结果是与平台无关的宽字符串,借助对象FString带参构造函数TCHAR类型指针来构建FString对象字符串
0330
文本在进行存储时会选择文本的格式。文本常用表示格式分为二进制(无格式)、文本(ASCII)、UTF-8和UTF-16
虚幻4中的所有字符串都作为FString或者TChar数组以UTF-16格式存储在内存中。大多数代码假设2个字节等于一个代码点,因此只支持基本多文种平面(BMP),这样虚幻内部编码可以准确地描述为UCS-2。字符串以适合于当前平台的字节次序存储
当向从磁盘序列化到程序包,或者联网区间序列化时,TCHAR字符串小于0xff的字符串均存储为一串8位字节,否则存储为双字节UTF-16字符串。序列化代码可以根据需要处理任何字节次序转换
UE4提供多种字符类型进行数据处理,在不同的情境下,需要选择不同的类型进行操作
区别:大小不同、编码方式不同,所有的文本在进行存储的时候,编译器编译阶段会根据编码类型进行转码
//~ Character types.
/// An ANSI character. Normally a signed type.
typedef FPlatformTypes::ANSICHAR ANSICHAR;
/// A wide character. Normally a signed type.
typedef FPlatformTypes::WIDECHAR WIDECHAR;
/// Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requirements of the licensee.
typedef FPlatformTypes::TCHAR TCHAR;
/// An 8-bit character containing a UTF8 (Unicode, 8-bit, variable-width) code unit.
typedef FPlatformTypes::CHAR8 UTF8CHAR;
/// A 16-bit character containing a UCS2 (Unicode, 16-bit, fixed-width) code unit, used for compatibility with 'Windows TCHAR' across multiple platforms.
typedef FPlatformTypes::CHAR16 UCS2CHAR;
/// A 16-bit character containing a UTF16 (Unicode, 16-bit, variable-width) code unit.
typedef FPlatformTypes::CHAR16 UTF16CHAR;
/// A 32-bit character containing a UTF32 (Unicode, 32-bit, fixed-width) code unit.
typedef FPlatformTypes::CHAR32 UTF32CHAR;
文本类型
// Usage of these should be replaced with StringCasts.
#define TCHAR_TO_ANSI(str) (ANSICHAR*)StringCast<ANSICHAR>(static_cast<const TCHAR*>(str)).Get()
#define ANSI_TO_TCHAR(str) (TCHAR*)StringCast<TCHAR>(static_cast<const ANSICHAR*>(str)).Get()
#define TCHAR_TO_UTF8(str) (ANSICHAR*)FTCHARToUTF8((const TCHAR*)str).Get()
#define UTF8_TO_TCHAR(str) (TCHAR*)FUTF8ToTCHAR((const ANSICHAR*)str).Get()
转码函数
FString str = FString(TEXT("OK")); // FString构建方式1
FString str1(TEXT("OK")); // FString构建方式2
FString str2; // FString无初始值构建
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, str);
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, str1);
// 普通数据类型转换为FString
FString::SanitizeFloat(1.0f);
FString::FormatAsNumber(23);
FString::FromInt(10);
bool b = true;
b ? TEXT("true") : TEXT("false");
// FString转为其他类型
FCString::Atoi
FCString::Atof
FCString::Atod
// ESearchCase::Type::CaseSensitive 比较大小写
// ESearchCase::Type::IgnoreCase 不比较大小写
str.Equals(str1, ESearchCase::Type::IgnoreCase); // 比较大小
str.Contains(str1); // str中是否存在子字符串与str1相同,参数2 是否忽略大小写 参数3 查询方向从前往后还是从后往前
str.Find(str1); // 检查str1在str中存在的位置,与Contains函数搭配使用,参数3 为起始查找位置
FString source(TEXT("A*B*C*D"));
FString Left; // "A"
FString Right; // "B*C*D"
// 根据第一个找到的切割字符串进行分割,左边的给Left,右边的给Right
source.Split(TEXT("*"), &Left, &Right);
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, Left);
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, Right);
// 拼接字符串
Left += Right; // "AB*C*D"
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, Left);
// 路径拼接符
FString result =source / Left; // "A*B*C*D/AB*C*D"
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, result);
// 拼接
FString msg = FString::Printf(TEXT("%d %d"), 1, 2); // "1 2"
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, msg);
主要作用 是标记名称
FName n1 = FName(TEXT("NO"));
FName n2(TEXT("NO"));
// 比较名称相同
if (n1 == n2) {
}
// 输出
GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, n1.ToString());