Painting
Painting
鲸拓工作室视频教程已同步发布至B站:https://www.bilibili.com/video/BV1oY411r7mY
画作
首先完成常规的项目创建操作,将多余的物体全部删掉,然后新建复制默认的游戏模式和VRPawn,并将默认地图另存为PaintingMap
画点连线
创建一个Actor,命名为BP_Stroke,并在这个Stroke下创建两个“实例化静态网格体组件”
导入Cylinder和Sphere(拖入即可),然后创建一个M_Stroke材质备用
注意混合模式要选择半透明,这能保证在笔画堆叠的情况下不会出现闪烁的效果
接着在StrokeMeshes中选择Cylinder静态网格体和M_stroke材质;在JointMeshes中选择Sphere静态网格体和M_stroke材质。
创建PreviousCursorLocation和ControlPoints变量,以及Update函数
在PaintingPawn中调用Update函数,实现按下Trigger生成连续小球的功能
将Joint反转变换位置收缩为GetNextJointTransform函数
创建一个GetNextSegmentTransform函数,用于计算每一帧的点之间的长度和旋转
在Update中更新StrokeMeshes和JointMeshes,即可实现画点连线
为了存档,需要将每次的位置记录在Control Points中
接着改造环境,进入封闭的场景,发现初始的第一个点始终在原点,解决方案如下
保存画作
在PaintingPawn中创建一个Painting变量,用于保存本画作的名字
创建一个Stroke结构,用于存放ControlPoints数组
创建一个SaveGame类,命名为BP_PainterSave
创建一个Strokes变量,类型为Stroke结构的数组
接着再创建Save和Load两个函数,用于存储数据
在PaintingPawn中初始化游戏存档对象
随后创建Save和Load两个操作映射,用于发起存档和读档操作
实现存档操作
当按下对应操作按钮的时候,就会把我们当前的画作存储到本地,效果如下
读取存档
创建一个DestoryWorld函数,在读档之前先清空
创建一个Load函数,用于加载存档
其中SpawnAndDeserialStrokes函数,用于解压读档后的数据
最后在PaintingPawn中调用Load函数即可完成读档
画册
UI画板
地图配置
创建一个MainMenu地图,并创建与之对应的UIGameMode和UIPawn
UIGameMode直接复制上一个PaintingGameMode即可
UIPawn继承自VRPawn
在MainMenu中做好如下配置
WBP_PaintingGrid
创建一个控件蓝图,继承自用户控件,命名为WBP_PaintingGrid
创建一个画布面板,注意锚点设定在中间,以及位置、尺寸、对齐和填充的设置
尺寸框注意需要填充对齐,并将行和列都设置为对应的索引
BP_PaintingPicker
创建一个Actor蓝图,命名为BP_PaintingPicker
创建一个样条Spline和一个控件组件PaintingGrid
Spline的配置细节如下
切换到视口的右视图,将PaintingGrid的位置移动到样条的端点,移动之后,PaintingGrid的细节配置如下
UIPawn
在UIPawn中新增一个控件交互组件,用于VR与控件蓝图之间的交互
做好如下配置,然后在时间图表中补充这段蓝图,用于将手柄交互映射为鼠标交互
地图配置
新创建的地图是空的,需要加上用户起始点以及上面创建的BP_PaintingPicker
然后运行蓝图,你就会发现面前有一个面板,并且可以进行简单的交互
操作台
创建一个Img文件夹,将图像素材都导进去
创建一个控件蓝图,继承自用户控件,命名为WBP_ActionBar
创建两个按钮,分别是删除和新增
在BP_PaintingPicker中再创建一个样条,然后在对应的位置把这个ActionBar加进去
接着再运行VR,会看到下面有两个按钮,
新增画作
对画作的主要操作都在BP_PaintingPicker中完成,
我们需要将ActionBar和PaintingPicker绑定起来
打开ActionBar的图表,创建函数,存储PaintingPicker的引用
然后打开PaintingPicker,做一些初始化操作,关联ActionBar,同时存储PaintingGrid的面板
注意PaintingGrid要将变量打开
创建一个用户控件,命名为WBP_PaintingCard
在WBP_PaintingCard的图表中新增一个Painting变量,用于存储这个画作的名字
在PaintingPicker中创建AddPainting函数
其中GetTimeStamp是获取时间戳,保证每个画作的名字不一样
AddCard是新增画作卡片
最后绑定ActionBar中的添加事件即可
删除画作
本节要实现的功能是,点击操作台上的删除按钮,所有画作都会出现红框,然后点击对应的画作就可以删除
首先打开PaintingCard控件,创建一个PaintingPicker变量,用于绑定它两之间的关系
然后在PaintingPicker的AddCard逻辑里加上这部分的初始化逻辑
创建一个删除画作的函数,叫DeletePainting
核心思路是先从Paintings字符串数组里找到要删除的那个Painting,然后清空所有面板,接着根据新的Painting数组再创建所有卡片
其中InitCards的逻辑如下
接着为了控制删除模式和正常模式,我们需要创建一个ChangeDeleteMode函数,并创建好相关的变量(DeleteButtonStyle、NormalButtonStyle、DeleteMode)
DeleteButtonStyle是删除状态下的卡片样式
NormalButtonStyle是正常状态下的卡片样式
DeleteMode是一个布尔值,用于控制状态
SetCardButtonStyle的详细逻辑如下
控制好这两种模式之后,再创建一个CardClick函数,用于实现删除模式下,点击卡片就删除的效果
最后在PaintingCard中调用该函数即可
保存画板
创建一个SaveGame蓝图,命名为BP_MenuSave
创建Save和Load两个函数
在PaintingPicker初始化的时候,完成BP_MenuSave的初始化,并读取相应存档
在新增画作或者删除画作的时候,也需要做存档,所以先创建一个存档函数,然后在相应的时候调用
完成之后,整个画板就会自动保存和读档了
优化
数据联通
创建一个游戏实例,命名为PaintingGameInstance
在这个游戏实例中创建一个CurrentPainting变量
在项目设置中应用这个游戏实例
在用户点击卡片的时候,我们就将游戏实例里的CurrentPainting设置为对应卡片的Painting,并打开PaintingMap
在初始化PaintingPawn的时候,根据CurrentPainting来读档就可以了
最后补充一个返回画册的操作
画作封面
目前从画册里看,所有画作都是白色封面,我们需要做个截图来提醒用户每幅画的大致内容
先创建一个渲染目标,命名为RT_PainterSpectator
然后配置它的细节,注意别填错
接着打开PaintingPawn,创建一个场景捕获2D
在用户存档的时候,就顺便截个图
为了防止在截图的时候,画面被头显挡住,我们可以关掉头显的可视
(测试一下,存档之后会看到你的截图)
接着回到PaintingPicker,我们需要读取图片,然后展示在画册上
创建一个DefaultImg变量,当用户新创建画作,或者画作截图为空的时候,就读取这个默认图片
创建SetImg函数,实现给卡片填充封面的能力
在初始化所有卡片的时候,补充上填充封面的功能
在用户新增画作的时候,也需要补上默认封面
至此所有的画册就都有封面了
笔画删除
我们在绘画的时候发现还无法删除笔画,这里就补上相关功能
首先在PainterPawn里面创建一个Painter静态网格体,用于指示准确的笔尖触点
为了区分删除模式和正常模式,我们想让Painter在不同的模式下显示出不用的颜色
首先在PaintingPawn初始化的时候创建动态材质实例
然后按下右手Grab键的时候,设置不同的模式和颜色(注意DeleteMode变量需要提前创建)
接着检测Painter和笔画之间的碰撞关系,如果在删除模式下碰到了,就直接删除该笔画
为了让碰撞顺利发生,我们还需要设置Painter和BP_stroke的碰撞预设
至此,整个VR绘画就基本完成了!