用Bolt实现角色运动控制(1):FPS式第一人称角色控制

在这一讲以及接下来的两讲中,我会教大家用Bolt来实现3中常见的角色运动控制方式:第一人称FPS式、第三人称RPG式、点击-移动式。具体的原理在之前的《PlayMaker简单实例(2):角色与摄影机的运动控制》一文中已经说得很多了,这里只是换了一个方式来实现而已。

相关视频教程我发在B站了,但因为没有加速过,所以都还蛮长的,建议还是先看看图文教程熟悉下再去观看视频教程。


FPS式第一人称角色控制

视频教学地址:B站在线观看
工程文件下载地址:https://pan.baidu.com/s/1EvhF1P73ldyOvQRnxxvXoQ 提取码:5m93

PS:本教学使用的是Bolt1.4版,并未包含在工程文件包中,请大家自行购买安装

基本需求:

  • 鼠标运动控制角色视角移动
  • 键盘ASWD控制角色前后左右运动
  • 空格键跳起

实现原理:

  • 使用Rigidbody来运动角色
    - 通过设置Rigidbody.velocity属性来控制角色移动速度
    - 通过Rigidbody.AddForce函数来让角色跳起
  • 将摄影机设置为角色子物体以实现跟随
  • 通过HorizontalVertical的Input Axis来获取运动控制的输入
  • 通过Mouse XMouse Y的Input Axis来获取鼠标移动的输入
  • 通过Jump的Input Button来获得空格键的输入

实现步骤:

首先设置场景:

新建一个4×1×4的Plane当做地面,重置position,添加一个带地面贴图纹理的材质,避免地面太白看不清楚。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第1张图片

新建一个空物体当做Player,放置在(0,1,0)位置。

给Player添加一个Capsule Collider当做碰撞体,设置中心点位置为(0,0,0),高为2,这样这个Player就正好站立在地面上了。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第2张图片

将摄影机设置为Player的子物体,设置position为(0,1,0),rotation为(0,0,0),这样摄影机就在Player的头顶处了。

给Player添加Rigidbody组件。这时可以选择Freeze掉x轴和z轴的旋转,以避免一些奇怪的旋转,但因为我们后面会直接设置Player的旋转属性,所以其实最终是不需要Freeze掉的。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第3张图片

再给Player添加一个Flow Machine组件,使用Embed模式,准备开始制作运动控制的交互逻辑。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第4张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第5张图片

完整Graph如下:

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第6张图片

然后设置角色移动的交互逻辑

点击Edit Graph打开Flow Graph窗口,右键单击选择Add Unit...,搜索“set velocity”,得到相应的Set Velocity单元。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第7张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第8张图片

因为是使用Rigidbody来控制,所以新建一个Fixed Update单元,将其与Rigibody: Set Velocity连接起来。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第9张图片

使用Fixed Update而非Update是因为前者是每一步物理解算都会更新,而后者只是每帧更新,在这个范例里区别其实不明显,但如果涉及到比较严格的刚体运动、碰撞等计算,还是用Fixed Update比较保险。

Rigibody: Set Velocity直接使用Self(自身)作为被设置速度的对象,我们需要再给其输入一个Vector3向量以代表速度(的强度以及方向)。

这里使用了两个Input: Get Axis单元来分别获得“Horizontal”和“Vertical”两个axis上的输入数值,也就是AWSD或←↑↓→键的“是否被按下”状态,将这两个值分别连给一个Vector3: Create Vector 3单元的x和z输入port,用来组合一个velocity向量。

这个速度向量的y输入则使用Rigibody: Get Velocity单元来获得自身的velocity值,并通过Vector3: Get Y单元来获取这个值在y轴上的分量。也就是说,我们并不想去控制Player的y轴运动。

要注意:我这里xx: xxx xxx的写法代表了这个Bolt单元的完整名称,:之前其实是类名,:之后才是真正的函数名或属性名(一个“类”里面可能有很多个函数或属性,大家可以自己去查Unity帮助文件)。以后慢慢就不会写得这么麻烦了,经常会省略掉类名,大家要习惯。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第10张图片

Create Vector 3的输出会传递给Set Velocity的输入,这样就可以通过键盘的输入来控制Player的移动了。

当然这样控制的移动速度是很慢的,因为Input: Get Axis的取值范围是(-1,1)。想要更快的速度就需要将这个值放大,所以可以将这两个Input: Get Axis的输出值乘上一个系数之后再传递给Create Vector 3

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第11张图片

这里的Multiply单元相当于乘号,Float单元的完整名称是“Float Literal”,相当于一个浮点常数4。

要注意,规范的做法是新建一个变量“Speed”,设置“Speed”初始值为4,然后在这里使用这个变量“Speed”,直接在脚本中用常量而不用变量的做法是不太符合规范的,只是本范例中我故意回避了去使用变量,所以就直接用了Float Literal

运行场景,可以在Graph窗口中看到实时的数值传递,这对于我们debug(调试除错)是非常非常有帮助的。我们可以看到,Get Axis输入的1经过Multiply放大后变成了4,然后被组合成了velocity向量(4,0,0)。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第12张图片

PS:在Play状态下,蓝色的单元代表当前帧是活动的,起效的,白色代表不执行。这也很方便我们的调试工作。上面这个Graph因为是从Fixed Update开始的,每帧都会运行,所以所有单元都是蓝色活动单元。

接下来是角色及视角旋转的交互逻辑

角色及视角的旋转我准备直接对Transform进行操作,用不到Rigidbody,因此也不需要从Fixed Update开始,从Update出发就足够了。

新建一个Transform: Set Euler Angles单元,连给Update

Set Euler Angles需要输入一个Vector3向量,用来设置rotation的三个角度值,我们可以用Create Vector 3来创建这个向量,并且用Get Axis获取“Mouse X”(也就是鼠标x轴方向的位移量),将这个数值传递给Create Vector 3的y轴输入以达到用鼠标x轴运动来控制自身y轴旋转的目的。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第13张图片

但这样得到的结果只是不断地在偏移一个较小的角度,因为鼠标每帧x轴的位移量有限,且鼠标不移动是,角色自身的旋转就为0了。因此需要将鼠标x轴位移作为角色自身y轴旋转的增量而不是绝对量。

因此,我们需要添加一个变量“Rotation Y”,然后将其与Get Axis的结果相加,并将相加的结果通过Set Variable单元设置给“Rotation Y”变量本身,这样的结果是变量“Rotation Y”每帧都会根据鼠标x轴位移数值而增加(或减少)一定的量。

然后再将变化过的变量“Rotation Y”输出给Create Vector 3的y轴输入,这样就可以得到正确的“随鼠标运动而旋转”的结果了。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第14张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第15张图片

同理,可以使用来一个变量“Rotation X”来与Get Axis获取的“Mouse Y”的输入数值相加,得到一个随鼠标y轴移动而不断增加或减少的变量,再用这个变量来控制相机(而不是Player)的x轴旋转即可得到角色视角随鼠标上下移动而变化的交互控制。

注意,这里使用了Camera: Get Main单元来获得场景的主摄影机。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第16张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第17张图片

只不过,这时候得到的是视角上下变化与我们想象中有所区别,因为鼠标向上移动时Mouse Y为正,那么相机的rotateX为正,相机是往下在旋转。

修正这个问题只需要将前面的Add单元改成Subtract单元,让变量“Rotation X”每帧减去(而不是加上)Mouse Y的输入值即可。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第18张图片

最后,为了防止视角穿帮,我们希望限制相机的rotateX不要超过一定的范围,所以就在Subtract的后面又添加了一个Mathf: Clamp单元,让其输出值保持在-90°到60°之间。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第19张图片

完成旋转的交互操作之后再测试,发现移动的交互操作其实是有问题的,当我们通过移动鼠标改变了角色的y轴旋转方向之后,角色的运动控制就不是我们想象中的“按下w键角色往自己的前方走”了,而是斜着沿世界坐标的z轴正方向运动。这是因为Set Velocity中设置的速度是基于世界坐标系,而我们想要得到的控制却是基于角色自身坐标系的。

因此,我们需要将我们的输入从自身坐标系转换成世界坐标系,也就是说,当我们按下w键时,不能直接输出一个(0,0,5)的velocity,而是要输入一个相对于自身坐标系(0,0,4)的velocity在世界坐标系中的准确方向。

这一工作可以用一个Transform: Transform Direction单元来完成:

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第20张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第21张图片

可以看到,最终输出的velocity并不是(0,0,-5),而是(0.9,0,-4.9)。

完整Graph如下:

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第22张图片
位移控制Graph
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第23张图片
旋转/视角控制Graph

补充说明:其实还有更简单的方法来实现“根据鼠标运动旋转视角”这样交互行为,那就是使用Transform: Rotate单元。Transform: Rotate会根据所输入的数值对对象进行持续的旋转操作,而不仅仅只是改变对象的旋转属性值,这样就不要手动去做每帧累加的操作,仅需要直接将“Mouse X”的输入值倍乘之后传递给Transform: Rotate即可。

接下来是跳跃的交互逻辑

在Graph的空白处添加一个On Button Input单元,设置Action为“Down”。这是一个Event类型的单元,其作用是“当xx按钮被按住/按下/松开时,被触发并执行其后连接的单元”。Bolt中有很多这类“On xxx Input”,比如监测鼠标按键的、监测键盘按键的、监测碰撞/触发器的,等等等等。

以这类单元打头的Graph,只有在Action条件被满足之时才会运行。以On Button Input为例,如果Action选择“Hold”,则当按钮被按下时会每帧运行,直到按钮被松开,但如果选择“Down”或者“Up”,则只会在按钮被按下或松开的那一帧运行一次。

On Button Input单元后添加一个Rigidbody: Add Force单元,设置Mode为“Impulse”,代表一个突然爆发的作用力,同时创建一个Create Vector 3单元,设置成(0,4,0),传递给Add Force的Force端口,代表这个突然爆发的作用力的方向,是沿着世界坐标y轴正方向的。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第24张图片

运行场景,按下空格键时,Player会跳起。但是,如果不停按空格键,Player会不断上弹,完全不会落地。这就暴露出一个问题:我们需要检测Player是否落地,如果没有,就不能让Add Force起效。

要做到这一点,可以设置一个Bool类型的变量“Grounded”,并在On Button Input后面添加一个Branch单元,用变量“Grounded”作为输入条件,并设置当其值为“True”时,才会继续执行Add Force,否则就什么都不执行。

Branch单元相当于if... else...条件语句,通过它,可以改变Graph的“流动”方向,产生“支流”。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第25张图片

但现在变量“Grounded”并不能真的反映角色是否落地面上。我们还需要一个新的Graph来完成检测角色是否落地这一任务,并将结果返还给变量“Grounded”。

通常这种“检测是否落地”的任务都可以用射线(Raycast)来检测,我们可以从略高于角色脚底的位置向下发射一条较短的射线,如果这条射线碰到了地面物体,那么我们可以认为角色落地了,否则角色就是悬空的。Unity甚至允许用一个虚拟的球形来做这类检测工作,可以得到更准确的反馈。相应的单元是Physics: Sphere Cast

要注意,Physics: Sphere Cast这个单元有非常多的分身,我们一定要选择到有Origin、Radius、Direction、Max Distance、Layer Mask以及Hit Info的那一个。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第26张图片

最后的Graph是这样的:

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第27张图片

上图中的意思是:从自身位置(应该是离地1单位高度)向下方(y轴负方向)发射一个半径为0.3的球,球会一直运动1单位长度,如果在球的运动过程中没有检测到“层”设置为“Ground”的碰撞体,则输出值“False”给变量“Grounded”,反之则输出值“True”。

其中的Layer Mask单元的全名是“Layer Mask Literal"。

然后将地面物体的Layer设置成“Ground”,如果没有“Ground”这个layer,就自己先点击“Add Layer...”创建一个,然后再设置。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第28张图片

注意,新手常常会犯的两个问题是:1. 忘记设置Layer了,那么会导致怎么着都检测不到地面,因为没有任何碰撞体处于“Ground”层;2. 创建Layer之后就以为已经自动指定给游戏物体了,其实并没有,一定要好好检查。

如果设置没有错误,那么角色没有落地的时候Physics: Sphere Cast是检测不到任何碰撞体的,那么变量“Grounded”始终为“False”,按空格键不会有任何反应。


至此,我们就完成了这个简单的第一人称视角角色运动控制的交互逻辑。为了增加一点趣味性,我们可以利用Cursor: Set Visible单元隐藏掉鼠标图案:

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第29张图片

并在场景正中间创建一个UI小黑点来模拟武器准心。

用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第30张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第31张图片
用Bolt实现角色运动控制(1):FPS式第一人称角色控制_第32张图片
准心效果

你可能感兴趣的:(用Bolt实现角色运动控制(1):FPS式第一人称角色控制)