目录
1 重力变量化
1.1 Up轴
1.2 点积
1.3 相对控制
1.4 对齐轨道相机
2 球形重力
2.1 自定义重力
2.2 应用自定义重力
2.3 拉向原点
2.4 推离
3 其他物体
3.1 特化刚体组件
3.2 休眠
3.3 保持唤醒
本文重点内容:
1、支持任意重力
2、自定义的向上轴
3、把所有物体拉向一个点
4、对任意物体应用重力
这是有关控制角色移动的教程系列的第五部分。它涵盖了使用自定义方法替换标准重力的方法,通过该方法,我们支持在球体上行走。
本教程是CatLikeCoding系列的一部分, 原文地址见文章底部。
本教程使用Unity 2019.2.21f1创建。另外,它还使用ProBuilder软件包。
(探索一个迷你星球)
1 重力变量化
到目前为止,我们一直使用固定的重力矢量:9.81向下。这对于大多数游戏来说已经足够了,但并不能满足所有游戏。例如,在代表行星的球体表面行走目前是不可能的。所以这次我们会添加对自定义重力的支持。
在变得复杂之前,让我们开始一个简单的重力翻转,并通过项目设置使重力矢量的Y分量为正,看看会发生什么?如下方展示,它会将其变成反重力,并使我们的球体向上掉落。
(反重力)
事实证明,我们的球体确实向上飞,但它一开始是粘着地面的。这是因为我们的代码假设重力正常并把它拉向了地面。所以我们需要改变它,这样它就可以适用于任何重力矢量。
1.1 Up轴
默认的Up轴始终等于Y轴。为了摆脱这种假设,我们必须向MovingSphere添加Up轴字段,并使用它。为了支持随时可能变化的重力,我们必须在FixedUpdate的开始处设置Up轴。它指向与重力相反的方向,因此它等于取反的归一化重力矢量。
现在,我们必须用新的Up轴替换Vector3.up的所有用法。首先,在UpdateState中,当球体处于空中时,我们将其用于接触法线。
其次,在Jump中偏向跳跃方向。
然后,还需要调整如何确定跳跃速度。方法就是抵消重力。我们使用的是重力Y分量的-2倍,但现在这不再起作用。相反,我们必须使用重力矢量的大小,而不管其方向如何。这意味着我们也必须删除减号。
最后,在SnapToGround中探查地面时,我们必须将Vector3.down替换为相反的Up轴。
1.2 点积
当需要点积时,我们也不能再直接使用法向向量的Y分量。而是需要使用向上轴和法线向量作为参数来调用Vector3.Dot。首先在SnapToGround中,检查我们是否发现地面。
然后在CheckSteepContacts中查看我们是否陷入缝隙中。
并在EvaluateCollision中检查我们有什么样的联系。
现在,无论向上哪个方向,我们的球体都可以移动。在播放模式下也可以更改重力方向,它也将立即适应新情况。
(翻转重力)
1.3 相对控制
然而,尽管将重力上下翻转没有问题,但任何其他的方向都会使控制球体变得更加困难。例如,当重力与X轴对齐时,我们只能控制沿Z轴的运动。沿着Y轴的运动是不受我们控制的,只有重力和碰撞能影响它。输入的X轴被消去了因为我们仍然在世界空间XZ平面上定义控制。所以我们需要在重力垂直的平面上定义所需的速度。
(如果重力向左的话,我们就会丢失X方向的控制权)
由于重力可以变化,我们也必须使right 轴和forward 轴相对应的做出反应。那么,为它们添加字段。
我们需要在平面上投影方向来实现这一点,所以让我们用一个更通用的ProjectDirectionOnPlane方法来代替ProjectOnContactPlane,这个方法可以处理任意法线,并在最后执行标准化。
在AdjustVelocity中使用此新方法来确定X和Z控制轴,将其输入变量轴并与法线接触。
相对重力轴是在Update中得出的。如果存在玩家输入空间,则我们将其向右和向前矢量投影在重力平面上,以找到与重力对齐的X和Z轴。否则,我们将投影世界坐标轴。现在,相对于这些轴定义了所需的速度,因此无需将输入向量转换为其他空间。
这仍然不能解决控制轴与重力对齐时被消除的问题,但是当使用轨道摄像机时,我们可以对其进行定向,以重新获得完全控制权。
(使用轨道相机)
1.4 对齐轨道相机
轨道摄像机仍然很笨拙,因为它始终将世界Y轴用作其向上方向。因此,当直接向上或向下看时,我们仍然可能消除控制轴。理想情况下,轨道摄像机将自身与重力对齐,这既直观又确保相对运动始终按预期进行。
我们使用轨道角度来控制相机的轨道并对其进行约束,以使其不会太高或太低。无论采用哪种方式,我们都希望保留此功能。这可以通过应用第二次旋转来完成,该旋转使轨道旋转与重力对齐。为此,将四元数重力对齐字段添加到OrbitCamera中,并使用标识旋转对其进行初始化。
在LateUpdate开始时,请调整对齐方式,使其与当前向上方向保持同步。为了使轨道在需要调整时不会发生不规则的变化,我们必须使用从当前路线到新路线的最小旋转。最小旋转可通过Quaternion.FromRotation找到,该旋转创建从一个方向到另一个方向的旋转。我们的则是从最后对齐的向上方向到当前的向上方向。然后,将其与当前对齐方式相乘,最后得到新的对齐方式。
轨道旋转逻辑必须保持不受重力的影响。要做到这一点,添加一个字段来单独跟踪轨道旋转。这个四元数包含了轨道角度的旋转,应该在Awake中进行初始化,设置为与初始相机旋转相同的值。可以使用链式赋值。
只有在手动或自动旋转时,才需要在LateUpdate中进行更改。look rotation然后变为重力路线乘以轨道旋转。
(轨道摄像机对齐左向重力)
这在手动调整轨道时有效,但是AutomaticRotation会失败,因为它仅在重力指向下方时有效。我们可以通过在确定正确的角度之前取消重力对齐来解决此问题。这是通过将反重力对齐方式应用于运动增量来完成的,可以通过Quaternion.Inverse方法获得该增量。
2 球形重力
我们支持任意重力,但仍然限于统一的Physics.gravity向量。如果我们想支撑球形重力并在行星上行走,那么我们必须提出一个定制的重力解决方案。
2.1 自定义重力
在本教程中,我们将使用非常简单的方法。使用公共GetGravity方法创建一个静态CustomGravity类,该方法返回给定的重力向量,并在世界空间中定位。最初,我们将返回未经修改的Physics.gravity。
当我们使用重力来确定球面和轨道摄像机的Up轴时,我们还要添加一个方便的GetUpAxis方法,再次使用position参数。
我们还可以更进一步,实现一个一次性提供这两种功能的GetGravity变体方法。让我们通过为向Up轴添加一个输出参数来实现这一点。我们通过在参数定义前面写出来来标记它。
out参数如何工作?
它的工作方式类似于Physics.Raycast,它返回是否击中某物并将相关数据放入作为输出参数提供的RaycastHit结构中。
out关键字告诉我们该方法负责正确设置参数,并替换其先前的值。不为其分配值将会产生编译器错误。
在本例中,基本原理是返回重力向量是GetGravity的主要目的,但是你也可以通过输出参数同时获得关联的向上轴。
2.2 应用自定义重力
从现在开始,我们可以依靠OrbitCamera.LateUpdate中的CustomGravity.GetUpAxis来执行重力对齐。我们将基于当前焦点进行此操作。
在MovingSphere.FixedUpdate中,我们可以基于身体的位置使用CustomGravity.GetGravity来获取重力和上轴。我们还需要自己施加引力,将其添加到最终速度作为加速度。另外,让我们将重力向量传递给Jump。
这样我们就可以在需要的时候计算出重力的大小,而不必再根据我们的位置来确定重力。
并且由于我们使用的是自定义重力,因此必须确保标准重力不会应用到球体上。我们可以通过在Awake中将主体的useGravity属性设置为false来强制执行此操作。
(在播放密室下让重力自动反转)
2.3 拉向原点
尽管我们已切换到自定义重力方法,但所有操作仍然是一样的。更改Unity的引力向量会像以前一样影响所有事物。为了使重力变为球形,我们必须进行一些额外的更改。我们将使其保持简单,并使用世界原点作为重力源的中心。因此,上轴只是指向位置的方向。相应地调整CustomGravity.GetUpAxis。
真实重力随距离变化。你离得越远,你受它的影响就越小。但我们这里会保持它的强度常数,使用单位重力矢量的Y分量。因此,我们可以通过向上扩展轴来满足。
到这已经完成了简单的球形重力所需要的全部工作。
(在半径为10的球体上漫步,重力为-9.81)
请注意,当在一颗小行星上行走和跳跃时,有可能最终停留在它周围的轨道上。你在下落,但前进的动量使你沿着表面下落,而不是朝表面下落,就像卫星一样。
(被困在了轨道上)
可以通过增加重力或行星半径,允许空气加速或通过引入使你减速的阻力来缓解这种情况。
2.4 推离
我们不必局限于现实情况。通过使重力为正,我们最终将球体推离原点,从而可以沿球体内部移动。但是,在这种情况下,我们必须翻转上轴。
(在球体内部运动)
3 其他物体
我们的球面和轨道摄像机使用自定义重力,但其他一切仍然依赖于默认重力才能下降。为了使具有刚体分量的任意对象落入原点,我们还必须对它们应用自定义重力。
3.1 特化刚体组件
我们可以扩展现有的Rigidbody组件来添加自定义重力,但是这样就很难隐藏已经配置了Rigidbody的对象。因此,我们将创建一个新的CustomGravityRigidbody组件类型,该组件类型要求存在一个body,并在其唤醒时检索对它的引用。它还要让常规重力失效。
要使物体落入原点,我们要做的就是在FixedUpdate中对其调用AddForce,并根据其位置传递自定义重力。
但是重力是加速度,因此添加ForceMode.Acceleration作为第二个参数。
(球体上有一堆小立方体)
为什么飞行的方块会抖动?
发生这种情况的原因与我们上章节演示的球体抖动一样。当相机也在移动时,对于快速移动的物体尤其明显。如果太明显,则可以使立方体插值其位置。也可以添加逻辑以仅在需要时打开插值。
3.2 休眠
每次FixedUpdate 应用重力的缺点是刚体不再进入休眠状态。PhysX在可能的时候,会让物体进入睡眠状态,有效的减少物体的计算量。因此,限制受重力影响的物体数量是个好主意。
现在,我们可以做的一件事是,通过调用body的IsSleeping方法,在FixedUpdate开始时检查body是否处于睡眠状态。如果是的话,它就处于平衡状态,我们不应该打扰它,所以马上返回。
但是它永远不会入睡,因为我们对其施加了加速。因此,我们必须首先停止加速。让我们假设,如果人体的速度非常低,它就静止了。我们将使用0.0001阈值作为其速度的平方大小。每秒0.01个单位。它比不施加重力要慢。
这是行不通的,因为物体开始时是静止的,也可能因为各种原因在空中停留一段时间。让我们添加一个float 延迟,在此期间我们假设物体是浮动的,但仍有可能下落。它总是重置为零,除非速度低于阈值。在这种情况下,我们等一秒钟再停止施加重力。如果没有足够的时间让物体移动,那么它就应该静止下来。
(红色是唤醒,黄色是浮动,灰色休眠)
请注意,我们不强迫物体自己休眠。我们将其留给PhysX。这不是支持休眠的唯一方法,但是对于大多数简单情况而言,这是简单而足够的。
为什么物体有时拒绝休眠?
这是因为PhysX一直在做微小的调整,要么改变得非常缓慢,要么在两种状态之间振荡。这可能发生在有一个接近稳定的碰撞状态下。
3.3 保持唤醒
我们的方法相当强大,但并不完美。我们做出的一个假设是,重力对于给定位置保持恒定。一旦我们停止施加重力,即使重力突然翻转,物体也会保持原样。在其他情况下,我们的假设也会失败,例如,当我们浮动但尚未休眠时,物体可能会非常缓慢地移动,或者地板可能会消失。另外,如果物体短暂存活,例如临时的碎屑,我们也不必担心休眠问题。因此,让我们可以配置是否允许物体漂浮以使其进入休眠状态。
(开启了浮动休眠设置)
下一章节,介绍复合重力。
欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。
本文翻译自 Jasper Flick的系列教程
原文地址:
https://catlikecoding.com/unity/tutorials
本文分享自微信公众号 - 壹种念头(OneDay1Idea)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。