在Unity中实现角色行走时会遇到林林总总的问题,这里就记录一下我遇到的问题并提供解决方法。
由于本人是零基础自学,可能会出现一些很基本的问题,记录成长为主。
1.玩家键盘的输入转为移动信号
玩家的输入一般由两种形式,一是键盘输入,二是手柄输入,这里暂且只考虑键盘输入。一般移动的输入键是wasd亦或是↑↓←→。而移动信号的表现形式为一个二维坐标轴:
我们要做的就是当玩家键入W时,记录坐标轴的xy变量分别为(1,0);另外还要考虑冲突问题,当玩家同时键入AD时xy变量应为(0,0),角色应静止不动。
由此我们可以写下如下代码:
显然targetUp为x坐标,targetRight为y坐标。剩下要做的就是添加blend tree和动画,把blend tree(Blend Type为1D)的参数与信号联系起来,这是后话了。
2.平滑移动的问题
按上述实现的代码的话,不难发现:当玩家键入W,角色会从静止立刻到播放行走的动画,即0直接变为1,这就很突兀,动画播放上会不连贯。解决这个问题的根本就是0应该匀速增长到1或是1匀速减少到0。恰好Mathf类提供了这么一个方法:
这个函数能实现从一个三维坐标向一个理想值地逐渐转变。由图知此函数需要一个当前的坐标值、一个目标坐标值、一个当前速度的三维坐标引用,一个平滑时间的浮点值。其中的3个不难理解,平滑时间就是规定完成这个过程的时间,但这个currentVelocity需要解释一下,按照官网的描述,当你每一次调用这个函数时,这个值都会被修改,那么其实就提供一个不用初始化的Vector3变量就好,其余的SmoothDamp会自己搞定。
targetUp呢就是刚才得到的硬邦邦的不是0就是1的值,显然这个就是目标值,需要匀速增长或减少到这个值;Dup呢就是初始值,是增长或减少前的值。当Dup为0时,targetUp为1时,就从0增到1;当targetUp变回0时,此时Dup是1,就从1减到0。
这个处理其实与Blend Tree里的参数是很符合的。
3.转向问题
转向问题其实就是改变角色当前向前的方向的问题(forward)。一开始用的是坐标轴的坐标做信号,现在可以换个方式,用距坐标轴原点的距离做移动的信号,配合上单位向量,就能解决转向和斜向走的问题。
设置一变量记录距原点的距离:勾股定理,不难理解。
当然Dmag只是一个标量,不具备方向,但可以与Blend Tree的参数产生联系了,forward为在Blend Tree里的参数,anim为一Animator类型,接收其Animator组件,不再赘述。
我们还需要一个Vector3变量来记录其方向和坐标值:
以此改变模型的向前向量:
此时角色就能随意转向了,但仍有一点瑕疵:
1.当Dup、Dright归0时,Dvec会得到一个(0,0,0)的矢量,从而导致模型的向前向量也变为(0,0,0),会出现无论我怎么转方向,过一会就会回到模型z轴的正方向。
2.转向也缺少平滑处理。
3.Dmag会出现1.414的情况,原因是当斜向走时由于勾股定理得到的是一个根号2,这会造成当你角色真正走起来时斜向走的速度会比正向走快40%。
关于第一个问题,只要在改变转向之前给Dmag设个最低阈值,这就能防止把(0,0,0)给到向前向量。
其他问题将会在后续解决。
4.真正走起来
在给模型添加了刚体(Rigidbody)组件后,有两种方式可以令角色真正走起来,一是改变刚体的位置position,二是改变刚体的速度velocity。改变位置很简单,速度乘以时间就好:
但这一过程要在FixedUpdate()里实现,而不是Update(),因为这两个函数每一帧的时间不一致,Update()每一帧的时间不固定,即第一帧与第二帧的时间t1和第三帧与第四帧的时间t2不一定相同。FixedUpdate()每帧与每帧之间相差的时间是固定的。所以一些物理属性的更新操作应该放在FxiedUpdate中操作,因为这样GameObject的物理表现的更平滑,更接近现实。而Time.fixedDeltaTime与Time.DeltaTime也有所区别,前者是固定时间,为0.02s(可修改);后者是前一帧到后一帧所用的时间,因为Update()中的帧率会变化,所以Time.DeltaTime也会变化。
如果改变的是刚体的速度,要有一点需要注意,如果单纯是这样:
这在地面上时没什么问题,可以正常行走,但当你走上斜波再走下来,你会发现你悬空然后慢慢下降:
因为上述代码把它速度中的y分量置0了,movingVec中只有x(Dright)和z(Dup)分量有值,y分量为0,这就导致了它一下坡就悬空了。代码应该改为
这就可以了。现在角色走起来没什么大碍了。
以后多点GIF来展示效果好一点。
2019年7月1日 17:07:20
5.平滑转向
现在物体前进的方向是受Dvec控制的,而Dvec只是单纯的坐标轴坐标与单位向量相乘而得,就是说D就是左,A就是右,从左转到右不具备转身的过渡态,是立刻完成。上图或许更直观:
这里需要一个函数来解决这个平滑转向的问题,那就是Vector3.Slerp,先看看这个函数在官网上的描述:
能在两坐标之间进行球面插值,何为插值?就是在离散数据之间补插一条连续函数,使得这些离散数据可以在这条函数中估算得到。例如两个不相关的点,在其间补插了一条连续函数之后,能用这条函数从一个点递增或递减到另外一个点,不过这是线性插值,线性插值有经过零点的可能,相关函数是Mathf.Lerp。球面插值与线性插值不同,它加入了向量当做方向,这使得它可以不经过零点。这个特性很好的解决了转向的问题。
接下来看看它要怎么调用:
它吃3个参数,一个是当前值,一个目标值,一个是进行插值过程的速度。好来试试看:
为了让效果明显一点,我把速度设为了0.1f,这个转向速度就很慢了:
我们可以看到,现在已经有一个转身的效果了,但是速度设置太慢会有一个副作用,就是会产生一段与转身反向垂直方向的位移,这不能说不好,看是否需要吧,要是不需要就把速度提高点,提高到0.5试试看:
现在呢位移就几乎没有了(实际上还是有的),转向也很流畅、自然。