灵感档案 | 《Celeste》中跳跃手感的仿制版源代码学习
视频BV1xr4y1s71V简单讲述了制作者是如何模仿出《蔚蓝》这样角色移动与跳跃的手感的。今天就来啃一啃这位外国大佬的代码,学习一下优化的角色移动。
状态的判断
在源代码的Movement脚本中,使用了多个bool变量来记录角色是否可以进行某行动的状态。
Update作为输入检测的主循环,在前半部分更新玩家当前的状态,后半部分根据状态和输入进行响应。由于源代码无注释,这里写了笔者自己的批注。
按左shift键可以抓在墙上而不滑落。这里很明显可以看到是通过wallGrab进行划分的,为true时应该是什么条件,为false时应该是什么条件,然后抓取时保证不滑落。
在地面上时,使用专门的优化脚本。同时确保除地面跳跃的其他状态,也就是墙上跳跃的状态为false,避免影响。
抓取墙面时玩家可以在墙面上下移动。向上速度减半,向下全速下落。如果没有爬墙在爬墙时冲刺了,重力大小变回来。
这就是Update前半部分主要用于更新状态的代码。后面的部分主要负责根据状态调整方法。
然后我们来看看比较特殊的一些方法实现。在向下滑动时,通过向墙方向移动会使滑动速度减慢,否则原速滑动。
在蹬墙跳跃时,让玩家在起跳的短时间内无法操纵角色以确保跳跃动作确实做出。由于只能跳跃一次所以改变标志。
然后我们再来看一下用于优化的脚本BetterJumping。
这也是优化手感必要的一步,下落时增加速度,长按跳的更高,短按增加重力跳的更低。
尝试模仿
那么笔者就通过上述技巧和方法,来自己实现一下手感优化的人物手感。不同是我希望将各个功能拆分为不同的脚本,这样一来就方便后续人物各个功能的限制与扩展了(单一职责说是)。
那么我暂时先尝试一下这种结构。
在编写移动脚本时,我发现无论我怎么调整角色移动都有惯性感。这是因为Unity的Input.GetAxis会根据输入设备自动做平滑插值,模拟“加速”和“减速”效果。GetAxisRaw则不会。
这个插值可以在输入管理器中更改。“重力”代表按键松开,值归零的速度。”灵敏度“代表按键按下,值更新时的速度。重力越大减速越快,灵敏度越大加速越快。
那么我们应该如何确定玩家是否是在地面上呢?我采用了碰撞点类ContactPoint2D。
其中,法线方向就是垂直于碰撞面的方向,我们就利用这个来判断角色在碰撞体的上面还是侧面,从而更新地面和墙面逻辑。需要注意的是Exit方法执行时似乎没有接触点生成,无法用于判断离开方向。
离开时直接使用维护的法线方向即可。
还有一点需要注意(还真是处处碰壁啊),Unity 的Collision2D对象在Exit触发时,可能已经被物理系统回收或重用,是临时的。所以用GameObject或Collider2D更稳妥。
目前这种方式只适用于规则的方形碰撞器,对于不规则碰撞器笔者暂时没有想到解决方案。
这样一来,在跳跃脚本编写好以后,我们就可以自由调节落下与按键松开时的重力加速度。在Unity的刚体中,重力大小对应着是重力加速度,Unity应该是没有出现过“力”的相关属性。所以要调节按住按键时的跳跃需要通过刚体的重力与起跳速度共同调节。
在蹬墙跳脚本中,通过检测canMove来保证蹬墙的起跳速度不被滑落速度覆盖。
至此,跳跃手感这一块算是不错了。
土狼时间与跳跃缓冲
那么现在,我想尝试加上大名鼎鼎的土狼时间和跳跃缓冲来进一步提升手感。土狼时间要求我们在离开地面的短时间能可以再次起跳,跳跃缓冲要求我们在落地前的短时间内的跳跃输入能够检测到。
针对土狼时间,我们可以直接依据onGround判断是否需要倒计时。
然后是跳跃缓冲,在输入跳跃时倒计时,在起跳成功时置为0。
这时我们发现,角色起跳后的短时间内可以二次跳跃,这明显不符合逻辑。这是因为土狼时间在跳起后也会进入倒计时,而跳跃缓冲的加入放大了这一现象。
即便我在跳跃后重置计时器为零也无法解决,这种令人难绷的问题依然是由于物理模拟与输入检测的时间差导致的,是否在地面的检测是由碰撞器物理模拟的,所以我在Update的跳跃后地面的检测没能及时更新,最终就变不成0。
既然没法从检测逻辑入手,那就只好限制跳跃后短暂时间内无法跳跃好了。
这样一来,一个优化手感的且有一些神秘缺陷的人物移动脚本就大功告成了。
小结
其实对于人物控制脚本还有很多可以扩展的地方。而且我认为我的碰撞器检测部分的代码有待优化,后续关于有关输入检测与物理模拟的问题还会更多,恐怕这种检测方式会难以处理。
如有补充纠正欢迎留言。