用Bolt实现角色运动控制(2):RPG式第三人称角色控制


RPG式第三人称角色控制

视频教学地址:B站视频YouTube在线观看
工程文件下载地址:

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

基本需求:

  • 俯45°视角固定跟随摄影机;
  • 按住右键绕角色旋转摄影机视角;
  • 键盘ASWD控制Player在摄影机视图中前后左右运动。

实现原理

  • 使用Transform: Rotate Around单元来旋转摄影机
  • 使用Rigidbody来运动角色
    - 通过设置Rigidbody.velocity属性来控制角色移动速度
    - 通过Transform: Transform Direction单元来将世界坐标系的速度矢量转换为相机坐标系的速度矢量
    - 通过Transform: LookAt单元来旋转角色使其始终面朝运动方向
  • 通过Animator Controller来控制角色动画

实现步骤:

首先设置场景:

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

从Asset Store下载免费的牛仔低模作为Player角色(Low Poly Cowboy),并导入自带资源中的idle、run、walk三段动画数据。

将Cowboy模型的prefab拖入场景,新建一个Animator Controller素材(取名Cowboy_AC),并指定给Cowboy的Animator组件上的Controller。

这种下载的Asset资源大多都给大家做好了prefab,直接使用即可。但有些不太规范的Asset仅提供FBX甚至OBJ模型文件,就需要手动去设置三维模型的导入配置了。

双击Cowboy_AC打开Animator窗口。我们可以添加一个Blend Tree来作为初始默认State,然后在Blend Tree里用3个Motion(idle、walk、run)来做动作混合。在Parameter一栏中添加速度控制float类型变量MoveSpeed

这样设置的结果是:当MoveSpeed = 0时,角色播放idle动画,增加MoveSpeed值使得idle动画与walk动画融合,直到MoveSpeed = 0.5时完全播放walk动画,继续增加MoveSpeed值,walk动画与run动画开始融合,直到MoveSpeed = 1,角色完全播放run动画。

通常我们不会直接在美术素材上添加交互,所以我还是在场景中专门添加了一个Player空物体,将Cowboy作为Player的子物体,然后在Player上做交互。

  • 确保Cowboy上没有诸如Collider或者Rigidbody这样的交互相关组件,只是一个纯粹的美术素材
  • 在Player上添加Capsule Collider,设置成合适的大小
  • 在Player上添加Rigidbody,设置相关参数
  • 在Player上添加Flow Machine,改成Embed模式

然后制作角色的运动

点击“Edit Graph”开始制作交互逻辑。

基本的控制方法如下图所示,通过Input: Get Axis节点获取键盘的输入,转换为速度矢量后输出给Rigidbody: Set Velocity节点。

但这样的速度是相对于世界坐标系的,而不是相对于摄影机坐标系。

为了让整个交互的结构显得清晰一些,我选择将计算Velocity的流程与设置Player运动的流程区分开来。

新建一个Graph变量Velocity (类型为Vector3),在一个Graph中用变量Velocity输出给Set Velocity节点,在另一个Graph中计算这个Velocity究竟应该怎么取值。

首先在Create Vector 3节点后面添加一个Transform Direction节点,并添加一个Camera: Get Main节点来获得当前主摄影机以链接给Transform Direction

添加一个Debug: Draw Ray节点来描绘出变量Velocity的方向以供我们判断。

这时我们可以看到,原本世界坐标的“上下左右”被转换成了摄影机视图的“上下左右”。但这里出现了一个新问题,那就是摄影机视图的“上下左右”其实也并不是我们所想要的正确方向,我们其实希望Player的速度矢量依然是在世界坐标系的XZ平面上。

为了解决这个问题,我将Transform Direction输出的Vector3重新拆开,获得其x和z轴上的分量,然后再用Create Vector 3重新组合成一个新的Vector 3。

这样依然有问题,虽然Velocity的方向匹配上了,但其Forward方向上的数值却被缩小了,而且摄影机越是“俯视”,最终的Velocity.z就越小。

因此,我需要添加一个Normalize节点,将Create Vector 3所输出的Velocity矢量进行“标准化”。

Normalize可以将一个输入的矢量转换为一个方向不变,矢量长度恒定为1的新矢量。

如此一来,我们搞定了Velocity的方向问题,但也因为Normalize的关系,失去了按下按键之后输入数值“渐变”的效果,因为不论输入的控制数值多小,都会被Normalize为长度为1的Velocity。

如果想要找回这种“渐变”效果,可以用一个Vector3: Magnitude节点计算出原始输入的矢量长度,然后与Normalize节点的输出结果相乘。

仔细观察一下,还可以发现一个之前忽视了的问题,那就是当键盘控制角色“斜”走的时候,实际得到的速度值其实是大于键盘控制角色“直”走的。这是因为斜走需要x和z轴都输入,而其合并后的Magnitude其实长度不是1,而是1.414。

想要解决这个问题,需要分别对输入的x和z分量进行一定的缩放,然后再用其缩放后的结果去计算新的Magnitude。

我这里提供一种解决方案来进行这一缩放操作,即:对原始输入的Vector3进行Normalize,然后分别用Normalize之后的x和z分量去乘原始输入的Horizontal和Vertical数值。大家也可以思考一下还有什么别的办法可以解决这个问题。

这样我们就把所需要的Velocity给计算出来了,最后还可以再乘上一个自定义的MaxSpeed (Float)变量,来整体放大角色的运动速度。这一阶段完成的完整Graph如下:

接下来制作角色的旋转

当角色的运动方向发生变化时,其自身也会发生旋转来保持始终正面(forward)朝向运动的方向。这一需求通常都可以通过Transform: LookAt节点来实现。最基本的Graph是这样的:

LookAt本质上是让一个物体的正面指向空间中的一个点或另一个物体(的中心点)。它有很多“分身”,我们这里选择的是带有World PositionWorld Up参数的那个分身。

想要LookAt一个具体的方向而不是具体的点,可以用这个方向和物体本身的位置(position)做相加(Add)计算,这样就得到了一个合适的点位置。

这样做出来的效果是一个“突变”效果,就是角色的旋转是突然变化的,而不是我们所希望的“转向”目标方向。想要得到“逐渐转向”的效果,需要用到Lerp相关的计算。

Lerp是很常见的一种数学计算函数,通常用来将一个值“逐渐变化成”另一个值。它的本质是根据一个T值(代表变化的程度)来取得值A向值B做“缓入缓出”变化的过程中某一个特定中间点的值C,但我们可以通过每帧计算值C向值B进行Lerp而得到新的值C的方式,来呈现值B逐渐变化为值C的过程。

这一数学原理应用到我们的Graph中就是这样的:

我用Transform: Get Forward获得Player当前的forward方向,然后将其用Vector3: Lerp向目标方向(也就是Velocity的)做Lerp计算。由于Lerp的结果将会通过LookAt旋转角色进而改变Player自身的forward方向,每帧Player的Forward都会逐渐靠近Velocity方向,这样就得到了一个逐渐变化的过程。

这里,T值决定了变化的快慢。T = 0 时不会变化,T = 1 时变化瞬间完成。我们可以这样理解,T = 0.1 代表这个变化会在10帧内完成。如果不喜欢用帧数来决定这个过程,可以添加一个Per Second节点来控制这个T值。Per Second可以将“每帧”值转换成“每秒”值,Per Second = 1 代表这个变化会在1秒内完成,Per Second = 10 代表这个变化会在0.1秒内完成。

控制角色动画

Player这个角色的动画其实我们已经在Animator中设置好了,通过MoveSpeed变量控制Blend Tree上三个动画片段之间的混合。但这个Animator组件并不在Player上,而在其子物体Cowboy上。我们只能通过变量的方式来调用并设置不属于自身的组件的变量。

新建一个Update Event,连接一个Animator: Set Float节点。新建一个变量Animator(Animator类型),手动指定其取值为Cowboy上的Animator组件。将这个变量连给Graph中的Set Float节点。

修改Set Float节点的“Name”输入为“MoveSpeed”,将变量Velocity计算出其Magnitude值(也就是当前速度快慢数值),连接给Set Float节点的“Value”。

这样做其实是在用Player实时的运动速度快慢来驱动Animator Controller中的Blend Tree中三个动画片段的混合,以达到一种“自动化”的程度。也就是说,我无需手动设置什么时候该“走”什么时候该“跑”,只要角色运动速度达到一定的程度,角色就会自动选择是站立还是走还是跑。

目前我设置的MaxSpeed是2,也就是说角色的最大速度不可能超过2,实测发现这样的运动速度是和动画不匹配的,太慢了,脚步会“滑”,很不真实。

通过测试,可以找出最适合这个Cowboy模型的walk动画的速度值是1.6,最适合run动画的速度值是4.8。

MaxSpeed变量设置为5,修改Blend Tree中三段动画的混合阈值如下图:

我们就可以得到一个比较匹配我们模型的“站走跑”动画混合了。

相机旋转和跟随

在上一讲,相机旋转是通过直接旋转来实现的,这一讲中是第三人称视角,需要相机绕着Player旋转(始终看向Player),所以选择使用Transform: Rotate Around,但基本思路是一样的。

  • Transform: Rotate Around需要一个旋转中心点,这里使用Player的轴心点上方1.2单位高度的位置(大概是角色胸口的位置)来作为这个中心点;
  • Transform: Rotate Around还需要一个旋转速度,这里使用鼠标X轴位移来计算得到这个值。

在这个例子中,相机不仅需要能够旋转,还需要能够跟随Player一起运动。最简单的办法是实时获取Player的position,添加一个固定Offset变量,再用其结果设置相机本身的位置(position)。

这两个功能单独用起来都没有问题,但如果一起使用(运动过程中旋转摄影机),整个效果就不对了。这是因为:1. 相机旋转实际上改变了相机与Player的相对位移(也就是Offset变量所应该体现的内容);2. 相机需要先跟随Player移动位置,再绕Player旋转,最后更新Offset值以供下一帧使用,所以这3个操作需要做成一个Graph以保证其运行顺序。

因此,最终的相机交互逻辑修改如下:

在这个Graph中,每帧首先根据Offset值设置好相机位置,然后判断是否需要旋转相机,如果需要,旋转后更新Offset值。另外,我还做了一个Start Event上的交互逻辑让游戏运行时能够自动计算出初始的Offset值来。


至此,我们就完成了这个简单的RPG式第三人称角色控制的交互逻辑。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容