最近对UE4商城AdvancedLocomotionSysemV4进行了一波初步分析,感觉这个项目已经包含了大多数情况能用得到的动画表现,从移动表现中比较容易出问题的脚步问题到IK、攀爬、翻滚、布娃娃以及动作叠加等模块都有讲到

打算参考这个项目的思路重新实现一边动画逻辑,顺便对其中重要的模块和知识点记录一下

这一篇先从基础的移动逻辑,包括转身、起跳等,再顺带看一下脚步IK的实现

AdvancedLocomotionSystem分析(二):攀爬系统

AdvancedLocomotionSystemV4分析(三):动作叠加

AdvancedLocomotionSystemV4分析(四):布娃娃和起身

AdvancedLocomotionSystemV4分析(五):镜头

阅读全文 »

此文讲解如何使用Git版本的小乌龟插件

由于git默认的黑窗口对于部分开发人员特别是美术人员比较不太友好,所以很多人会选择使用可视化的TortoiseGit(小乌龟)插件来协作开发

安装

首先必须先安装git,

Git链接

无脑下一步安装完毕,如需了解git如何使用,可以参考本人另外一篇文章

Git使用指北

然后下载小乌龟插件,可以随意搜索下载,或者使用如下地址下载

TortoiseGit

无脑下一步安装,成功以后右键菜单会添加小乌龟按钮

image-20200921102026347

使用方法

先来一张最简化的流程图,上下两部分分别用文字或者命令来解释流程

协作开发流程 (1)

如果有使用过SVN的同学,在这里要说明gitSVN在使用上会多一步操作,也就是git分布式特点的体现,

git会有本地仓库一个概念,SVNCommit意味着已经把本地修改推送到了远程服务器端,但是gitcommit只是提交到本地仓库,在本地仓库存储着几乎与远程服务器同步的信息,包括所有文件信息、提交日志、分支信息等,也就意味着git可以在没有网络的时候也同样可以维护着一个仓库内容,在需要的时候在同步到远程服务器,这一点对于人数比较多的团队协作开发是优于svn

环境

在正常使用小乌龟之前,需要先配置一下环境

如果使用GitHub仓库,需要到GitHub去注册一个账号,比较简单此步忽略

如果是本地仓库的协作开发的,用已经搭建好的本地仓库ip去注册(例如192.168.1.111),然后需要管理员授权以后就可以参与开发

关于如何搭建本地Git仓库

然后在开发之前,需要生成本地密钥,作为开发权限的一种判定

打开小乌龟安装目录,打开PuTTYgen

界面如下

image-20200921110129186

Generate来生成密钥,或者如果你已经用git生成过密钥,就可以使用Load选项来加载之前生成的私钥,记得勾选AllFile选项

然后进入如下界面

image-20200921110352117

SavePublicKeySavePrivateKey按钮来保存一份本地的密钥文件,我这里把私钥保存为了toPrivate.ppk文件

接下来进入下一步

打开软件Pageant,双击以后如果没有出现窗口记得在右下角找到一个图标,打开以后如下窗口

image-20200921110554461

点击AddKey添加刚才保存的密钥(toPrivate.ppk)

如果到这里之前的步骤提示你需要输入账户/密码的,这是git初始化的时候需要初始化账户,请参考我的git文档完成这一步

目前掌握的信息每次电脑启动时都需要手动添加key,此坑先留着,以后研究

至此,小乌龟的环境已经配置完毕

拉取测试

我在github上有一个测试仓库,我们直接用此仓库来进行测试

地址为git@github.com:VJien/Test.git

右键GitClone,输入URL,保持其他默认,点击OK

拉取成功

image-20200921111331388

修改/提交

我们来对test.txt文件做个修改,然后在新建要给文件 1.txt

test文件里面我们加入一些字符串,保存以后该文件变为红色感叹号

在该目录下右键小乌龟选择Dif,我们在同时用GitBase窗口来对比一下

看到如下提示,表示该文件被修改了

image-20200921112548067

可以右键单击一个文件来选择回退(Revert)或者提交(Commit),似乎不需要Add操作,对于新创建的文件1.txt会不一样,一般需要先AddCommit,对于用惯了黑窗口的我来说,这里还是有区别的

image-20200921113125132

image-20200921113134620

这里我们直接点击大窗口下面的Commit提交测试一番

image-20200921113351447

Message里输入本次提交的名称,记得输入容易查找的名称,万一以后翻看日志如果发现都是 一对0102之类的会很崩溃

点击Commit显示成功

image-20200921113532839

在该窗口上有Push按钮,不过一般我们需要先Pull合并远程仓库的内容(除非是单人开发)

image-20200921113633715

image-20200921113647229

无论是Pull还是Push都需要选择正确的分支,即上图的RemoteBranch,因为不同的分支是用于不同功能或者人员使用的,同时也需要比对本地的分支信息

拉取成功以后可以在窗口按钮里找到拉取信息差异对比等,这个自行测试

然后是提交,一般选好分支,其他保持默认选项

image-20200921113927421

image-20200921114022126

常用功能

先来一个所有功能的图

image-20200921114112053

add

标记需要提交的文件,在GitBase内是所有修改和新创建的都需要add的,在小乌龟里需要对新创建的进行add操作

Switch/Checkout/CreateBranch

image-20200921114312815

创建新的分支和切换分支,类似GitBasegit checkout -b 分支/git checkout 分支操作

  • Revert

回退操作,test文件修改以后即红色感叹号的形况下,点击Revert后见下图

image-20200921114553268

选中点击OK以后就将此文件回退到之前版本

Resolve

解决冲突,这个是新手非常头大的一个问题,也是经常会出现的问题

我在其他文件拉取了这个工程,然后对test文件做了修改,然后回到此文件下同时对test文件修改,这一操作就模拟了协作开发时的冲突问题

然后拉取以后就发生冲突了

image-20200921115129773

然后点击resolve选项进行处理

image-20200921135405643

第一个选项可以看冲突信息,当然不是所有文件都可以看到;同时也可以在下图所示的地方选择使用哪个版本的内容

image-20200921135028147

第3,4选项分别选择使用远程还是本地

解决完成后就再commit->pull->push操作,中间如果有警告出现可以忽略直接进行下一步

merge

在合并分支之前,首先要确保本地有需要合并的分支信息,比如master分支需要合并test分支,但是test分支是在远程电脑上创建的,本地还没有这个分支,那就先需要进行fetch操作

然后就可以找到所有分支了

使用merge操作就可以把test分支合并到本地

前言

我们在项目开发的过程中,经常需要在项目设置里面添加一些参数,本文介绍如何添加这些参数

另外介绍一些特殊的资源类参数的添加和使用

先上图

image-20200831114210083

添加自定义项目设置

一般情况下,我们需要创建一个插件来添加这类属性,我们创建一个蓝图函数库模板的插件

然后创建一个UObject类来添加属性

阅读全文 »

UE4 4.25离线文档,基于Dash文档源,使用Zeal查阅

image-20200828133618221

image-20200828133843396

Zeal下载

4.25文档下载

使用方法

直接将压缩版解压到Zeal\docsets\路径内,没有文件夹请自行新建

然后按照下图设置docsets路径

image-20200828134552604

下载其他文档

参考如下图方式下载,因为都是国外链接,所以请酌情使用交通工具✈

image-20200828155324943

虚幻商城有一个非常成熟而且有诚意的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来生成

image-20200820111916928

BP_MapInfo

放置在场景中的具体对象,主要就是为了配置Info_Level,在BP_GameInstance_Main类中直接通过方法GetCurrentLevelClass从此对象去要到Info_Level

Info_Level
Streaming Level

用于加载子关卡,比如当前关卡是TestMainMap,LevelsToLoad设置为TestMap01,那么在进入TestMainMap后会自动加载TestMap01

Transition

切换关卡的选项,首先设置时间变量,如果是0就是瞬间加载,可以设置天空球贴图和背景色

Panel

关卡面板的UI布局信息,可以参考demo演示的布局,见下图

image-20200820135730829

key

本地化文字有关的一个变量,此框架是用表格的方式设置本地化。

此变量作为DataTable的RowName读取表格

pawn

对应平台的pawn设置

Pallet

LevelImage变量决定关卡在Map面板下的显示图片,如下图

image-20200820140512683

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/传送
  • image-20200820142920984
  • 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/移动

image-20200820143452130

  • 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:对应按键映射中的TurnLeftTurnRight的转向角度

按键映射补充
  • Trigger Grip:是否允许长按select的按钮来触发grip(默认是0.4秒,可以通过BP_MCmop_Laser中的Handle Trigger来修改长按间隔)
  • Holding Grip:是否允许按下按钮抓取,松开按钮放开,或者为单次按下抓取,抓取状态下再次按下松开
关于按键的自定义

按键的起始点在Pawn内,如下截图

image-20200820145557962

调用到BP_MotionController内的方法KeyInput,然后遍历调用所有BP_MComp组件的方法FunctionInput,然后继承自BP_MComp的组件各自实现/重写按键对应的逻辑,一般可以用一个Switch来做

如需要添加自定义按键事件,修改枚举Enum_Control_Function即可

Comp_RadialMenu

image-20200820150244981

如图所示,框架默认给予2个RadialMenu事件,分别可以设置2个面板,

image-20200820150349447

Comp_UI_Display

image-20200820150423064

image-20200820150455057

如图所示,手部内测的面板,通过设置该组件配置

Comp_UI_Pallet

一个次级面板,在RadiaMenu面板中如果指定了对应的类就可以触发此面板,比如指定了BP_Radial_Button_Pallet_Bookmarks,在此组件内就可以配置相应的Bookmarks内容

此组件又非常多的功能,之后逐步细讲

DataAssets

首先要回顾一下Info_Level类中有一个ActorInfos列表,需要在里面添加所有需要用到的表

image-20200820164521985

如上图所示,我们需要的DA_Test资源需要从PDA_ActorList创建,此类是继承自PrimaryDataAsset的蓝图类,内部就是创建了一系列变量;然后把这个资源放入Info_Level

image-20200820165040854

然后我们右键自建蓝图类,CreateDatatableEntry,主要选择标签和ActorList,创建后自动生成基础的DataAsset,即可添加到之前创建的DA_Test

然后我们以Map_Demo_UI关卡做测试,找到类BP_Pallet_ObjectDropperComp_UI_Pallet组件,配置如下

image-20200820165341595

运行以后就得到如下结果

image-20200820165410975

Comp_Select

关于选择的组件,比较基础也比较有用

对于VR玩家,默认使用Trigger按键类出发select功能

image-20200820172012396

  • 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变量为上次的反向值
  • SectionSet:分段极值设定
  • TriggerAlsoOwner:通知owner
  • ComponentTagToSearch:需要拖动的mesh需要加上该标签
使用流程
  • 在被拖动的模型中加入插槽AttachPoint来指定交互点

  • 确保交互模型可以被射线检测到

  • 挂上Comp_Drag,然后需要配合Comp_Latch使用,该组件可以使用默认值

  • 在交互模型中加入以上2个组件所需要的tag,默认为Drag,Latch

  • 可以配合Comp_ListenToServer使用,可以监听每次到达极值的时间,或者Tick的Drag事件

  • 同一个类下可以挂载多个Drag组件来拖动不同的模型组件,记得修改identifierComponentTagToSearch,也需要修改Latch组件的ComponentTagToSearch变量,也不要忘记区分不同模型的Tag

  • 如果是spline模式,在类内创建一个spline组件


Comp_Grab

抓取组件,对于VR玩家可以有射线和接触抓取两种方式,不过具体的功能会有区别,见下图

image-20200821095808183

变量解释
  • GrabType:是否在抓取后模拟物理,利用的是PhysicsHandle的抓取机制,一般的需要把模型放到root位置
  • SnappingType:取消抓取后吸附的过程是否开启物理模拟
  • ShouldKeepUpright:在抓取后是否保持世界坐标正前方
  • ShouldAutoPickup:VR模式下手部接触以后直接抓取
  • CanBePickedUp:是否可以被抓取
  • SnapAndReplaceControlls:抓取之后替换手柄预设的所有操作,直到release为止,之后手柄的操作可以映射到组件的KeyInputThumbsticks方法中
  • 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

image-20200821170439723

  • Anchor_Deactived:关闭吸附效果
  • Allow_Physics:吸附后启用物理
  • Connect_IDs_To_Allow:需要与配套组件对应的ID
  • Connect_Should_Attach:子物体吸附到此物体后是否attach
  • Sphere_Radius:查找半径
  • Attached_Lock:吸附以后锁定,即不能再交互
Comp_Connect_Anchor

image-20200821170830375

  • 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:判断连接时的球形射线距离组件原点的距离

最终效果

image-20200821171243617

image-20200821171309985

Comp_Latch

在之前Grab组件中已经有用到,主要作用是用来锁定位置

主要设置就是LatchType

  • Drag:对应的是就是抓取
  • SimpleLatch:简单的吸附,不需要配合Grab组件,手靠近以后就自动吸附,针对VR玩家
  • Climb:攀爬模式,可以用于攀岩类操作,针对VR玩家

Comp_ListenToServer

除了上面的Grab用到的功能,该组件还有其他作用

Light

设置为Light模式,需要配合组件Comp_Light使用,在类内需要至少给一个光源,对于多个光源可以一起被作用

测试类如下图

image-20200821105432348

如果使用ListenToSingle模式那么对于Drag过来的事件按50%作为临界点开关

Active

详情请看后面的Comp_Active

Open

详情请看后面的Comp_Open

Window

详情请看后面的Comp_WindowObject

Comp_Active

配合其他可以发送active事件的组件使用,如select.

该组件可以收到事件ActiveStateChangedConstActiveStateChanged,自行实现后续的逻辑

Comp_Open

配合其他可以发送Open事件的组件,比如Select或者Drag组件,来实现移动、旋转或者缩放的操作

需要将key的值对应到交互模型的tag

可以通过Listen组件来监听其他类的事件发送

Comp_Overlap

一般搭配Drag组件来模拟按钮效果

image-20200821152007053

image-20200821152143566

如上图所示,绿色模型开启overlap效果,增加tag成员overlap,drag

抓取其他物件对绿色部分overlap以后就能模拟按钮效果

Comp_WindowObject

主要是文字和图片说明的窗口,可以配合Select组件展示

需要注意的是挂载此组件的类必须注册到PDA_Actor_List里面,否则框架没有判空直接报错,具体请看之前Info_level以及Data_Assets内容

image-20200821172837931

image-20200821172859847

Comp_Visual

改变外观的组件,一般配合select组件的selectionMenu模式,然后在SelectionTypes内添加VisualChange成员来实现

image-20200821154014085

  • CurrentMaterials:当前的材质类型,对应下面的配置
  • PossibleMaterials:材质配置表
    • Key:材质效果的键
    • ButtonMaterial/ButtonTexture:按钮的材质和图片
    • MeshInfo:替换模型的效果
      • Key:需要把这个值填写到模型的Tag中去
      • Value:替换模型的模型和材质效果

image-20200821154329493

Comp_Widget

封装了UMG的组件,同时方便于交互

image-20200821160827586

  • 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可以用来覆盖原有材质,让物体在小地图里显示特殊材质

image-20200821112246681

然后需要若干个地面,用框架中的类BP_Floorplanmeasurement

指定贴图,该贴图最好跟场景的样貌匹配,给定FloorName,用于显示在面板上的名称

2DMap

使用类BP_2DMap,直接拖入场景中即可,记得设置InitTab

3DMap

使用类BP_3DMap,拖入场景中以后需要给与变量Measurement一个地面单位即可显示3D地形;

或者直接使用Comp_Child_3DMap也是一样的,记得把地形的缩放拷贝到这个类的缩放中,不然会导致变形

第一步我们需要将虚拟机的网络模式修改为桥接模式,并且勾选复制物理网络连接状态选项

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
2
3
4
5
6
7
8
9
10
11
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
ethernets:
ens33:
dhcp4: false
addresses: [192.168.1.222/24]//ip地址,后面的24不需要改,更掩码有关系
nameservers:
addresses: [8.8.8.8,192.168.0.1]
~

上面的222是你自己设定的IP地址,前面需要参考你真是物理IP的情况

然后输入sudo netplan --debug apply应用

然后通过ifconfig查看IP

git 是一种基于命令的版本控制系统,全命令操作,没有可视化界面

gitlab 是一个基于git实现的在线代码仓库软件,提供web可视化管理界面,通常用于企业团队内部协作开发

当然还有github也可以用作代码仓库,较多用于个人

准备工作

安装git

git的使用和安装推荐本人另外一篇博客git使用指北

安装虚拟机

推荐下载VMwareWorkstation,可以到官网下载,然后百度搜索绿色方法

然后我们安装Linux镜像,本人使用的是Ubuntu20.4版本,这个网上一搜一大堆,随意选择版本,初学者可以选择桌面版本,如这里下载

然后一路安装,运行,设置root账户等

这里推荐虚拟机的使用2核,至少使用超过4GB的内存,本人直接上40GB

搭建基本环境

安装依赖项

如果是桌面版,右键进入终端,非桌面版忽略

1
2
sudo apt update
sudo apt install ca-certificates curl openssh-server postfix
安装GitLab
1
2
cd /tmp
curl -LO https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh

tmp目录安装gitlab,当然你可以自己选择目录

1
sudo bash /tmp/script.deb.sh

该脚本将设置您的服务器以使用GitLab维护的存储库。这使您可以使用与其他系统软件包相同的软件包管理工具来管理GitLab。完成后,您可以使用以下apt命令安装实际的GitLab应用程序

1
sudo apt install gitlab-ce

此条命令将在系统上安装必要组件

如果在安装gitlab使有如下报错,可以今夏如下操作

1
2
3
4
5
# apt-get install gitlab-ce
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package gitlab-ce

修改安装脚本

1
2
sudo vim /etc/apt/sources.list.d/gitlab_gitlab-ce.list
1

OLD

1
2
3
deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ focal main
deb-src https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ focal main
12

NEW

1
2
3
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main
deb-src https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main
12

再次执行

1
2
3
sudo apt update
sudo apt install gitlab-ce
12

参考链接:https://gitlab.com/gitlab-org/gitlab-foss/-/issues/2370

修改防火墙
1
sudo ufw status

检查防火墙状态,如果没有防火墙需要apt安装

1
2
3
sudo ufw allow http
sudo ufw allow https
sudo ufw allow OpenSSH

然后通过上述3条命令让防火墙支持http\https\OpenSSH

再次检查以后大概会有如下状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Status: active

To Action From
-- ------ ----
80 ALLOW Anywhere
22 ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
OpenSSH ALLOW Anywhere
80 (v6) ALLOW Anywhere (v6)
22 (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
OpenSSH (v6) ALLOW Anywhere (v6)
修改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如下图

image-20201021104015652

后面因为网段改为了2,所以整体把虚拟机和gitlab地址都改成了2段,然后运行gitlab就发现如下日志

image-20201021104134810

直接告诉我招不到证书文件,于是翻遍资料最后找到一个粗暴的方法,手动生成证书文件

  1. 创建私钥和证书请求
1
openssl req -nodes -newkey rsa:2048 -keyout 192.168.2.110.key -out 192.168.2.110.csr

上面的ip地址需要替换你对应的地址

私钥会提示输入密码,记住了跟着提示输入

输入以后跟着提示输入你的信息,我这边老老实实的输入了正确信息,不确定可否随意输入

1
2
3
4
5
6
7
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:zhejiang
Locality Name (eg, city) [Default City]:hangzhou
Organization Name (eg, company) [Default Company Ltd]:xiaoshan
Organizational Unit Name (eg, section) []:xiaoshan
Common Name (eg, your name or your server's hostname) []:xiaoshan
Email Address []:自己的邮箱地址
  1. 移除私钥的密码短语
1
2
3
4
cp -v /etc/gitlab/ssl/192.168.2.110.{key,original}
openssl rsa -in /etc/gitlab/ssl/192.168.2.110
-out /etc/gitlab/ssl/192.168.2.110.key
rm -v /etc/gitlab/ssl/192.168.2.110.original
  1. 创建证书
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. 设置权限
1
chmod 600 /etc/gitlab/ssl/192.168.2.110.*

有文章提示可以删除.csr文件,我这里没删除也没遇到问题

如果需要删除,运行rm -v /etc/gitlab/ssl/192.168.2.110.csr

然后重新配置gitlab,重启gitlab就可以了

仓库操作

创建仓库流程于github非常类似而且非常简单

image-20200818071803709

然后我们需要为项目添加成员,如下图输入其他成员的邮箱或者用户名,添加以后其他用户自己登陆后也能看到项目了

image-20200818073638776

完成仓库操作以后可以添加SSH Key来允许用户提交

image-20200818071718978

然后我们可以在windows端clone下来,就可以正常git流程操作了

image-20200818071856868

本文主要介绍UE4下的JSON文件的读写

文件读写

首先我们需要扩展普通的文件读写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool UTestLib::V_LoadStringFromFile(const FString& FileName, const FString& RelativePath, FString& result)
{
if (!FileName.IsEmpty())
{
FString file=FPaths::ProjectContentDir()+RelativePath+FileName;
return FFileHelper::LoadFileToString(result,*file);

}
return false;
}
bool UTestLib::V_SaveStringToFile(const FString& FileName, const FString& RelativePath, const FString& string)
{

if (!FileName.IsEmpty())
{
FString file = FPaths::ProjectContentDir() + RelativePath + FileName;
return FFileHelper::SaveStringToFile(string,*file);

}
return false;
}

此方法是以工程目录下的Content路径为基础的,如果需要指定其他路径就需要替换 FPaths::ProjectContentDir()方法或者干脆直接把整个路径给公开

阅读全文 »

本文描述了如何简单的创建自定义的视图和节点

后续补充具体功能开发

image-20200805140828530
插件模块

创建插件TestGraph

  • 包含如下模块
1
2
3
4
5
6
7
8
9
10
11
"Projects",
"InputCore",
"UnrealEd",
"LevelEditor",
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"ToolMenus",
"GraphEditor",
"EditorStyle"

在模块类里声明2个变量

1
2
class UEdGraph* GraphObj;//图表实例的类型,定义了该图表的行为(例如保存图表
TSharedPtr<class SGraphEditor> GraphEdSlate;//图表编辑器的Slate类型

构造GraphObj,指定GraphObj内的Schema

创建GraphEdSlate,同时把GraphObj指定给GraphEdSlate

1
2
3
4
5
GraphObj=NewObject<UEdGraph>();
GraphObj->AddToRoot();
GraphObj->Schema=UTestGraphSchema::StaticClass();//自定义的schema类,刚开始可以先使用默认的UGraphSchema即可显示基础图表
GraphEdSlate=SNew(SGraphEditor)
.GraphToEdit(GraphObj);

在返回中添加次Slate

1
2
3
4
5
6
7
8
9
10
11
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SNew(SBox)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
GraphEdSlate.ToSharedRef()//需要转成ref
]
];
Schema
  1. EdGraphSchema里定义了图表操作的大部分全局行为
  2. FBlueprintEditorUtils::CreateNewGraph中将UEdGraphEdGraphSchema建立映射
  3. FEdGraphSchemaAction类主要执行了一个PerformAction,用于生成UEdGraphNode
FEdGraphSchemaAction
  • PerformAction

生成节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
UEdGraphNode* FTestGraphSchemaAction::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /* = true */)
{
UEdGraphNode* resNode=nullptr;
if (NodeHello)
{
const FScopedTransaction Trans(LOCTEXT("FF","Hello:NewNode"));
ParentGraph->Modify();
if (FromPin!=nullptr)
{
FromPin->Modify();
}
NodeHello->Rename(TEXT("TEST"),ParentGraph);
ParentGraph->AddNode(NodeHello,true,bSelectNewNode);

NodeHello->CreateNewGuid();//唯一标识
NodeHello->PostPlacedNewNode();//初始化
NodeHello->AllocateDefaultPins();//针脚
NodeHello->AutowireNewNode(FromPin);//自动创建节点

NodeHello->NodePosX=Location.X;
NodeHello->NodePosY = Location.Y;

NodeHello->SetFlags(RF_Transactional);

resNode=NodeHello;
}

return resNode;

}
UEdGraphSchema
  • GetGraphContextActions

右键点击空白页面,用于创建选择节点

1
2
3
4
5
6
7
8
9
10
11
12
void UTestGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
TSharedPtr<FTestGraphSchemaAction> NewNodeAction(new
FTestGraphSchemaAction(LOCTEXT("11","GraphNode"),
LOCTEXT("Desc","TestNodeDesc"), //desc
LOCTEXT("NewGraphText","Add Hello Node"),//hover
0));

NewNodeAction->NodeHello=NewObject<UTestNode_Hello>(ContextMenuBuilder.OwnerOfTemporaries/*存储节点*/);
ContextMenuBuilder.AddAction(NewNodeAction);

}

image-20200805110906761

如上图,显示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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void UTestGraphSchema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const
{
if (Context->Pin)
{
{
FToolMenuSection& Section = Menu->AddSection("TestGraphSchemaNodeActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions ___"));
// Only display the 'Break Links' option if there is a link to break!
if (Context->Pin->LinkedTo.Num() > 0)
{
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakPinLinks);

// add sub menu for break link to
if (Context->Pin->LinkedTo.Num() > 1)
{
Section.AddSubMenu(
"BreakLinkTo",
LOCTEXT("BreakLinkTo", "Break Link To..."),
LOCTEXT("BreakSpecificLinks", "Break a specific link..."),
{}
);
}
}
}
}
else
if (Context->Node)
{
{
FToolMenuSection& Section = Menu->AddSection("TestGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);

Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
}
Super::GetContextMenuActions(Menu, Context);
}

image-20200805111804599

连接方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UENUM()
enum ECanCreateConnectionResponse
{
/** 多对多 */
CONNECT_RESPONSE_MAKE,

/*不能连接,默认 */
CONNECT_RESPONSE_DISALLOW,

/** 多对一 */
CONNECT_RESPONSE_BREAK_OTHERS_A,

/** 一对多 */
CONNECT_RESPONSE_BREAK_OTHERS_B,

/** 一对一 */
CONNECT_RESPONSE_BREAK_OTHERS_AB,

/** Make the connection via an intermediate cast node, or some other conversion node. */
CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE,

CONNECT_RESPONSE_MAX,
};

重写CanCreateConnection设置节点的连接方式

1
2
3
4
virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT("Not 2 by this schema"));
}
  • 创建连接方式

使用自定义的类FTestConnectionDrawingPolicy来定义连接方式

1
2
3
4
class FConnectionDrawingPolicy* UTestGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
return new FTestConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,InZoomFactor,InClippingRect,InDrawElements, InGraphObj);
}
自定义节点
UEdGraphNode
  1. EdGraphNode是图表节点实例的类型,定义了节点的行为
  2. AutowireNewNode定义了节点的自动连接行为( 参考上述在schema创建的时候调用)

class UTestNode_Hello :public UEdGraphNode

重写2个方法

AllocateDefaultPins:用于创建节点

GetNodeTitle:标题

可以参考系统的节点

目录大多在 \Engine\Source\Editor\GraphEditor\Public\KismetPins\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void UTestNode_Hello::AllocateDefaultPins()
{
CreatePin(EGPD_Input,"HelloNodeInput_Bool",FName(),TEXT("bool"));
CreatePin(EGPD_Input, "HelloNodeInput_Color", FName(), TEXT("color"));
CreatePin(EGPD_Input, "HelloNodeInput_Enum", FName(), TEXT("enum"));
CreatePin(EGPD_Input, "HelloNodeInput_Exec", FName(), TEXT("exec"));
CreatePin(EGPD_Input, "HelloNodeInput_NameList", FName(), TEXT("namelist"));
CreatePin(EGPD_Input, "HelloNodeInput_Object", FName(), TEXT("obj"));
CreatePin(EGPD_Input, "HelloNodeInput_String", FName(), TEXT("str"));
CreatePin(EGPD_Input, "HelloNodeInput_Vector4", FName(), TEXT("v4"));
CreatePin(EGPD_Input, "HelloNodeInput_Integer", FName(), TEXT("int"));
CreatePin(EGPD_Input, "HelloNodeInput_Vector2", FName(), TEXT("v2"));

CreatePin(EGPD_Output, "HelloNodeInput_1", FName(), TEXT("1"));
CreatePin(EGPD_Output, "HelloNodeInput_2", FName(), TEXT("2"));
CreatePin(EGPD_Output, "HelloNodeInput_3", FName(), TEXT("3"));
CreatePin(EGPD_Output, "HelloNodeInput_4", FName(), TEXT("4"));
CreatePin(EGPD_Output, "HelloNodeInput_5", FName(), TEXT("5"));
}

FText UTestNode_Hello::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return FText::FromString("Hello !");
}
SGraphNode

此类是用于显示具体效果的

1
2
3
4
5
6
void Construct(const FArguments& InArgs, UEdGraphNode* InNode);//构造跟默认slate不一样
virtual void UpdateGraphNode()override;//刷新节点
virtual void CreatePinWidgets()override;//创建pin的ui显示
virtual void AddPin( const TSharedRef<SGraphPin>& PinToAdd )override;//添加pin,不重写就使用默认的(一个圆点)
protected:
TSharedPtr<class SBox> PinBox;
  • 构造
1
2
3
GraphNode= InNode;//自带的变量,存储对应的UEdGraphNode
this->SetCursor(EMouseCursor::GrabHand);//鼠标样式
this->UpdateGraphNode();
  • 刷新节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void STestNode::UpdateGraphNode()
{
//清空所有的Pin和box
InputPins.Empty();
OutputPins.Empty();
RightNodeBox.Reset();
LeftNodeBox.Reset();
//从style得到icon
const FSlateBrush* nodeIcon=FEditorStyle::GetBrush(TEXT("Graph.StateNode.Icon"));

//用此方法创建一个视图
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew(PinBox,SBox)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SNew(SBorder)
.BorderBackgroundColor_Lambda([&]()
{
FSlateColor color(FLinearColor(1,1,1));
return color;
})
//注释代码可以手动设置图片
//.BorderImage_Lambda([&]()
// {
// const FVector2D iconSize(64.0,64.0);
// return FTestGraphStyle::GetImageBrush(TEXT("ButtonIcon_40x"),iconSize);
// })
[
//添加2个自带的NodeBox,注意顺序需要先左后右
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
SAssignNew(LeftNodeBox,SVerticalBox)
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
SAssignNew(RightNodeBox,SVerticalBox)
]

]

]


];

//设置顶层box尺寸
PinBox->SetWidthOverride(400);
PinBox->SetHeightOverride(600);
//创建PinWidget
CreatePinWidgets();
}
  • 创建Pin的图形

2种方式,可以手动指定Pin,也可以使用系统的Pin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//手动指定所有Pin
void STestNode::CreatePinWidgets()
{
UTestNode_Hello* helloNode=CastChecked<UTestNode_Hello>(GraphNode);
if (helloNode)
{
//全部的Pin都指定为STestPin
for (auto p : helloNode->Pins)
{
TSharedPtr<SGraphPin> pin = SNew(STestPin, p);
pin->SetIsEditable(IsEditable);//把Node的可编辑性赋予Pin
this->AddPin(pin.ToSharedRef());//添加pin
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//使用系统的Pin
//用一个宏快速定义
//注意宏里的newPin对应的是第15行创建的指针变量
//顺序对应的是UEdGraphNode中创建的Pin
#define RESET_PIN(STestPin,GraphPinObj,...)\
newPin = SNew(STestPin,GraphPinObj,__VA_ARGS__);\
newPin->SetIsEditable(IsEditable);\
this->AddPin(newPin.ToSharedRef());
void STestNode::CreatePinWidgets()
{
static TArray<TSharedPtr<FName>> nameList;
nameList.Add(MakeShareable(new FName("player")));
nameList.Add(MakeShareable(new FName("monster")));
nameList.Add(MakeShareable(new FName("ai")));

TSharedPtr<SGraphPin> newPin=nullptr;
RESET_PIN(SGraphPinBool,helloNode->Pins[0]);
RESET_PIN(SGraphPinColor, helloNode->Pins[1]);
RESET_PIN(SGraphPinEnum, helloNode->Pins[2]);
RESET_PIN(SGraphPinExec, helloNode->Pins[3]);
RESET_PIN(SGraphPinNameList, helloNode->Pins[4],nameList);
RESET_PIN(SGraphPinObject, helloNode->Pins[5]);
RESET_PIN(SGraphPinString, helloNode->Pins[6]);
RESET_PIN(SGraphPinVector4, helloNode->Pins[7]);
RESET_PIN(SGraphPinInteger, helloNode->Pins[8]);
RESET_PIN(SGraphPinVector2D, helloNode->Pins[9]);
RESET_PIN(SGraphPinClass, helloNode->Pins[10]);
RESET_PIN(STestPin, helloNode->Pins[11]);
RESET_PIN(STestPin, helloNode->Pins[12]);
RESET_PIN(STestPin, helloNode->Pins[13]);
RESET_PIN(STestPin, helloNode->Pins[14]);
}
  • AddPin

用来设置Pin的显示和布局

如果不重写,就使用系统自带的Pin(非常的小)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void STestNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
//设置owner
PinToAdd->SetOwner(SharedThis(this));
//得到对应的Obj
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
//设置可见性
if (PinObj&&PinObj->bAdvancedView)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
#if CUSTOM_PIN
//如果是自定义的Pin就设置缩放,用系统自带的就不需要了
PinToAdd->SetDesiredSizeScale(FVector2D(16, 16));
#endif

//判断是输入和是输出节点
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
//添加到左边盒子里
LeftNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.0f)
.Padding(20.0f, 0)
[
PinToAdd
];
//添加到InputPins数组里
InputPins.Add(PinToAdd);
}
else if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Output)
{
RightNodeBox->AddSlot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.FillHeight(1.0f)
.Padding(-30.0f, 0.f)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
自定义连线

继承自FConnectionDrawingPolicy

  • 构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//.h
FTestConnectionDrawingPolicy(int32 InBackLayerID, //线ID
int32 InFrontLayerID, //箭头ID
float InZoomFactor, //缩放
const FSlateRect& InClippingRect,//视口裁剪
FSlateWindowElementList& InDrawElements,//绘制元素
UEdGraph* InGraphObj);
/*
protected:
UEdGraph* EdGraphObj;
TMap<UEdGraph*, int32>EdNodeWidgetMap;*/

//cpp
FTestConnectionDrawingPolicy::FTestConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements , UEdGraph* InGraphObj)
:FConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,InZoomFactor,InClippingRect,InDrawElements),EdGraphObj(InGraphObj)
{

}
  • 设置样式
1
2
3
4
5
6
7
8
9
void FTestConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params)
{
Params.WireThickness=3;//宽度
Params.WireColor=FLinearColor::Green;//颜色
if (HoveredPins.Num()>0)
{
ApplyHoverDeemphasis(OutputPin,InputPin, Params.WireThickness = 3, Params.WireColor);//此方法显示拖动效果
}
}
  • 绘制连接

如果不重写就使用默认的贝塞尔曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//绘制直线+流动的泡泡
void FTestConnectionDrawingPolicy::DrawConnection(int32 LayerId, const FVector2D& Start, const FVector2D& End, const FConnectionParams& Params)
{
const FVector2D delta=End-Start;
const FVector2D DirDelta=delta.GetSafeNormal();
//变成直线
FSlateDrawElement::MakeDrawSpaceSpline(
DrawElementsList,
LayerId,
Start,DirDelta,
End,DirDelta,
Params.WireThickness,
ESlateDrawEffect::None,
Params.WireColor
);
//使用气泡
//底线一堆算法就是为了MakeBox
if (bUseBubble)
{
FInterpCurve<float> SplineCurve;
float splineLength=MakeSplineReparamTable(Start,DirDelta,End,DirDelta,SplineCurve);

const float BubbleSpacing=32*ZoomFactor;
const float BubbleSpeed=64*ZoomFactor;
const FVector2D BubbleSize=BubbleImage->ImageSize*ZoomFactor*0.1*Params.WireThickness;

float deltaTime=FPlatformTime::Seconds()-GStartTime;
const float BubbleOffset=FMath::Fmod(deltaTime*BubbleSpeed,BubbleSpacing);
const int32 num=FMath::CeilToInt(splineLength-BubbleSpacing);

for (int32 i=0;i<num;i++)
{
const float dis = float(i)*BubbleSpacing+BubbleOffset;
if (dis<splineLength)
{
const float alpha=SplineCurve.Eval(dis,0);
FVector2D bubblePos=FMath::CubicInterp(Start,DirDelta,End,DirDelta,alpha);
bubblePos-= BubbleSize*0.5;

FSlateDrawElement::MakeBox(DrawElementsList,LayerId,
FPaintGeometry(bubblePos,BubbleSize,ZoomFactor),
BubbleImage,
ESlateDrawEffect::None,
Params.WireColor
);
}
}
}
}

工厂类

可以用工厂类来注册Node,Pin,ConnectionPolicy

重写如下方法

1
2
3
virtual TSharedPtr<class SGraphNode> CreateNode(class UEdGraphNode* Node) const { return NULL; }
virtual TSharedPtr<class SGraphPin> CreatePin(class UEdGraphPin* Pin) const { return NULL; }
virtual class FConnectionDrawingPolicy* CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const { return nullptr; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
TSharedPtr<class SGraphNode> FTestNodeFactory::CreateNode(class UEdGraphNode* Node) const
{
if (UTestNode_Hello* nd=Cast<UTestNode_Hello>(Node))
{
return SNew(STestNode,nd);
}
return nullptr;
}
TSharedPtr<class SGraphPin> FTestNodePinFactory::CreatePin(class UEdGraphPin* Pin) const
{
if (UTestNode_Hello* nd=Cast<UTestNode_Hello>(Pin->GetOuter()))
{
return SNew(STestPin,Pin);
}
return nullptr;
}
class FConnectionDrawingPolicy* FTestConnectionFactory::CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
if (Schema->IsA(UTestGraphSchema::StaticClass()))
{
return new FTestConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,ZoomFactor,InClippingRect,InDrawElements, InGraphObj);
}
return nullptr;
}

然后在模块加载的时候注册,这样就可以去掉在schema内的内容

1
2
3
FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FTestNodeFactory));
FEdGraphUtilities::RegisterVisualPinConnectionFactory(MakeShareable(new FTestConnectionFactory));
FEdGraphUtilities::RegisterVisualPinFactory(MakeShareable(new FTestNodePinFactory));
1
//virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj)const override;

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
2
UGameInstance* GameInstance = ...;
UMyGameSubsystem* MySubsystem = GameInstance->GetSubsystem<UMyGameSubsystem>();
  • 本地玩家ULocalPlayerSubsystem

随着本地玩家创建而创建

通过ULocalPlayer访问

1
2
ULocalPlayer* LocalPlayer = ...;
UMyPlayerSubsystem * MySubsystem = LocalPlayer->GetSubsystem();

示例

创建UMyGameInstanceSubsystem继承自UGameInstanceSubsystem

重写基类方法,如下图所示

1
2
3
4
5
6
//返回false就不会自动创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
//初始化
virtual void Initialize(FSubsystemCollectionBase& Collection)override;
//卸载
virtual void Deinitialize()override;
1
2
3
4
5
6
7
8
void UMyGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp,Log,TEXT("MySubsystem Initialize!!!!"));
}
void UMyGameInstanceSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Log, TEXT("MySubsystem Deinitialize!!!!"));
}

另外我们手动创建一个继承自UGameInstance的蓝图类

image-20200803100004102

然后我们在子系统里实现一个测试方法

1
2
3
4
UFUNCTION(BlueprintCallable)
void TestPrint();
UPROPERTY(BlueprintReadWrite,EditAnywhere)
int32 Number=0;
1
2
3
4
void UMyGameInstanceSubsystem::TestPrint()
{
UE_LOG(LogTemp,Log,TEXT("Number= %d !!"),Number);
}

在蓝图里默认直接可以得到子系统如下图

image-20200803100155123

在关卡蓝图里测试

image-20200803100224677

得到结果

image-20200803100258176