VR框架AdvancedVRFrameworkV3_1分析
虚幻商城有一个非常成熟而且有诚意的VR框架,涵盖了基本上VR可以用到的所有主流功能,同时也有PC和移动端的支持。
趁着项目需求借机分析一下这个框架,后续打算基于此扩展适用于项目的框架
分析记录
2020.8.21:
1.纯蓝图+组件的思路搭建,使用起来不算麻烦也不能算太方便,学习成本还是有的
2.解耦做的不是特别的好,很多类之间有强依赖,少一个就报错
3.功能做的非常完善,该有的基本的有了
4.性能无实测,但是各类组件无脑Tick初步看有性能压力
5.部分内容比如地图、边缘发光、武器/弓箭交互等单独拎出来都是非常实用的功能
关卡管理
必要元素
- GameMode
- BP_Info_Map
- BP_Info_Level
- BP_PostProcess
- BP_PlayerPostion
以上的这些都可以用EU_Widget
来生成
BP_MapInfo
放置在场景中的具体对象,主要就是为了配置Info_Level
,在BP_GameInstance_Main
类中直接通过方法GetCurrentLevelClass
从此对象去要到Info_Level
Info_Level
Streaming Level
用于加载子关卡,比如当前关卡是TestMainMap,LevelsToLoad设置为TestMap01,那么在进入TestMainMap后会自动加载TestMap01
Transition
切换关卡的选项,首先设置时间变量,如果是0就是瞬间加载,可以设置天空球贴图和背景色
Panel
关卡面板的UI布局信息,可以参考demo演示的布局,见下图
key
本地化文字有关的一个变量,此框架是用表格的方式设置本地化。
此变量作为DataTable的RowName读取表格
pawn
对应平台的pawn设置
Pallet
LevelImage变量决定关卡在Map面板下的显示图片,如下图
BP_PostProcess
维护后期效果的类,主要目的是用于高光显示,当然高光显示还有其他几个方式,只是后期方式效果比较好
BP_PlayerPostion
玩家起始位置,内部主要实现了一个传送玩家并校准位置的方法
VR玩家配置
VR玩家需要继承自BP_Pawn_VR
或者BP_Pawn_VR_Char
,区别在于后者提供了一个半身小白人模型
Comp_Controller_VR
- Controller Set
自定义对应MotionController
的类,框架给的BP_MotionController_Hands
内通过4个ChildrenActor
分别实现模型、传送、菜单、移动功能,具体功能类如下
- BP_MComp_Teleport:传送功能,以及传送效果的一些参数配置
- BP_MComp_Hand:手模型以及动画,提供一些抓取等参数的设置
- BP_MComp_Controller:控制器模型,代替手
- BP_MComp_RadialMenu:菜单功能
- BP_MComp_Movement:移动功能
- BP_MComp_Trigger:单纯用于监听按键事件的组件,可以用来扩展输入事件
- BP_MComp_Laser:射线功能组件,提供一些射线相关参数配置
- BP_MComp_Smartwatch:手表菜单组件,需要配置相应UMG
如果设置了Controller Mesh Override
,就会用相应的模型覆盖上面设置的模型
- ControlsPresets
对应平台的按键映射
- Haptics
- HapticsForSelect:在点开Select选单时手柄震动
- HapticsForDevices:在与工具类型的道具交互时手柄震动
- HapticsForGrab:在触发Grab时手柄震动
- HapticsForTrigger:在各种操作触发触发器需要修改set值时手柄震动
Teleport/传送
- Teleport Mode:配置不同的传送交互类型
- TeleportFree:传送到射线所指定的位置,但是在Z轴normal值小于30度的情况下不允许传送
- TeleportAreaSelect:激活Teleport出现网格,触发手柄上的select操作进行传送,传送后网格消失
- TeleportAreaActive:手柄指向地面时出现网格,激活Teleport即传送
- Nav Mesh:在寻路网格可达的范围内进行传送,交互与TeleportFree类似,但是取消了30度的角度判断
- Teleport Search Type:用于传送的射线类型
- Straight:直线射线指向
- Projectile:抛物线射线指向
- Teleport Time:传送所花费的总时间
- Use Teleport Motion:设定传送的过渡类型
- None:没有过渡态,直接传送到目标点
- Indication:location具有过渡态,逐步过渡到目标点
- FullMotion:location与rotation都具有过渡态,位置过渡到目标点且角色的rotation过渡到目标rotation
在配置按键映射的DataAssets内相关的teleport选项
- Teleport Execute:传送触发的条件
- OnTrigger:trigger按键按到底触发
- OnThumbstickRelease:thumbstick按下并松开时(通过thumbstick来转向时比较好用)
- OnTeleportButtonRelease:上面注册的Teleport按钮被按下并松开时
- Teleport Deactivate:传送取消的条件
- OnlyExecute:只允许被触发不允许取消
- NeutralThumbstick:trackpad中键取消传送
- TeleportRotationType:传送时的面向操控
- None:无法转向
- RotateByThumbstick:通过trackpad来操作传送时的方向
- RotateByHand:通过手的转向来操作传送时的方向
Movement/移动
- Movement Style:移动的方式
- Fluent:在地面上以固定速度移动
- StopMotion:当walk的按钮按下后产生一个ghost,然后移动固定秒后瞬移到ghost的位置,若提前松开则取消移动,最多两秒
- Ghost:当walk的按钮按下后产生一个ghost,松开则瞬移到ghost的位置
- Ghost Behaviour:Ghost的位移操作形式配置
- Direction:通过手柄的方向来操作ghost移动的方向与速度
- MovementRelativeToOrigin:通过按下walk按钮时的初始位置来计算后续的相对偏移量来操作ghost的移动,非常难用
- PositionRelativeToOrigin:按下按钮后通过手柄的移动距离来移动ghost的位置,通过手柄的方向来改变ghost的朝向
- Stop Motion Interval:用于配置StopMotion的固定秒数时间,大于2秒也会被设置成2秒
- Movement Speed:ghost移动的速度
- Move with Fade:瞬移时是否允许黑屏过渡
Rotation
RotateDerees
:对应按键映射中的TurnLeft
和TurnRight
的转向角度
按键映射补充
- Trigger Grip:是否允许长按select的按钮来触发grip(默认是0.4秒,可以通过BP_MCmop_Laser中的Handle Trigger来修改长按间隔)
- Holding Grip:是否允许按下按钮抓取,松开按钮放开,或者为单次按下抓取,抓取状态下再次按下松开
关于按键的自定义
按键的起始点在Pawn内,如下截图
调用到BP_MotionController
内的方法KeyInput
,然后遍历调用所有BP_MComp
组件的方法FunctionInput
,然后继承自BP_MComp
的组件各自实现/重写按键对应的逻辑,一般可以用一个Switch来做
如需要添加自定义按键事件,修改枚举Enum_Control_Function
即可
Comp_RadialMenu
如图所示,框架默认给予2个RadialMenu事件,分别可以设置2个面板,
Comp_UI_Display
如图所示,手部内测的面板,通过设置该组件配置
Comp_UI_Pallet
一个次级面板,在RadiaMenu面板中如果指定了对应的类就可以触发此面板,比如指定了BP_Radial_Button_Pallet_Bookmarks
,在此组件内就可以配置相应的Bookmarks内容
此组件又非常多的功能,之后逐步细讲
DataAssets
首先要回顾一下Info_Level
类中有一个ActorInfos
列表,需要在里面添加所有需要用到的表
如上图所示,我们需要的DA_Test资源需要从PDA_ActorList
创建,此类是继承自PrimaryDataAsset
的蓝图类,内部就是创建了一系列变量;然后把这个资源放入Info_Level
内
然后我们右键自建蓝图类,CreateDatatableEntry
,主要选择标签和ActorList
,创建后自动生成基础的DataAsset,即可添加到之前创建的DA_Test
内
然后我们以Map_Demo_UI
关卡做测试,找到类BP_Pallet_ObjectDropper
的Comp_UI_Pallet
组件,配置如下
运行以后就得到如下结果
Comp_Select
关于选择的组件,比较基础也比较有用
对于VR玩家,默认使用Trigger
按键类出发select功能
- SelectMenu
对应上面的变量SelctionTypes
,点击以后就自动生成按钮
- Custom
自定义PressSelect
事件就可以
- Trigger
需要配合组件Comp_ListenToTrigger
使用,点击以后Comp_ListenToTrigger::TriggerSingle
能执行
可以设置ActorsToTrigger
变量来让其他类收到Listen消息
设置ComponentTagToSearch
可以对类内的不同模型进行筛选
- window
详情请看后面的Comp_WindowObject
- visual
配置请看后面的Comp_Visual
,区别于SelectMenu+VisualChange
的组合,如果直接指定Visual
则是直接切换,无菜单提示
- light
配合光源和Light组件一起使用
- open
详情请看后面的Comp_Open
- active
详情请看后面的Comp_Active
Comp_Drag
拖拽组件,可以平移和旋转拖拽,一般需要配合Latch
组件来使用
变量解释
- identifier:组件之间交互的名称,如果类内有多个Drag组件就需要区分不同的名称
DragType:拖动规则
- key:具体规则,如变量名称描述的含义
- value
- SnapType:吸附规则
- FreeMovement:自由移动
- SnapToSegment:吸附到最近的分段,即下面的sectionSet分段
- Reset:重置为初始状态
- StartPositon:初始位置
- SnappingSpeed:吸附速度
- FollowSpeed:跟随速度
- SelectRule:取值方式,3个选项以此为四舍五入,ceil,floor
- Toggle:强制设定每次到达极值时的set变量为上次的反向值
- SnapType:吸附规则
- SectionSet:分段极值设定
- TriggerAlsoOwner:通知owner
- ComponentTagToSearch:需要拖动的mesh需要加上该标签
使用流程
在被拖动的模型中加入插槽
AttachPoint
来指定交互点确保交互模型可以被射线检测到
挂上
Comp_Drag
,然后需要配合Comp_Latch
使用,该组件可以使用默认值在交互模型中加入以上2个组件所需要的tag,默认为
Drag,Latch
可以配合
Comp_ListenToServer
使用,可以监听每次到达极值的时间,或者Tick的Drag事件同一个类下可以挂载多个Drag组件来拖动不同的模型组件,记得修改
identifier
和ComponentTagToSearch
,也需要修改Latch组件的ComponentTagToSearch
变量,也不要忘记区分不同模型的Tag如果是spline模式,在类内创建一个spline组件
Comp_Grab
抓取组件,对于VR玩家可以有射线和接触抓取两种方式,不过具体的功能会有区别,见下图
变量解释
- GrabType:是否在抓取后模拟物理,利用的是
PhysicsHandle
的抓取机制,一般的需要把模型放到root位置 - SnappingType:取消抓取后吸附的过程是否开启物理模拟
- ShouldKeepUpright:在抓取后是否保持世界坐标正前方
- ShouldAutoPickup:VR模式下手部接触以后直接抓取
- CanBePickedUp:是否可以被抓取
- SnapAndReplaceControlls:抓取之后替换手柄预设的所有操作,直到release为止,之后手柄的操作可以映射到组件的
KeyInput
和Thumbsticks
方法中 - RelativeControllerPostion:抓取以后的相对位置校准
- MaxDistanceToSocket:最大抓取距离(距离socket)
- SpawnTinyDisplay:抓取以后生成小提示面板,需要用DA去配置,同样的只支持VR射线模式
- GrabAttachedActorInstead:决定是否抓取此物体时替换掉之前抓取的物体
- GrabTag:默认抓取类中的所有物体,可以用此标签区分
Comp_Connector_Surface
配合Grab组件一起使用,目的是
- RotateOnSurface:决定如何旋转,是否垂直墙面等
- ShouldBeActiveEvenIfPhysics:如果开启物理模拟,就需要开启此选项,防止过多碰撞挤压
- AllowPhysicsAfterSnap:此物体吸附到平面后是否启用物理模拟
- RequiresTag:对吸附的平面做tag判断
- SearchDistance:吸附距离
需要特别注意的是,需要设置此组件的朝向问题,默认是X轴检测
Comp_Anchor/Comp_Connect_Anchor
特殊的吸附配套组件,后者吸附到前者
Comp_Anchor
- Anchor_Deactived:关闭吸附效果
- Allow_Physics:吸附后启用物理
- Connect_IDs_To_Allow:需要与配套组件对应的ID
- Connect_Should_Attach:子物体吸附到此物体后是否attach
- Sphere_Radius:查找半径
- Attached_Lock:吸附以后锁定,即不能再交互
Comp_Connect_Anchor
- Tag Of Component to Attach:初始时此Actor attach到Attached to Actor下的带有此tag的component中
- Attached to Socket:当Attached to Actor不为None,且Tag Of Component to Attach也不为None时,该actor会attach到Tag Of Component to Attach所提到的component中的名为Attached to Socket的socket中,若Attached to Actor不为None,但是Tag Of Component to Attach为None时,此Actor会attach根组件下名为Attached to Socket的socket上,若没有则会attach到Attached to Actor的根组件下
- ConnectorID:与配套组件对应的ID
- SearchRadius: 查找半径
- SearchDistance:判断连接时的球形射线距离组件原点的距离
最终效果
Comp_Latch
在之前Grab组件中已经有用到,主要作用是用来锁定位置
主要设置就是LatchType
- Drag:对应的是就是抓取
- SimpleLatch:简单的吸附,不需要配合Grab组件,手靠近以后就自动吸附,针对VR玩家
- Climb:攀爬模式,可以用于攀岩类操作,针对VR玩家
Comp_ListenToServer
除了上面的Grab用到的功能,该组件还有其他作用
Light
设置为Light
模式,需要配合组件Comp_Light
使用,在类内需要至少给一个光源,对于多个光源可以一起被作用
测试类如下图
如果使用
ListenToSingle
模式那么对于Drag过来的事件按50%作为临界点开关
Active
详情请看后面的Comp_Active
Open
详情请看后面的Comp_Open
Window
详情请看后面的Comp_WindowObject
Comp_Active
配合其他可以发送active事件的组件使用,如select.
该组件可以收到事件ActiveStateChanged
和ConstActiveStateChanged
,自行实现后续的逻辑
Comp_Open
配合其他可以发送Open事件的组件,比如Select或者Drag组件,来实现移动、旋转或者缩放的操作
需要将key的值对应到交互模型的tag
可以通过Listen组件来监听其他类的事件发送
Comp_Overlap
一般搭配Drag组件来模拟按钮效果
如上图所示,绿色模型开启overlap效果,增加tag成员overlap
,drag
抓取其他物件对绿色部分overlap以后就能模拟按钮效果
Comp_WindowObject
主要是文字和图片说明的窗口,可以配合Select组件展示
需要注意的是挂载此组件的类必须注册到PDA_Actor_List
里面,否则框架没有判空直接报错,具体请看之前Info_level
以及Data_Assets
内容
Comp_Visual
改变外观的组件,一般配合select组件的selectionMenu
模式,然后在SelectionTypes
内添加VisualChange
成员来实现
- CurrentMaterials:当前的材质类型,对应下面的配置
- PossibleMaterials:材质配置表
- Key:材质效果的键
- ButtonMaterial/ButtonTexture:按钮的材质和图片
- MeshInfo:替换模型的效果
- Key:需要把这个值填写到模型的Tag中去
- Value:替换模型的模型和材质效果
Comp_Widget
封装了UMG的组件,同时方便于交互
- UsesRecursiveWidgetSelection:使用框架自定义的UMG交互,如
Widget_Button
- ‘UsesWidgetInteractionComponent:使用默认UMG空间交互,如
Button
UsesRecursiveWidgetSelection
我们创建一个继承自Widget_Pallet_Button_Normal
的UMG,直接放入测试UMG中,监听Pressed
事件即可得到点击事件
UsesWidgetInteractionComponent
创建一个Button,点击交互后就可以走到Pressed
事件
要注意
Clicked
事件无效
框架UMG类
待补充….
Map
小地图的使用,分为2D和3D两种模式
在需要显示在地图上的类中需要挂载组件Comp_MapLocation
设置Icon及尺寸,以及是否支持旋转缩放等选项
SubstitueMaterial可以用来覆盖原有材质,让物体在小地图里显示特殊材质
然后需要若干个地面,用框架中的类BP_Floorplanmeasurement
指定贴图,该贴图最好跟场景的样貌匹配,给定FloorName,用于显示在面板上的名称
2DMap
使用类BP_2DMap
,直接拖入场景中即可,记得设置InitTab
3DMap
使用类BP_3DMap
,拖入场景中以后需要给与变量Measurement
一个地面单位即可显示3D地形;
或者直接使用Comp_Child_3DMap
也是一样的,记得把地形的缩放拷贝到这个类的缩放中,不然会导致变形
ubuntu修改静态IP
第一步我们需要将虚拟机的网络模式修改为桥接模式
,并且勾选复制物理网络连接状态
选项
ubuntu在20.4版本以后修改IP的方法与之前的有了比较大的区别,在之前的版本是通过编辑/etc/network/interfaces
文件去修改IP,在新版本以后修改的配置文件是
/etc/netplan/01-cloud-init.yaml
,这个01
可能每个人不相同,网上有些人是00,我这边是01
所以,我们用用sudo vim /etc/netplan/01-network-manager-all.yaml
打开,输入以下指令
1 | # Let NetworkManager manage all devices on this system |
上面的222是你自己设定的IP地址,前面需要参考你真是物理IP的情况
然后输入sudo netplan --debug apply
应用
然后通过ifconfig
查看IP
用GitLab搭建本地代码仓库
git 是一种基于命令的版本控制系统,全命令操作,没有可视化界面
gitlab 是一个基于git实现的在线代码仓库软件,提供web可视化管理界面,通常用于企业团队内部协作开发
当然还有github也可以用作代码仓库,较多用于个人
准备工作
安装git
git的使用和安装推荐本人另外一篇博客git使用指北
安装虚拟机
推荐下载VMwareWorkstation,可以到官网下载,然后百度搜索绿色方法
然后我们安装Linux镜像,本人使用的是Ubuntu20.4版本,这个网上一搜一大堆,随意选择版本,初学者可以选择桌面版本,如这里下载
然后一路安装,运行,设置root账户等
这里推荐虚拟机的使用2核,至少使用超过4GB的内存,本人直接上40GB
搭建基本环境
安装依赖项
如果是桌面版,右键进入终端,非桌面版忽略
1 | sudo apt update |
安装GitLab
1 | cd /tmp |
到tmp
目录安装gitlab,当然你可以自己选择目录
1 | sudo bash /tmp/script.deb.sh |
该脚本将设置您的服务器以使用GitLab维护的存储库。这使您可以使用与其他系统软件包相同的软件包管理工具来管理GitLab。完成后,您可以使用以下apt
命令安装实际的GitLab应用程序
1 | sudo apt install gitlab-ce |
此条命令将在系统上安装必要组件
如果在安装gitlab使有如下报错,可以今夏如下操作
1 | # apt-get install gitlab-ce |
修改安装脚本
1 | sudo vim /etc/apt/sources.list.d/gitlab_gitlab-ce.list |
OLD
1 | deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ focal main |
NEW
1 | deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main |
再次执行
1 | sudo apt update |
参考链接:https://gitlab.com/gitlab-org/gitlab-foss/-/issues/2370
修改防火墙
1 | sudo ufw status |
检查防火墙状态,如果没有防火墙需要apt安装
1 | sudo ufw allow http |
然后通过上述3条命令让防火墙支持http\https\OpenSSH
再次检查以后大概会有如下状态
1 | Status: active |
修改GitLab配置文件
1 | sudo vim /etc/gitlab/gitlab.rb |
该文件比较大,在顶部找到``external_url `,按照如下注释修改
1 | external_url 'https://example.com' // 此处修改为您的域名或ip地址,不用加端口 |
然后查找letsencrypt['contact_emails']
,如果没有先忽略跳过,如果有则进行如下修改
1 | letsencrypt['contact_emails'] = ['sammy@example.com'] // 此处修改为您的邮箱地址 |
然后重新配置GitLab
1 | sudo gitlab-ctl reconfigure // 每次修改配置文件都需要重新配置 |
本人在这一步遇到一个报错,最后一句是
Please upgrade your ACME client to a version that supports ACMEv2 / RFC
解决方法是在
/etc/gitlab/gitlab.rb
中添加letsencrypt['enable'] = false
重启
1 | sudo gitlab-ctl restart |
GitLab常用命令
常用命令 | 说明 |
---|---|
sudo gitlab-ctl reconfigure | 重新加载配置,每次修改/etc/gitlab/gitlab.rb 文件之后执行 |
sudo gitlab-ctl status | 查看 GitLab 状态 |
sudo gitlab-ctl start | 启动 GitLab |
sudo gitlab-ctl stop | 停止 GitLab |
sudo gitlab-ctl restart | 重启 GitLab |
sudo gitlab-ctl tail | 查看所有日志 |
sudo gitlab-ctl tail nginx/gitlab_acces.log | 查看 nginx 访问日志 |
sudo gitlab-ctl tail postgresql | 查看 postgresql 日志 |
systemctl enable gitlab-runsvdir.service | 开机启动 |
systemctl disable gitlab-runsvdir.service | 禁止开机自启动 |
首次登陆
在Web浏览器中访问GitLab服务器的域名:
1 | https://example.com // 您external_url配置的地址 |
第一次访问需要设置管理员密码,记得保存,之后如果没有gitlab账户就需要创建一个并登陆,然后就可以正常创建项目了
证书问题
有时候因为各种原因需要修改仓库端的虚拟机ip,也就意味着gitlab需要修改ip地址
这些操作很基础,在上面也讲了,但是修改IP以后本人发现原有的证书就无效了,应该是第一次部署的时候默认会生成我们IP对应的证书,我这边的第一次部署的ip地址是192.168.1.111
如下图
后面因为网段改为了2,所以整体把虚拟机和gitlab地址都改成了2段,然后运行gitlab就发现如下日志
直接告诉我招不到证书文件,于是翻遍资料最后找到一个粗暴的方法,手动生成证书文件
- 创建私钥和证书请求
1 | openssl req -nodes -newkey rsa:2048 -keyout 192.168.2.110.key -out 192.168.2.110.csr |
上面的ip地址需要替换你对应的地址
私钥会提示输入密码,记住了跟着提示输入
输入以后跟着提示输入你的信息,我这边老老实实的输入了正确信息,不确定可否随意输入
1 | Country Name (2 letter code) [XX]:CN |
- 移除私钥的密码短语
1 | cp -v /etc/gitlab/ssl/192.168.2.110.{key,original} |
- 创建证书
1 | openssl x509 -req -days 1460 -in /etc/gitlab/ssl/192.168.2.110.csr -signkey /etc/gitlab/ssl/192.168.2.110.key -out /etc/gitlab/ssl/192.168.2.110.crt |
- 设置权限
1 | chmod 600 /etc/gitlab/ssl/192.168.2.110.* |
有文章提示可以删除
.csr
文件,我这里没删除也没遇到问题如果需要删除,运行
rm -v /etc/gitlab/ssl/192.168.2.110.csr
然后重新配置gitlab,重启gitlab就可以了
仓库操作
创建仓库流程于github非常类似而且非常简单
然后我们需要为项目添加成员,如下图输入其他成员的邮箱或者用户名,添加以后其他用户自己登陆后也能看到项目了
完成仓库操作以后可以添加SSH Key来允许用户提交
然后我们可以在windows端clone下来,就可以正常git流程操作了
Json文件读写
本文主要介绍UE4下的JSON文件的读写
文件读写
首先我们需要扩展普通的文件读写方法
1 | bool UTestLib::V_LoadStringFromFile(const FString& FileName, const FString& RelativePath, FString& result) |
此方法是以工程目录下的Content路径为基础的,如果需要指定其他路径就需要替换 FPaths::ProjectContentDir()
方法或者干脆直接把整个路径给公开
简单的自定义视图和节点
本文描述了如何简单的创建自定义的视图和节点
后续补充具体功能开发
插件模块
创建插件TestGraph
- 包含如下模块
1 | "Projects", |
在模块类里声明2个变量
1 | class UEdGraph* GraphObj;//图表实例的类型,定义了该图表的行为(例如保存图表 |
构造GraphObj
,指定GraphObj
内的Schema
创建GraphEdSlate
,同时把GraphObj
指定给GraphEdSlate
1 | GraphObj=NewObject<UEdGraph>(); |
在返回中添加次Slate
类
1 | return SNew(SDockTab) |
Schema
EdGraphSchema
里定义了图表操作的大部分全局行为- 在
FBlueprintEditorUtils::CreateNewGraph
中将UEdGraph
和EdGraphSchema
建立映射 FEdGraphSchemaAction
类主要执行了一个PerformAction
,用于生成UEdGraphNode
FEdGraphSchemaAction
- PerformAction
生成节点
1 | UEdGraphNode* FTestGraphSchemaAction::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /* = true */) |
UEdGraphSchema
- GetGraphContextActions
右键点击空白页面,用于创建选择节点
1 | void UTestGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const |
如上图,显示3个对应的名称
- GetContextMenuActions
右键点击节点后的效果,产生例如BreakLink
之类的效果
此方法在4.24版本(不确定之前哪个版本开始)有更改如下
1
2 virtual void GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context)const//新版本
void GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, class FMenuBuilder* MenuBuilder, bool bIsDebugging) const; //旧版本
1 | void UTestGraphSchema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const |
连接方式
1 | UENUM() |
重写CanCreateConnection
设置节点的连接方式
1 | virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const |
- 创建连接方式
使用自定义的类FTestConnectionDrawingPolicy
来定义连接方式
1 | class FConnectionDrawingPolicy* UTestGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const |
自定义节点
UEdGraphNode
EdGraphNode
是图表节点实例的类型,定义了节点的行为AutowireNewNode
定义了节点的自动连接行为( 参考上述在schema创建的时候调用)
class UTestNode_Hello :public UEdGraphNode
重写2个方法
AllocateDefaultPins
:用于创建节点
GetNodeTitle
:标题
可以参考系统的节点
目录大多在
\Engine\Source\Editor\GraphEditor\Public\KismetPins\
1 | void UTestNode_Hello::AllocateDefaultPins() |
SGraphNode
此类是用于显示具体效果的
1 | void Construct(const FArguments& InArgs, UEdGraphNode* InNode);//构造跟默认slate不一样 |
- 构造
1 | GraphNode= InNode;//自带的变量,存储对应的UEdGraphNode |
- 刷新节点
1 | void STestNode::UpdateGraphNode() |
- 创建Pin的图形
2种方式,可以手动指定Pin,也可以使用系统的Pin
1 | //手动指定所有Pin |
1 | //使用系统的Pin |
- AddPin
用来设置Pin的显示和布局
如果不重写,就使用系统自带的Pin(非常的小)
1 | void STestNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd) |
自定义连线
继承自FConnectionDrawingPolicy
- 构造函数
1 | //.h |
- 设置样式
1 | void FTestConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) |
- 绘制连接
如果不重写就使用默认的贝塞尔曲线
1 | //绘制直线+流动的泡泡 |
工厂类
可以用工厂类来注册Node,Pin,ConnectionPolicy
重写如下方法
1 | virtual TSharedPtr<class SGraphNode> CreateNode(class UEdGraphNode* Node) const { return NULL; } |
1 | TSharedPtr<class SGraphNode> FTestNodeFactory::CreateNode(class UEdGraphNode* Node) const |
然后在模块加载的时候注册,这样就可以去掉在schema内的内容
1 | FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FTestNodeFactory)); |
1 | //virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj)const override; |
UE4子系统
UE4在4.22版本后加入了子系统即Subsystem,子系统是生命周期受控的自动实例化类,非常易用。
概述
目前在4.24版本中,子系统主要分为4大类
- 引擎
UEngineSubsystem
当引擎子系统的模块加载时,子系统将在模块的 Startup()
函数返回后执行 Initialize()
,子系统将在模块的 Shutdown()
函数返回后执行 Deinitialize()
。
通过GEngine
访问
1 | UMyEngineSubsystem MySubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>(); |
- 编辑器
UEditorSubsystem
当编辑器子系统的模块加载时,子系统将在模块的 Startup()
函数返回后执行 Initialize()
,子系统将在模块的 Shutdown()
函数返回后执行 Deinitialize()
。
这些子系统可通过GEditor
访问
1 | UMyEditorSubsystem MySubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>(); |
- 游戏实例
UGameInstanceSubsystem
生命周期跟随游戏实例的初始化和卸载
可通过UGameInstance
访问
1 | UGameInstance* GameInstance = ...; |
- 本地玩家
ULocalPlayerSubsystem
随着本地玩家创建而创建
通过ULocalPlayer
访问
1 | ULocalPlayer* LocalPlayer = ...; |
示例
创建UMyGameInstanceSubsystem
继承自UGameInstanceSubsystem
重写基类方法,如下图所示
1 | //返回false就不会自动创建 |
1 | void UMyGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection) |
另外我们手动创建一个继承自UGameInstance
的蓝图类
然后我们在子系统里实现一个测试方法
1 | UFUNCTION(BlueprintCallable) |
1 | void UMyGameInstanceSubsystem::TestPrint() |
在蓝图里默认直接可以得到子系统如下图
在关卡蓝图里测试
得到结果
插件:批量重命名
DX学习笔记(五):绘制几何体2
帧资源
上一篇在绘制每一帧都会进行一次CPU与GPU的同步,目的是
- GPU未结束命令分配器中所有命令执行之前,不能重置,如果重置,那么GPU当前还未处理的命令就会被清除
- 对于常量缓冲区的数据而言,不同步的话会导致数据异常
所以我们用FlushCommandQueue
来保持同步
但是有2个缺点导致性能浪费
- 每一帧起始阶段GPU无任务,处于闲置状态
- 帧结尾CPU处于闲置状态等待GPU完成绘制
帧资源
CPU会比GPU提前两帧工作,以保持GPU可持续工作
1 | //存有CPU为构建美珍命令列表所需的资源 |
1 |
|
然后我们的应用程序类将用一个vector容器实例化3个帧资源对象
1 | const int gNumFrameResources = 3; |
然后在CPU处理第n帧的算法是如下
1 | void ShapesApp::Update(const GameTimer& gt) |
1 | void ShapesApp::Draw(const GameTimer& gt) |
这种处理无法完全避免等待的发生
如果GPU速度远大于CPU,,则GPU会处于空闲
反之,CPU会空闲,但是CPU空闲是有利的,因为CPU还需要处理其他算法
采用多帧资源的方法目的是让命令队列一直处于非空状态,即GPU一直有事情可以做
渲染项
单次绘制调用过程中,需要向渲染流水线提交的数据集成为渲染项
我们用一个轻量级的结构体来存储绘制物体所需的数据
1 | struct RenderItem |
渲染过程中所用到的常量数据
- 扩展常量缓冲区
1 | //着色器 |
1 | //与之对应的常量缓冲区数据结构 |
- 刷新常量缓冲区
1 | void ShapesApp::UpdateObjectCBs(const GameTimer& gt) |
1 | void ShapesApp::UpdateMainPassCB(const GameTimer& gt) |
- 着色器改变
1 | VertexOut VS(VertexIn vin) |
- 调整根签名
1 | void ShapesApp::BuildRootSignature() |
不同形状的几何体
GeometryGenerator类
1 | class GeometryGenerator |
细分
1 | void GeometryGenerator::Subdivide(MeshData& meshData) |
中间点的生成
1 | Vertex GeometryGenerator::MidPoint(const Vertex& v0, const Vertex& v1) |
盒子
1 | GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth, uint32 numSubdivisions) |
球体
1 | GeometryGenerator::MeshData GeometryGenerator::CreateSphere(float radius, uint32 sliceCount, uint32 stackCount) |
柱体网格
1 | GeometryGenerator::MeshData GeometryGenerator::CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount) |
每一个环最后一个顶点与第一个顶点是重合的,但是纹理坐标不同,只有这样才能保证绘制正确纹理
构建顶部和底部
1 | void GeometryGenerator::BuildCylinderTopCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData) |
几何球体
1 | GeometryGenerator::MeshData GeometryGenerator::CreateGeosphere(float radius, uint32 numSubdivisions) |
绘制几何体示例
我们已经创建一个由FrameResource类型元素所构成的向量,每个FrameReousrce中都有上传缓冲区,用于为场景中每一个渲染项存储渲染过程常量和物体常量数据。
如果有3个帧资源和n个渲染项,那么我们需要创建3n个物体常量缓冲区(object constant buffer)以及3个渲染过程常量缓冲区(pass 从constant buffer),因此我们需要创建3(n+1)个常量缓冲区视图。所以我们要修改CBV堆以容纳额外的描述符
1 | #include "../Common/UploadBuffer.h" |
根签名
根参数
1 | typedef struct D3D12_ROOT_PARAMETER |
1 | typedef |
- 描述符表:引用的是描述符堆种的一块连续范围,用于确定要绑定的资源.每个描述符表占用1DWORD
- 跟描述符:通过直接设置跟描述符即可指示要绑定的资源,而且无需将它存于描述符中,但是只有常量缓冲区
CBV
,以及缓冲区的SRV/UAV
才可以根据描述符的身份进行绑定.而纹理SRV
不可以.每个描述符占用2DWORD - 根常量:借助根常量直接绑定一系列的32位的常量值.每个常量32位,占用1个DWORD
描述符表
1 | typedef struct D3D12_DESCRIPTOR_RANGE |
CD3D12_DESCRIPTOR_RANGE
是封装了一些便捷方法,通过Init
方法初始化
如果NumDescriptors
设置为3,BaseShaderRegister
设置为1,类型为CBV,那么对应的HLSL就是
1 | cbuffer cbA : register(b1){};//从1开始 |
- 示例
1 | CD3DX12_DESCRIPTOR_RANGE descRange[3]; |
根描述符
填写D3D12_ROOT_PARAMETER
中的Descriptor
,可以定义为根描述符
1 | typedef struct D3D12_ROOT_DESCRIPTOR |
- ShaderRegister:如果设置为2,类型为CBV,那么对应的常量缓冲区是
register(b2)
- RegisterSpace:空间
与描述符表需要在描述符堆中设置对应的描述符句柄不同,根描述符只需要直接绑定资源的虚拟地址就可以
示例
1 | UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants)); |
根常量
1 | typedef struct D3D12_ROOT_CONSTANTS |
- 示例
1 | CD3DX12_ROOT_PARAMETER slotRootParameter[1]; |
- HLSL代码
1 | cbuffer cbSetting : register(b0) |
与根描述符一i杨,根常量无需涉及描述符堆
根签名复杂示例
- 着色器
1 | Texture2D gDiffuseMap : register(t0); |
- 根签名
1 | CD3DX12_DESCRIPTOR_RANGE texTable; |
陆地与波浪演示程序
栅格坐标
构建一个
m*n
个顶点组成的栅格,意味着具有(m-1)*(n-1)
个四边形,即2倍的三角形如果宽度为
w
,深度为d
,那么x和y轴方向上的间距分别为dx=w/(n-1)
和dz=d/(m-1)
那么第
i
行,第j
列的最坐标为Vij=[-0.5w+j*dx, 0.0, 0.5d-i*dz]
1 | uint32 vertexCount = m*n; |
栅格索引
三角形ABC的索引(i*n+j, i*n+j+1, (i+1)*n+j)
三角形CBD的索引((i+1)*n+j, i*n+j+1, (i+1)*n+j+1)
1 | meshData.Indices32.resize(faceCount*3); // 3 indices per face |
构建山体
1 | void LandAndWavesApp::BuildWavesGeometryBuffers() |
动态顶点缓冲区
动态顶点缓冲区即有可以频繁修改其中顶点数据的资源
如我们可以得到随着时间流走根性三角形的顶点高度,将此数据创建为动态顶点缓冲区
另外比如执行复杂的物理模拟和碰撞检测的粒子系统也需要用到
完整代码
- waves
1 | #pragma once |
1 | #include "Waves.h" |
- LandAndWaves
1 | //*************************************************************************************** |
自定义资源
自定义资源
新建插件StateMachine
,然后手动拷贝一份StateMachineEditor
用于编辑器模块 ,把里面的所有名称改为后缀为Editor版本
然后在Editor
版本的Build.cs
的Private部分中加入如下模块 "UnrealEd", "AssetTools"
,Public部分加入 "StateMachine"
模块
资源类
创建随意UObject
资源,可以保持默认,本例中为UStateMachineAsset
行为类
创建FAssetTypeActions_StateMachine
类继承自FAssetTypeActions_Base
,此类可以接管资源双击打开编辑器的动作,这个类需要在模块启动时注册,稍微会讲
此类建议创建在Editor模块
代码如下
1 | #pragma once |
1 |
|
工厂类
此类最好创建在Editor模块
此类是让引擎认识我们创建的这个资源和自定义标签,似乎定义了以后引擎就自动会识别,即上述两个类的功能
不创建的话连对应的自定义的标签也无法显示
1 | #pragma once |
1 |
|
模块
用于注册自定义标签以及我们之前创建的FAssetTypeActions_StateMachine
类
1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. |
1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. |
然后我们在引擎Content
目录下右键点击就出现我们要的效果