[UE4]第三人称探索类游戏的镜头控制思路与经验分享
我们工作室正在开发的独立游戏
《藏梦》
之前出了个小demo,作为一款第三人称探索类游戏,镜头却被疯狂吐槽,作为唯一码畜的我,有种被凌迟的感觉。
因为算是第一次开发需要上线的完整项目,毫无经验可言,所以之前一直没有重视过镜头控制这方面,3D镜头更是直接SpringArm就完事了,还处在“只求实现不求效果”的层面。
这次花了小半个月的时间,认真的研究了一下镜头控制的各种问题与细节,终于实现出了还不错的效果,把收获到的与大家分享,欢迎讨论。
内容提要
一、用不用SpringArm?
二、实现一个简单SpringArm
三、处理相机碰撞
四、镜头效果细节优化与注意事项
写在前面
- 本篇文章是思路与经验分享,只会提供部分实现蓝图。因为内容繁杂但并不难,并且节点重复率很高,只要理解了思路,一定可以自己实现的。
- 这里分享的所有思路与问题解决方法,都是我已经实现并验证过效果了的 ,并不是纸上谈兵,请放心食用。如果在实现上有任何问题,欢迎与我交流。
-
在《藏梦》中,我们目前使用了四种镜头:
3D镜头(需要鼠标),2D镜头,固定镜头,环山镜头。
并且涉及到镜头相互切换的需求,在此篇文章中以3D镜头为主。 - 主要参考了《风之旅人》与《Rime》两款游戏。
一、用不用SpringArm?
1.SpringArm无疑是个十分方便的组件,如果你 只有3D镜头一种需求当然首选SpringArm。
2.如果你有多个镜头,频繁涉及镜头切换,并且对切换时的效果有要求,那也可以像我一样从头到尾使用同一个镜头,通过一个状态机来控制。这样可以一定程度上自定义镜头切换时的逻辑,不过我这里只是从头到尾贯穿了将Camera焦点保持在人物身上的逻辑,没有加其他特殊的处理。
3.SpringArm与状态机并不冲突,用SpringArm也能实现其他几种镜头,但SpringArm的ArmLength(弹簧臂长度)是上限为1000的(可以通过SocketOffset来变相实现延长),而且我个人感觉用SpringArm去做其他特殊镜头没必要,反而让简单的逻辑变复杂了,不如自己从头撸一个。
下面是两种切换镜头的效果对比:
镜头分开做,用SetViewTargetWithBlend节点进行切换:
使用同一个镜头,通过状态机切换:
二、实现一个简单SpringArm
1.一般的跟随相机逻辑十分简单:鼠标控制ControlRotation,根据ControlRotation和Distance计算出相机需要移动到的位置,然后进行插值移动。
但SpringArm有一点区别,当鼠标旋转时,镜头从A点需要插值到B点,这时候镜头的移动路径则会沿着虚线,而如果是SpringArm则是沿着实线的路径。
所以实现SpringArm不能直接用 位置A 插值到 位置B了,而是应该改为 旋转插值 。
2.旋转插值的实现
因为SocketOffset(插槽偏移)用不上,所以我直接阉割掉了。
当前旋转可以通过 相机位置 和 玩家位置+TargetOffset(目标偏移) ,使用FindLookAtRotation节点求得。
目标旋转则直接GetControlRotation获得。
然后进行插值后获取到的Forward向量,就是相机目标位置指向相机焦点★的向量了。
(这里插值的速度自然就是CameraRotLagSpeed(相机旋转延迟速度))
同样,ArmLength(弹簧臂长度)也是需要插值来保证人物移动时镜头圆滑的跟随。
玩家位置+TargetOffset到相机位置的距离 插值到 ArmLength参数的默认值。
(可以用Clamp设定距离最小值最大值,以保证相机不会离Player太近或太远)
人物位置 - 目标方向向量 × 目标ArmLength 即可求得相机目标位置了,再使用一次FindLookAtRotation即可求得相机目标旋转了。
一个简单的SpringArm就完成了。
三、处理相机碰撞
1.SpringArm组件对镜头碰撞的处理其实只有两种,瞬间拉近 or 不拉近。
只有这两种显然不够用,很多场景物体不需要突兀的瞬间拉近,也就是说可以允许一定的穿模,但不能一直保持在穿模状态,那么就需要平滑拉近。
2.两种平滑拉近的情况
第一种是
只有当镜头自身碰撞到物体时,才会平滑拉近。
尽量减少镜头拉近拉远的次数,并且保证不停留在穿模状态。这也是《Journey》和《Rime》里主要使用的(当然Journey里不止这么简单..)。
第二种是
只要人物与相机之间存在物体,就平滑拉近。
这样可以保证人物永远不会被遮挡,推荐用在狭小的空间里比如迷宫之类的。
这样我们的相机就有了一共四种拉近的情况:瞬间拉近,当镜头接触到物体时平滑拉近,当镜头与人物之间有障碍时拉近,不拉近(几乎用不上)。
3.关于实现
①一般情况,地形是绝对不能穿模的,所以适用瞬间拉近。而场景里大部分物体是需要平滑拉近的,且Camera通道碰撞一般默认的是Block。
所以如果你使用了SpringArm组件的话,建议关掉自带的CollisionTest,然后多实现一个瞬间拉近的逻辑就可以了,否则场景里的物体你都要手动改掉Camera通道。
②要用球形射线检测
比如下面这种情况,普通射线只能保证相机位置不会在模型外,但不能保证视野范围,也就意味着会穿模,而球形射线就很好的避免了这个问题。
注意用HitLocation获取到的才是射线击中时的圆心位置(我们需要的)。ImpactPoint获取的则是绿点位置。
③区别三种拉近情况
个人认为通过物体的ObjectType来区别是比较方便的,新增一个ObjectType也很简单。
通过射线检测到的物体的ObjectType来判定是要瞬间拉近,还是平滑拉近。
④三种情况的实现逻辑
其实整体只需要一条
人物到相机的球形射线
,和
在相机位置的球形射线
(用来模拟相机的碰撞)即可。
注意:用来模拟相机碰撞的球形射线,Start和End并不能直接用相机位置坐标,需要加一点偏移,否则会失效。
瞬间拉近和人物镜头之间有遮挡平滑拉近都是通过人物到相机的球形射线直接检测,只不过拉近的方式
瞬间和插值的区别
。
而镜头碰撞时平滑拉近则是在
镜头碰撞射线检测到物体后
再开启人物与相机之间的检测来确定拉近目标位置,并且要保证拉近到的是
跟镜头碰撞的同一物体
前方。
如下图所示,黄色射线检测时需要跟蓝色射线检测到的物体进行比对,拉到B前方则为错误。
(相机碰撞射线建议比另一条的半径稍大一些)
三种情况如果检测到需要拉近,都要先将目标位置与人物的距离记下, 最终选择一个最近的位置进行拉近。
四、镜头效果细节优化与注意事项
1.对于两种平滑拉近的方式,理论上是可以同时在场景中使用的,但其实没有实际意义。基本都是在使用其中一种,通过trigger来切换开关。室外场景用镜头碰撞更佳,室内或狭小空间用遮挡拉近。
2.人物
上楼梯镜头抖动
问题
在通过镜头位置使用FindLookAtLocation确定镜头朝向的时候,也使用插值,并且将Pitch和Yaw用不同的速度插值。当上楼梯时,影响的是Pitch的值,所以Pitch用较小的速度进行插值,可以解决镜头抖动的问题。
3.仔细观察可以发现很多游戏里,
一直向左或向右走,走出来的不是一条直线,而是一个圆
。
人物左右移动方向也就是相机的左右侧方向,这点逻辑是没必要改变的。所以,只需要在人物左右移动时给视角加一点旋转,这样人物移动的路线也自然是有弧度的了。这样确实感觉效果更自然丝滑一些。
注意:
需要检测是否鼠标有X轴上的输入
,如果有,则关闭自动旋转偏移。
建议旋转速度与人物移动速度成正比,一般情况只需要很小的值。
在《Journey》里,进入空间较小的建筑时,会将自动旋转的值调的很大,达到自动跟随相机的效果,玩家可以直接抛弃右摇杆。PC端的话,用鼠标的情况下效果感觉没有那么好,容易晕,是否使用因项目而异了。
4.在《Rime》里,当人物走到墙角时,会抬高相机焦点位置,也就是增加TargetOffset的Z值。防止人物穿模或者视野太狭小的情况。(注意TargetOffset也需要插值变化)
5.镜头拉高时,可以适当增长ArmLength,更有那种孤独感,适合风景美的游戏。
6.当人物静止一段时间时(包括移动和视角转动),可以实现一个角度自动修正。镜头有一点点变化,会让游戏看起来更生动。
加上一点焦点偏移,让人物处在屏幕的黄金分割线位置效果真的加分巨多。
7.当你的镜头不丝滑,先想想是不是有需要插值的地方没有用。
插值,永远滴神!
文章内容欢迎讨论指正。感谢关注评论点赞收藏!
(藏梦官方QQ群:674309695 不定期抽奖游戏周边和小礼品~)