赞
踩
200+篇教程总入口,欢迎收藏:
放牛的星星:[教程汇总+持续更新]Unity从入门到入坟——收藏这一篇就够了zhuanlan.zhihu.com本文重点内容:
1、支持多重力源
2、限制重力半径
3、让重力随距离递减
4、创建平面、球形、和立方体重力源
这是有关控制角色移动的教程系列的第六部分。它通过支持多个重力源(包括平面,球体和立方体)扩展了我们的自定义重力。
本教程是CatLikeCoding系列的一部分,原文地址见文章底部。
本教程使用Unity 2019.2.21f1创建。它还使用ProBuilder软件包。
1 多重力源
如果你只需要相同的重力源,那么前面教程中介绍的方法就足够了。但是如果你需要使用不同种类的重力,或者每个场景一个不同的重力,或者在同一个场景中多个不同的重力,那么就需要更通用的方法。
1.1 默认重力源
为了在场景中支持多个重力源,我们将创建一个新的GravitySource组件类型,使用类似于CustomGravity中具有单个位置参数的公共GetGravity方法,只是在这种情况下它不是静态的。最简单的实现是仅返回Physics.gravity。将其用作默认重力源,可以将其添加到任何场景中,这将使我们的球体可以使用标准物理重力进行工作。
1.2 重力源列表
为了每个场景支持多个重力源,CustomGravity必须跟踪所有活动源。我们将为此提供一个静态的源列表。
该列表如何生效?
参见 ef="http://mp.weixin.qq.com/s?__biz=MzIxMzgzMzQxOA==&mid=2247489571&idx=1&sn=b42c2edd71018346ef18448255b49d64&chksm=97b19b86a0c61290f08e7d322820d4746ba336ebe57aa1f26ed386c7ffd827a77569bb29ebe2&scene=21#wechat_redirect">持久化对象 教程的第1.5节对通用列表的解释。
调整仅具有位置参数的GetGravity方法,以使其遍历源并累积其重力。
在另一个GetGravity方法中执行相同的操作,该方法也提供向上轴。现在我们别无选择,只能通过归一化和求反最终重力矢量来得出轴。
当只有一个来源时,我们可以优化它吗?
当然可以,但是我们不会对源的数量做任何假设。如果一次只使用一个源,那么循环就没有必要了。
GetUpAxis也必须以同样的方式进行调整。
使用列表是最好的方法吗?
如果仅同时有几个重力源处于活动状态,则列表是最简单的方法,也是最佳方法。只有在有许多资源发挥作用??时,才开始考虑更智能的空间分区策略(例如,限制体积层次结构)会更有收益。一种替代方法是使用碰撞和触发器来确定哪些对象受哪些重力源影响,但这会增加大量开销。还有一种方法是禁用离玩家太远而不会影响游戏体验的重力源。但是本教程将使其保持尽可能简单。
同时请记住,拥有多个强引力源且彼此接近的区域是不直观的。现实中我们没有这方面的经验。有几个引力源的场景可能会很有趣,但在一个有很多引力源的空间里航行可能会让人反感。
1.3 注册和反注册重力源
为了能够更改重力源,请添加公共Register和Unregister方法。他们只是在列表中添加或删除给定的重力源。
这个想法是单个来源只能注册一次,否则其效果将成倍增加。同样,仅取消注销先前已注册的源才有意义。可以多次注销和注册同一源。可以通过调用Debug.Assert来添加断言来验证这一点,以捕获编程错误。
Debug.Assert有什么作用?
如果第一个参数为false,则使用第二个参数消息(如果提供)记录断言错误。第三个参数是如果在控制台中选择了消息,则在编辑器中突出显示的内容。此调用仅包含在开发版本中,不包含在发行版本中。就好像是Debug.Assert(...); 从未出现过一样。因此,这是在开发过程中添加不会影响最终版本的检查的好方法。
我们可以通过分别在其GravitySource的OnEnable和OnDisable方法中进行注册和注销。这样,当创建,销毁,激活,停用,启用,禁用源代码以及跨编辑器热加载时,它就可以工作。
现在,我们可以将所有场景调整为再次使用默认重力,只需向每个场景添加带有GravitySource组件的游戏对象即可。
1.4 允许扩展
这个出发点是GravitySource是其他种类的重力源的基础,就像MonoBehaviour是我们创建的所有自定义组件类型的基础一样。新的重力源类型将通过使用自己的实现重写GetGravity方法来完成其工作。为了使之成为可能,我们必须声明此方法为virtual方法。
virtual关键字是什么意思?
参见 持久化对象 教程末尾的解释
2 重力平面
默认的物理引力只定义了一个矢量,代表了一个普遍向下的拉力。这个想法最简单的扩展就是定义重力平面。除了平面会将空间分成上下两部分之外,它的作用完全相同。我们可以用它来限制平面的重力范围。
2.1 重力源类型
创建一个继承自GravitySource的新GravityPlane组件类型。给它一个可配置的重力场。这是它应用于其范围内的所有物体的加速度。因此,正值表示通过重力的法向吸引力,而负值表示排斥,表示反重力。
覆盖GetGravity,使其返回指向正向上的向量,并按负配置的重力对其进行缩放。重写方法必须通过向其添加override关键字来显式完成。
现在,我们可以创建一个重力平面,如果将其配置为指向下方,则该重力平面的工作原理与默认物理重力矢量相同。
我们也可以通过使用游戏对象转换的向上矢量来支持任何方向的平面。
2.2 可视化平面
为了更容易查看平面的位置,我们将使用一些Gizmos将其可视化。如果启用了开关选项,它们将在场景窗口以及编辑器的游戏窗口中绘制。虽然平面是无限的,但我们需要使用有限的可视化效果,因此我们使用正方形。
通过添加OnDrawGizmos方法并使用Gi??zmos类的各种静态方法和属性来完成绘制Gizmos。首先将Gizmos.color设置为黄色,然后通过位于原点的Gizmos.DrawWireCube绘制线框立方体,其大小设置为(1,0,1),以使其展平为正方形。
默认情况下,Gizmos在世界空间中绘制。为了正确定位和旋转正方形,我们必须使用平面的转换矩阵,方法是将其localToWorldMatrix分配给Gizmos.matrix。这也使我们能够缩放平面对象,以便更容易看到正方形,即使这不影响平面的重力。
例如,我制作了一个小场景,包含两个相对的20x20矩形区域和伴随的重力平面,放置在它们上面和下面一点。下面的区域是正常的,而上面的区域是颠倒的。因为顶部平面旋转了,所以它也是上下颠倒的,这意味着它的重力翻转了。
我们是否需要在OnDrawGizmos中重置颜色和矩阵?
不需要,那是自动发生的。
2.3 重力范围
当两个具有相同重力的平面在相反方向上拉动时,它们会相互抵消,从而根本不会产生重力。如果我们想要一个重力在一个地方向下而在另一个地方向上的场景,则必须限制每个平面的影响。通过在GravityPlane中添加一个可配置范围来做到这一点。它表示相对于平面本身有效的重力,因此最小范围为零。平面的影响力没有极限,可以一直持续下去。
我们可以通过获取平面上矢量和该位置减去平面位置的点积来找到GetGravity中位置的距离。如果距离大于该范围,则合成重力应为零。
我们不能只切断平面上方的重力吗?
可以,但是稍后我们将使用范围逐渐减小重力,而不是使其变为二进制的有和无。
我们可以通过画第二个正方形来想象这个范围,最初是一个单位。我们给它一个青色。
但在本例中,我们想使用范围作为偏移量,而不受平面游戏对象的缩放的影响。可以通过矩阵4x4自己构造一个矩阵来实现传递位置、旋转和缩放。我们只需要用范围替换物体local scale的Y分量。
对这些平面进行了配置,以使它们的平面范围最终位于完全相同的位置。因此,它显示了重力翻转的平面。
如果范围是零,那么我们就不用费心画青色正方形了。
2.4 相机对齐速度
假设我们可以跳得足够高,现在可以在场景的一侧行走并跳到另一侧。当我们这样做时,重力会翻转,这也会突然使相机翻转,这会迷失方向。我们可以通过减慢OrbitCamera的重新对齐速度来改善重力突然变化的体验。
添加可配置的向上对齐速度,以限制相机调整其向上矢量的速度,以每秒度数表示。让我们使用360°作为默认值,这样完整的重力翻转需要半秒钟才能完成。
我们现在必须调整重力对准四元数得到的调整。首先,将代码移动到单独的UpdateGravityAlignment方法中,并使用变量跟踪当前和期望的向上向量。
我们可以通过取它们的点积,然后通过Mathf将结果转换成角度来求出向上的向量之间的夹角。Acos与Mathf.Rad2Deg相乘。最大允许的角度是按时间增量调整的对准速度。
arccos函数仅对落在-1至1范围内的输入产生有效的结果。由于精度限制,点积最终可能会超出该范围,从而产生非数字NaN值。为避免这种情况,请使用Mathf.Clamp来钳位点积结果。
如果角度足够小,那么我们可以像往常一样直接使用新的对准。否则我们必须在当前和期望的旋转之间进行插值,用最大角度除以期望角度作为插值器。我们使用Quaternion.Slerp ,它执行球面插值所以我们得到正确的旋转。
由于我们已经确保仅在必要时才进行插值,因此会保证插值器位于0–1范围内,我们可以改用SlerpUnclamped,以避免不必要的额外钳位。
插值取最短路径,但在180翻转的情况下它可以向任何方向移动。数学倾向于一个特定的方向,所以它将总是沿着相同的方向,虽然有时候看起来很奇怪。
2.5 重力消退
该平面范围的点是逐渐减小其重力,而不是突然将其切断。为了演示此方法的用处,我回到了重力盒的场景,并在盒子的六个侧面都添加了一个重力平面。我限制了它们的范围,因此盒子中间的大部分空地都没有重力。那里漂浮的任何东西要么保持静止不动,要么保持它已经拥有的动力。
由于每个平面的引力都是二进制截止的,奇怪的事情会发生在立方体的边缘和角落。重力突然以陡坡变化,很难通行。我们可以通过线性地减小重力平面的重力来改善这一点,因为距离从零到最大范围。如果位置在平面上,只要乘以1减去距离除以值域。
现在,当我们接近盒子的边缘或角落时,重力会更平滑地过渡。但是它仍然很奇怪,并且可能很难从角落中逃脱,因为三个平面的引力而被拉到那里,导致重力增加。稍后我们将提出一个更好的解决方案。
引力不应该除以距离的平方吗?
实际上,实际重力会根据距离的平方减小。但是,这假定了从其质心开始测量的大致球形重力源。这对于我们的重力平面没有意义,因为它是平坦的,无限的并且没有质心。你可以对平面进行衰减,但由于没有最大的重力范围,因此无法实现。由于该范围用于创建不真实的场景,其中不同区域仅受某些重力源的影响。衰减通常是线性还是二进制无关紧要。Linear更易于设计,这就是我所使用的。
3 重力球
现在我们有了一个功能性重力平面,让我们对球体应用相同的方法。
3.1 半径和衰减
使用可配置的重力,外半径和外衰减半径创建GravitySphere。我使用“外半径”一词,而不仅仅是“半径”,因为它表示重力达到最大强度时的距离。这不必与球体的表面半径匹配。实际上,将其扩展到足够远的位置是一个好主意,这可以让常规游戏区域能够承受恒定强度的重力。这使得设计起来容易得多。否则,你会发现在略微升高的位置进行常规跳跃可能已经将你带入太空。
在这种情况下,我们可以使用Gizmos.DrawWireSphere可视化重力,再次将黄色用作第一个阈值,将青色用作第二个阈值。如果衰减球大于外球,我们只需要显示它即可。
衰减半径小于外半径是没有意义的,因此请强制使其始终至少与Awake和OnValidate方法中的半径一样大。
3.2 应用重力
对于球体,GetGravity通过查找从位置指向球体中心的向量来工作。距离是向量的大小。如果超出外部衰减半径,则没有重力。否则,它是按配置的重力缩放的归一化向量。请注意,我们再次使用正重力表示标准拉动,而负重力会将对象推开。
我们已经计算了向量的长度,所以我们可以用配置的重力除以它来计算向量的长度,而不是对它进行归一化。
我们不能从比较平方距离开始吗?
可以,这将是一种优化,当位置最终超出范围时,会避免平方根运算。你需要在方法中平方配置的半径或将其存储在字段中。
在外半径和外衰减半径之间线性减小重力的作用类似于减小平面。我们只需要更改数学即可使其落在正确的范围内。因此,我们乘以一减去外半径的距离再除以衰减范围。该范围等于衰减半径减去外半径。将最后一位隔离在单独的衰减因子中,并将其存储在字段中,并在OnValidate中对其进行初始化。这样可以避免一些数学运算,但是我主要这么做的主要原因是,因为它使GetGravity中的代码更短。
通过衰减操作,现在可以使用多个具有重叠衰减区域的重力球,从而在它们之间进行平滑过渡。请注意,存在一个抵消区域,物体可能最终在两个球体之间绕圆轨道运行,但是当进入具有动量的区域时,很少会陷入其中。
从一个球体跳到另一个球体可能需要一些练习。特别是当强重力场在大范围内重叠时,你可能会陷入相当长的不稳定轨道。同样,当一个球体的一部分受到另一个重力源的强烈影响时,重力可能会变得有些奇怪。
3.3 倒置球体
我们还可以支持倒置重力球。但仅仅抵消重力是不够的,因为我们可能希望在球体中心有一个重力死区。这是球体的重力抵消其自身的区域,这对于正常行星和反向行星都是如此。它还可以在内部放置不受更大重力影响的另一个重力源。添加可配置的内部衰减半径和内部半径以实现此目的。让我们再次用青色和黄色可视化它们,但前提是它们大于零。
内部衰减半径的最小值为零,它设置了内部半径的最小值,而后者又设置了外部半径的最小值。还添加一个内部衰减因子,该函数与其他因子的作用相同,只是半径的顺序相反。
现在,当距离小于内部衰减半径时,我们也可以中止GetGravity。如果不是,我们还必须检查该距离是否落在内部衰减区域内,如果这样,则可以适当地缩放重力。
4 重力盒
通过完成重力盒场景结束本教程。我们将创建一个单一的盒形重力源,而不是使用六个平面使它可以在盒子内部行走。
4.1 边距
为基于方框的重力源创建一个新的重力盒类型。这个想法类似于一个球体,但是重力是直接向下拉到最近的面,而不是随着方向平滑地变化。我们需要一个可配置的重力,以及一种方式来定义盒子的形状。我们将使用边界距离向量,它有点像半径,但三个维度是分开的。
因此,这些是从中心到面的距离,这意味着盒子的大小是其边界距离的两倍。最小边界是零向量,我们可以通过Vector3.Max强制执。通过gizmo .drawwirecube用一个红色的线框立方体来可视化这个边界。
4.2 内距离
我们将再次定义一个内部区域,在该区域内重力将处于最大强度,外加一个内部区域,该区域将重力降至零,在该区域内部将不存在重力。这次的半径没有意义,因此我们将它们定义为相对于边界向内的内部距离。这三个维度的距离都相同,因此我们可以使用两个可配置的值。
两个内部距离的最大值等于最小边界距离。除此之外,内部衰减距离必须至少与内部距离一样大。
两种距离都可以通过线框立方体可视化,其大小等于边界距离减去相关距离的两倍。
4.3 衰减
计算盒子的重力衰减比球体要复杂一些,但是我们可以再次使用一个内部衰减系数。
最简单的方法是首先考虑一个单一的重力分量,它类似于一个平面。为此创建一个GetGravityComponent方法,带有两个参数。第一个是相对于盒中心的相关位置坐标。第二个是沿相关轴到最近面的距离。它返回沿同一轴的重力分量。
如果该距离大于内部衰减距离,则我们位于零重力区域,因此结果为零。否则,我们必须检查是否必须像对待球体一样减小重力。唯一的额外步骤是,如果坐标小于零,则必须翻转重力,因为这意味着我们位于中心的另一侧。
然后GetGravity方法必须使位置相对于盒的位置,并从一个零向量开始。然后计算从中心到中心的绝对距离,调用GetGravityComponent获得最小的距离,并将结果分配给向量的适当分量。其结果是,重力相对于最近的面直接向下拉,或者在重力为负的情况下向它推。
最后,为了支持任意旋转的立方体,我们必须旋转相对位置以与立方体对齐。为此,我们将其传递给其变换的InverseTransformDirection,而忽略其缩放比例。最终重力向量必须通过TransformDirection以相反的方式旋转。
这种方法的结果是,当最近的面发生变化时,重力会突然改变方向。当它漂浮在盒子里时,它工作得很好,但是在盒子里面与面之间移动就很困难了。我们将很快对此进行改进。
4.4 盒子的外部
像球体一样,我们还将支持外部距离和外部衰减距离,以及外部衰减因子。
盒子内部的重力必然会突然改变,但是在盒子外面,它会变得更加模糊。假设我们正在盒子的外面移动,被拉向盒子。如果我们经过一个面的边缘,重力仍然会拉低我们,但是如果我们继续将它与最近的边缘对齐,那么我们将跌过盒子而不是朝向盒子。这表明如果我们不直接位于表面上方的话,则应该朝最近的边缘或角落掉落。同样,应该确定相对于该边缘或面的距离,因此我们最终得到的是圆角立方体形状的区域。
没有使用Gizmos可视化圆形立方体的便捷方法,因此让我们创建一个简单的近似值。首先添加一个方法,该方法通过四个点绘制闭环,我们将使用该方法绘制矩形。
接下来,创建一个方法来绘制一个给定距离的外部立方体。给它四个矢量变量,并设置它们,以便我们在适当的距离为正确的面画一个正方形。
然后取反这些点的X坐标,以便绘制左面。
对其他四个面重复这个过程。
如果需要,在OnDrawGizmos中绘制两个外部立方体。
这向我们显示了圆形外部立方体的平坦区域,但未显示圆形边界。我们可以创建一个非常详细的可视化效果来显示它们,但这将需要大量代码。取而代之的是,在立方体的圆角点之间添加一个单独的线框立方体就足够了。这些点与边界立方体之间的距离一定,在所有三个维度上均等地偏移。因此,我们所需的距离等于边界立方体的和加上以√(1/3)缩放的相关距离。
你也可以沿圆角的边缘绘制直线,使用在两个维度上由√(1/2)缩放的距离,但是我们当前的方法就足够了。
4.5 检测侧面
现在,我们必须确定给定位置是在GetGravity的框内还是在框外。我们根据每个维度确定此数字,并计算出最终有多少外部。首先,检查位置是否超出右面。如果是这样,请将向量的X分量设置为X边界减去X位置。调整向量使其直接指向表面而不是中心。另外,增加外部计数。
如果我们不在右边,请检查我们是否在左边。如果是这样,请相应地调整矢量,这次是从负边界距离减去位置。
分别对Y和Z面执行相同的操作。
完成之后,检查我们是否在至少一个表面之外。如果是这样,则到边界的距离等于调整后的向量的长度。然后,我们可以使用与球体外相同的方法。否则,我们必须确定内部的重力。
注意,如果我们恰好在一个面外面,那么我们就在它的正上方,这意味着只有一个向量的分量是非零的。如果盒子很大,这是常见的情况。我们可以取向量分量的绝对和,这比计算任意向量的长度要快。
通过这种方法,如果我们使边界盒比表面盒小,重力就会沿着边缘和角落再次平滑地变化。在这些区域,重力也不再变得更强。你必须确保外部距离足够到达所有的角落。
现在已经可以创造出具有奇异引力的盒状行星了。注意,如果你跑得很慢,跑过一个表面会让你在一段时间内落擦落于盒状行星。这可以通过使重力盒的边界小于表面边界来减轻,当你接近边缘或角落时,表面边界会使重力提前弯曲。即使如此,你可能还是想要增加盒子的重力,以便在快速移动时紧贴表面。
下一章节,移动地面。
本文翻译自 Jasper Flick的系列教程
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。