本文重点内容: 1、基于现在的速度进行加速度 2、让球视觉上滚动 3、对齐球的运动 4、移动时,和地面保持对齐
这是有关控制角色移动的教程系列的第11部分,也是最后一部分。它把我们毫无特色的球变成了滚动的球。
本教程是CatLikeCoding系列的一部分,原文地址见文章底部。
本教程是用Unity 2019.4.8f1制作的。它还使用了ProBuilder包。
修正 我在MovingSphere.FixedUpdate调换了停在地面时检查是否希望爬升并保持静止的顺序。这样可以防止由于重力而在静止站立在斜坡上时造成攀爬的情况。
(在游玩区四处滚动)
1 依赖于速度的加速度
我们当前的加速方法是相对于玩家的输入空间的,我们使用世界空间或轨道摄像机。这可以正常工作,但是在应用加速度时它将忽略球体的当前速度。当放开控件而不与X和Z控制轴笔直或对角对齐时,这会变得很明显。如果加速度不足以实现近乎瞬时的停止速度,则其自身将与最近的轴对齐。发生这种情况是因为球体沿两个轴以相同的速度减速,因此最小的分量先到达零。
当使用键而不是摇杆来控制球体时,这最为明显。为了消除输入延迟,我将 “Horizontal” 和 “Vertical” 输入键的重力和灵敏度从3增加到3000。
(轴偏差)
当用对准轨道摄像机控制球体左右移动时,同样的现象会引起剧烈的之字形运动。
(锐利的之字形)
虽然当前的控制方法有偏差,但还是很有意思,也许你会不想改变。但我们还是要看看怎么消除这种偏差。
1.1 钳位速度差
为了消除偏差,我们需要使所有维度的速度调整形成依赖。因此,我们将切换到使用调整向量,而不是孤立的旧值和新值。当我们这样做的时候,还需要交换侧向移动和垂直移动的组成部分,因此Y在MovingSphere.Update中上下移动。
接下来,从AdjustVelocity中删除当前的X和Z值,将其替换为调整向量,在这里我们直接计算沿X和Z的所需速度调整。
如果我们要游泳的话,还包括此时的Y调整。否则为零。
然后,我们不是通过独立计算X和Z的新值,而是通过最大速度变化来钳制调整向量。这将施加一次加速度并消除偏差。
现在速度变化是X轴和Z轴通过各自的调整进行缩放。
如果需要,还可以沿Y轴进行调整。
(没有轴偏差)
这种新方法也取代了尖锐的横向之字形运动与一个平滑的曲线。这是更加符合现实的,因为它使在更高的速度转弯变的更困难,但它也使控制变的不那么精确了。你可以通过增加最大加速度来弥补。
(平滑的之字形)
2 滚动的球
我们的球体通过在表面上滑动,跳跃,游泳和跌落而运动。只要球体具有统一的颜色,它在任何方向上看起来都是相同的,因此我们将无法看到它是滚动还是滑动。为了更好地了解球体的运动,我们将使其滚动。
2.1 球的子节点
为了使滚动变得明显,我们需要在球体上应用纹理。这是用于此目的的纹理。它是512×256的纹理,旨在包裹在一个球体上,中间带有箭头或类似轨道的条纹,左侧和右侧为红绿色。将其应用于我们拥有的球体材质,并将普通材质的反照率设置为白色。
(球贴图)
运动球体本身不旋转,我们将其指定为球形子对象。首先从球形预制件上移除网格渲染器和过滤器组件。
(球预制体 组件)
然后向其中添加一个球形的子对象,这是一个默认的球体,其碰撞器已删除。默认球体网格是具有默认球体UV坐标的立方体球体,因此纹理在极点处会发生严重变形。因此,我们将球绕Z轴旋转270°,将两极放在侧面,这与纹理的均匀着色的红色和绿色区域匹配。
(球子节点)
2.2 调整球的材质
从现在开始,我们必须改变球的材质,而不是球体的。为球的Transform组件添加一个配置选项到MovingSphere,并在Awake中获得它的MeshRenderer。然后在预置中连接引用。
(预制体,引用自身的ball)
我们有一个纹理球,这很明显它只会滑动。
(滑动球)
让我们将与更新球相关的所有代码放在单独的UpdateBall方法中。将材质设置代码移到此处。另外,请切换到使用条件块,因为稍后我们将根据移动模式进行更多更改。
2.3 运动
为了使球滚动,我们必须旋转它,使它的表面运动与它的运动相匹配。最简单的完美情况是一个球在一条直线上滚动。在每个时刻,球表面只有一个点接触地面。当球体向前移动时,它会旋转,一旦完成360度的旋转,同样的点就会再次接触地面。在这段时间里,点相对于球的原点做了一个圆周运动。因此,移动的距离等于这个圆的周长,也就是2π乘以球的半径。
(滚动和旋转)
这意味着我们需要知道球的半径,该半径取决于球体的大小。让我们为其添加一个配置选项,该选项必须为正,默认设置为0.5,与默认球体匹配。
(球半径)
我们在常规的每帧更新期间(在UpdateBall中)使球滚动,因为它纯粹是视觉效果。但是球体在物理步长中会移动,因此如果帧频足够高,我们可能会在两者之间发生线性运动。只要将球体的Rigidbody设置为插值就可以了。然后,我们可以通过将身体的速度除以时间增量来找到合适的运动矢量。覆盖距离是该向量的大小。这不是完美的,但视觉上就足够了。
然后,相应的旋转角度是距离乘以180,再除以π,再除以半径。为了使球滚动,我们通过Quaternion.Euler乘以球的旋转来创建该角度的旋转。最初,我们将世界X轴用作旋转轴。
(沿着固定轴旋转)
2.4 旋转轴
只要我们沿着世界Z轴前进,这种方法就行得通。为了使其适用于任何方向,我们必须从运动方向和接触法线导出旋转轴。随着接触法线在每个物理步长中被清除为零,我们必须跟踪最后一个步长。在我们清除法线之前,将其复制到一个字段。
现在我们可以在UpdateBall中通过取最后一个接触法向量和运动向量的叉乘来找到旋转轴,并对结果进行归一化。
但是,这在静止时不起作用,因此如果该帧的移动很小(例如小于0.001),则中止操作。
(向适当的方向滚动)
2.5 对齐球
球现在可以正确旋转,但是这样做的结果是其纹理可以以任意方向结束。由于其图案具有隐含的方向,让我们使球与其前进方向对齐。这需要在滚动的顶部进行额外的旋转。可以自动调整其对齐的速度,就像轨道摄像机的对齐速度一样,因此可以添加一个选项。
(球的对齐速度设置为180°)
将OrbitCamera.UpdateGravityAlignment复制到MovingSphere,将其重命名为AlignBallRotation并进行调整,使其适用于球。给它两个参数,第一个是旋转轴,第二个是球的旋转。用球的局部上轴替换重力路线,并用旋转轴替换重力。最后,将调整应用于球的旋转并将其返回。
如果对齐速度为正,则在UpdateBall中调用该方法。
(朝前滚动)
这是可行的,但是如果校准是基于移动距离而不是时间,这就更有意义。通过这种方式,直线会随着运动而减速或加速。因此,将距离传递给alignballrotate并使用它而不是时间增量。因此,配置的速度是按每移动单位的度数计算的,而不是按每秒计算的。
(基于距离对齐,对齐速度为45°)
换向时球能否保持相同的方向? 可以。你可以通过检查对齐角度是否大于90°来做到这一点。如果是这样,请在对齐之前将角度减小90°并取反旋转轴。
3 在环境里滚动
现在,我们的球在简单情况下可以适当滚动,但是我们必须考虑一些特殊情况,以使其总体上表现良好。
3.1 陡坡
当我们使用最后一个接触法线导出旋转轴时,球在空中滚动时就像在平坦的地面上一样。即使球沿墙壁滑动,也会发生这种情况。
(擦墙而过)
尽管这是正确的,但如果球体自身对齐以使其沿墙的表面滚动,则看起来会更有趣。这也暗示了有可能跳离墙。为了使之成为可能,我们还需要追踪最后的陡坡的法线。
现在我们可以使用不同的法线来确定UpdateBall中的旋转平面。默认是使用最后一个接触法线,但如果我们不是在攀爬或游泳,不是在地面上,而是在一个陡坡的表面,那么使用最后一个陡坡法线代替。
(沿墙滚动)
3.2 忽略向上的运动
当前,我们使用所有三个维度的运动来确定球的旋转和对齐方式。这意味着相对的向上和向下运动会对其产生影响。不幸的是,这可能会在向上跳跃时造成麻烦,尤其是在重力不均匀的复杂情况下。直线跳跃会导致轻微的抖动运动,从而导致球旋转不稳定。
(不稳定的跳跃)
我们可以通过在更新球时忽略相对垂直运动来减小此影响,这是通过将运动投影到旋转平面法线上并将其减去矢量来实现的。
(稳定的跳跃)
3.3 空中和游泳时旋转
如果球在表面运动时滚动是合理的,但在空中或游泳时,技术上它不需要滚动。然而,由于我们的球体是自我推进的,它总是在滚动,这是很直观的。但当不直接接触一个表面时,它的旋转就没有匹配的表面,所以我们可以让它以不同的速度旋转。
为球的空气旋转和游泳旋转添加单独的配置选项。最低速度可能为零。让我们默认将空气旋转设置为0.5,这会使球在空中旋转的速度变慢。我们将2用作默认的游泳旋转系数,因此,游泳时球似乎更努力地工作。
(空中和游泳旋转速度)
我们通过在UpdateBall中按旋转因子缩放角度来调整旋转速度。默认情况下为1,但是在游泳或不接触任何东西时,我们应使用适当的配置速度。
(不同的滚动速度)
3.4 在移动的表面滚动
使我们的球正确滚动的最后一步是使它与移动表面相结合。现在,球继承了被连接的物体的运动,从而没法正确的进行滚动了。
(滚动到移动的平台)
为了补偿表面运动,我们必须跟踪最后的连接速度。
然后我们可以在UpdateBall中从物体速度中减掉。
(相对运动)
3.5 沿着表面旋转
除了移动之外,连接的物体还可以旋转。我们在确定运动时会考虑到这一点,但是球体的对齐方式尚未受到它的影响。
(没有受到平台旋转的影响)
在这种情况下,我们可以通过根据连接物体的角速度创建一个旋转(随时间增量缩放)来使球与平台一起旋转。我们可以为此使用Rigidbody.angularVelocity属性,以弧度为单位,在将其传递给Quaternion.Euler之前,必须将其与Mathf.Rad2Deg相乘。在滚动之前,将此旋转与球的当前旋转相乘。仅当我们保持与物体的连接时才需要这样做,但是如果这样的话,当球静止不动时也必须这样做。
(沿着平台旋转)
由于这是3D旋转,因此使球继承了所连接物体的任何旋转。因此,如果表面摆动,则球也会随之摆动。
(在摇晃的平台上滚动)
运动系列到此结束。从这里开始的下一步是将球替换为更复杂的物体,例如人。这是我以后将创建的后续系列的主题。