Unity基础:3D人物移动及朝向的开发逻辑

可以说是游戏开发中最基础,但做得不好又最容易被玩家诟病的模块。小生涉猎不深,写下此文仅方便自己记忆和入门,深知学艺不精,如有错误还请不吝赐教!

如今,几乎所有游戏都同时支持手柄及键鼠操作,这其实无形中就为我们明确了一个开发的基调:即最终的代码要支持多种设备的输入,就算玩家上一秒在用键鼠,下一秒在用手柄,甚至是手柄键鼠同时用(我第一次用手柄玩GTAV,发现手柄瞄准很难时就是手柄移动鼠标瞄准……),也要支持!于是对于游戏中人物的移动和朝向,应该使用一个统一的signal来驱动,而不是检测某个键位或摇杆的方向来驱动。对的,所以第一步是将键鼠/手柄的输入,转换成统一的信号。

注明一下,这里解决的人物移动和朝向问题,没有将摄像机镜头朝向囊括进来。

这里定义两个信号量,分别管理前后、左右方向。向前的话,则是1;向后则是0。同样的定义可以迁移到控制左右的信号量中。

//Turn Key to Signal, switch keyboard and joystick into same thing
targetDirectionUp = (Input.GetKey(KeyUp) ? 1.0f : 0) - (Input.GetKey(KeyDown) ? 1.0f : 0);
targetDirectionRight =  (Input.GetKey(KeyLeft) ? 1.0f : 0) - (Input.GetKey(KeyRight) ? 1.0f : 0);
复制代码

你可能会想问:诶这不是只有直来直去的前后左右四个方向吗?假如我用手柄精确地控制人物朝哪个方向移动,你这怎么办到?确实。但我们暂且放着这个问题,来思考下拿到这两个信号量后,我们要怎么驱动人物的移动和朝向?

对于移动,有两种思路:直接改变人物位置,或赋予人物一个带有方向的速度。举个例子,对于targetDirectionUp这个变量,它的值就是-1,0,1中的一个,既可以作为人物每个时间单位移动的距离,也可以作为人物移动的速度,逻辑是共通的。这里要注意的是,人物的位置position是一个三维向量,人物的速度velocity也是一个三维向量。那么现在问题变成了:这个三维向量应该是什么?在人物移动过程中应该怎么变化?

在untiy中,人物坐标系通常长这个样子:

也就是说,向前是z轴的正方形,向右是x轴的正方向。那一个很直观的思路出来了嘛:targetDirectionUp不为0我就加到z方向上嘛,targetDirectionRight不为0我就加到x方向上嘛。你问我同时按住↑和→怎么办?那就x轴z轴都加嘛!

OK,那么思路行得通的话,就来处理一下同时在前后和左右方向上移动的问题。我们来对人物做一个俯视图:

对于一个既不在z轴,也不在x轴方向上的信号量,通过API是能够得到它在z轴和x轴两个方向上的信号量的,那很容易想到的是对向量相加求一波噻,得到的结果向量就可以指示方向了,因此有如下代码:

finalDirection = targetDirectionUp * new Vector3(0, 0, 1) + targetDirectionUp * new Vector3(1, 0, 0);
复制代码

并且,将这个向量直接赋给人物的transform.forward,也就顺带解决了朝向的问题了!

到目前为止,思想已经捋清楚了,统一对代码进行整合优化一下下:

//Turn Key to Signal, switch keyboard and joystick into same thing
targetDirectionUp = (Input.GetKey(KeyUp) ? 1.0f : 0) - (Input.GetKey(KeyDown) ? 1.0f : 0);
targetDirectionRight = (Input.GetKey(KeyLeft) ? 1.0f : 0) - (Input.GetKey(KeyRight) ? 1.0f : 0);

//Role's forward direction
faceDirection = targetDirectionUp * transform.forward + targetDirectionRight * transform.right;

//distances per time unit
finalDirection = Mathf.Sqrt((targetDirectionUp * targetDirectionUp) + (targetDirectionRight * targetDirectionRight));
复制代码

重点关注在计算人物朝向向量时,我用transform.forward / right替换了上文中new出来了三维向量。不同之处在于new出来的向量是那么多就是那么多,人物怎么转怎么走都是那样;而transform.forward返回的是人物z轴正方向的单位向量,如果在你的代码逻辑中,人物朝向的旋转是会带动自身坐标系旋转的话,那么transform.forward得到的则不再是(0, 0, 1),而是根据实际而改变的,谨记!

配合动画

除了在Animator中一个一个地创建静止、行走、跑步等动画,然后再各种创建每个动画间的切换条件外,有一个很好用的东西,叫Blend Tree,混合树。

它能做的事情,是根据parameters的线性变换,将两个动画混合在一起,达到“渐变”的效果。在Motion中设置好动画以及对应的触发门限(Threshold)后,Blend Tree就能够非常智能地生成令人舒适的动画切换效果。那么!现在问题变成了:该怎么实现这个控制Blend Tree的parameter的线性变化呢?

在之前的代码中,targetDirection得到的是离散的-1,0,1三个值,由此计算出来的朝向向量和单位移动距离都是离散的而非线性的。具体到游戏的表现中,人物会出现突然旋转到另一个朝向,走着走着突然停下来的现象,放到现在必被玩家逮着喷的。因此需要对targetDirection做一个平滑处理,使得它们的值是逐渐变化的而非突然改变。思路也不难,知道当前的信号量,根据玩家的输入也知道目标的信号量,在两者之间做一个线性过渡就好了,具体如下:

directionUp = Mathf.SmoothDamp(directionUp, targetDirectionUp, ref velocityDirectionUp, 0.1f);directionRight = Mathf.SmoothDamp(directionRight, targetDirectionRight, ref velocityDirectionRight, 0.1f);
复制代码

之后再将前面的targetDirection都替换为direction,就能够将离散的信息转换为线性的。

于是可以将前面计算出来的finalDirection(distances per time unit)赋给forward,随着线性增长,人物也会从idle平滑过渡到walk状态。除了动画外,对于人物实际的移动,则是将计算出来的朝向向量赋值给transform.forward,然后再在这个基础上去改变transform.position,就实现了本文最终希望解决的问题。

//Turn Key to Signal, switch keyboard and joystick into same thing
targetDirectionUp = (Input.GetKey(KeyUp) ? 1.0f : 0) - (Input.GetKey(KeyDown) ? 1.0f : 0);
targetDirection = (Input.GetKey(KeyLeft) ? 1.0f : 0) - (Input.GetKey(KeyRight) ? 1.0f : 0);

//smooth
directionUp = Mathf.SmoothDamp(directionUp, targetDirectionUp, ref velocityDirectionUp, 0.1f);directionRight = Mathf.SmoothDamp(directionRight, targetDirectionRight, ref velocityDirectionRight, 0.1f);
//Role's direction
faceDirection = directionUp * transform.forward - directionRight * transform.right;

//distances per time unit
finalDirection = Mathf.Sqrt((directionUp * directionUp) + (directionRight * directionRight));
//Animator
anim.setFloat("forward", finalDirection);

model.transform.forward = faceDirection;

model.transform.position += finalDirection * model.transform.forward * Time.fixedDeltaTime;
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享