【unity】2d角色斜坡移动解决方案笔记
·解决思路参考
https://blog.csdn.net/qq_37776196/article/details/115233332
(b站有个国外教学视频讲解了斜坡移动,但我当时没看懂,可能和这篇文章解决方案类似,感兴趣可以搜unity斜坡/面移动找一下)
·问题的提出
不知道大家在做横版2d的角色上下斜坡时有没有遇到过这样的问题,使用胶囊碰撞体的角色,如果给她添加一个水平方向的力,根据力的平行四边形法则,会出现这样的情况:


你问为什么离开地面下落没有变成下落状态?emmm,不要在意这个小bug。
·一些失败的思路
固定位置的斜面检测:一开始的思路是,从角色前方一定位置向地面投射射线,检测到斜面时,可以得到斜面的法线方向,进而得到坡度方向,然后将速度的方向改变为坡度的方向,但一个圆上斜面时,并非它最底部的点先接触斜面,如下图所示:

如果这个射线的位置离圆中心比较远,比如从圆边缘向下,检测到斜面就改变速度方向,就会导致圆提前向上(图中红圆位置开始沿坡度方向向上),如果这个射线离圆中心极近,那么在离开下坡时,也会提前改变速度方向,因此这个方案虽然可以做到沿斜坡移动,但上下斜坡时会出现bug。
动态的斜面检测:既然固定位置检测不行,那如果我可以准确得到斜面与物体接触的时机,我就可以在正确时间改变方向了,思路是预先检测前方的斜坡,假如有,从圆心投射斜坡法线反向的射线,如果接触点接触到了斜坡(且距离等于圆半径),说明圆接触到了斜坡,离开斜坡也做类似的判断。。。。总之,这套方法不太优雅其实是有点麻烦,我猪脑过载了
·让物体沿固定轨迹移动:类似3d中的导航,根据地面碰撞体形状生成导航路径,角色在地面时始终沿路径移动,这套方法最大的缺点是,我不会(会的大佬也根本不会看这篇文章吧)
·简单粗暴的解决方法
让我们重新描述一下我们的需求,本来是:
角色在地面时可以左右移动,在斜面上时要始终贴着地面移动。
改为:
让角色向着地面,向左右移动。
然后我就有了这样的思考:我希望圆在每次移动时,都能多加一个向下直到地面的位移,也就是每次都把圆底部的点移到地面上,参考下图,可以看作是从D点移到B点,也就是B点的向量-D点的向量,得到的向量归一化后恰好就是我们所需要的速度方向。

解决方法这就出来了:
1.结合碰撞体类型、偏移和物体位置每帧计算出与地面接触点——这里我们先简单的用碰撞体最底部中心的点;
2.根据角色速度,计算出水平运动时,下一帧会移动到的水平位置,从这个位置发射一条向下的射线获得地面的位置,也就是射线碰到的地面上的那个点;
3.用2里得到的点-1里得到的点,归一化后就是我们要的速度向量,在地面上就用这个向量乘移动速度来进行移动;
4.当跳跃/浮空时,要改为用固定的水平速度移动。
我将发射位置的点固定在了移动方向前方,也就是圆最右/左边的点,得到的效果如下:

·存在的问题
最终效果看上去还行,但严谨来说,还是存在不少问题的:
·如果角色站在一个无限深的悬崖边,在掉下悬崖前存在检测不到地面的情况,需要额外处理,检测射线不够长且站在高台时也同理;
·文中的情况,角色在斜坡上总速度等于在水平面上左右移动的速度如果我数学没问题应该是这样,如果想要斜面上和水平面上左右方向的速度相等或是下坡快上坡慢,需要额外处理,前者可以参考文章顶部链接里的计算方法;
·实际上,上面的结论也不对,如果要更精准一点,在斜坡上的速度方向应该使用碰撞体和斜坡接触的那个点来计算;

·即使在水平地面上,角色也可能并非绝对精准的移动到预计位置,因为角色底部中心点存在没贴紧地面的可能。
·代码参考
我自己的代码里写了一堆bug就不贴了,上一段gpt的回答,方向计算和最下面的点位置有点问题,其它的应该都是对的,有兴趣的可以直接复制粘贴进unity看看效果:
好的,以下是代码实现的基本思路:
在角色对象上添加2D碰撞体组件和刚体组件;
声明一个变量存储碰撞体的偏移,并在 Start() 函数中获取碰撞体偏移;
编写移动函数 Move(),该函数接受一个移动速度参数 velocity,并根据水平输入轴(Horizontal)的值判断角色的朝向;
根据角色朝向和移动速度计算出预测的移动目标点 targetPosition;
在目标点 targetPosition 的上方发射一条向下的射线,射线只与地面层(LayerMask)碰撞,如果检测到地面,记录碰撞点作为实际目标点 actualTargetPosition;
将实际目标点 actualTargetPosition 减去实际位置点 currentPosition 得到移动方向 moveDirection,并将其归一化;
将角色刚体的速度设置为移动方向 moveDirection 乘以移动速度 velocity。
下面是基于以上思路的 C# 代码实现:
注:以下代码假设地面层的 LayerMask 值为 groundLayer。
(ai太强了,我瑟瑟发抖)