unity fixedupdate_Unity基础教程-物体运动(九)——游泳(Moving and Floating in Water

v2-8e421ca7358e83ed526fd547e49d0037_1440w.jpg?source=172ae18b

v2-208700dff5fe1e82b55938731f88bc31_b.jpg

200+篇教程总入口,欢迎收藏:

放牛的星星:[教程汇总+持续更新]Unity从入门到入坟——收藏这一篇就够了​zhuanlan.zhihu.com
v2-bdddda5a2c66383e63a14a50359d943a_180x120.jpg
本文重点内容:
1、检测水体
2、应用水的阻力和浮力
3、在水中游泳,包括水面上和水面下
4、让物体漂浮

这是关于控制角色移动的系列教程的第九部分。它让物体能够漂浮在水中并在水中移动。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。

本教程使用Unity 2019.4.1f制作。它还使用了ProBuilder包。

Unity升级我已经升级到Unity 2019.4 LTS和ProBuilder 4.2.3版本,所以一些视觉效果有所改变。

v2-bfbbfbb4bbd4a5fe405811407e6363e5_b.jpg
泳池里玩的愉快

1 水

很多游戏都有水,并且大都是可以游泳的。然而,对于交互式水没有现成的解决方案。PhysX并不直接支持它,所以我们必须自己创造一个水的近似值。

1.1 水场景

为了演示水,我创建了一个包含游泳池的场景。它有多种岸形,两个水面,两个水隧道,一座水桥,还有一些你可以在水下行走的地方。我们的水也可以在任意重力下工作,但这个场景使用简单的均匀重力。

v2-a54a5c2396abc5de1f73f5c7b8b5a47b_b.jpg
泳池

水面由具有半透明蓝色材质的单面平网格制成。从上方可见,但从下方看不到。

v2-5a3c0bce3973dfffae06f8e4260d31b6_b.jpg
水表面

水的体积必须用设置为触发器的碰撞器来描述。我在大部分的体积中使用了没有网格的盒碰撞器,比需要的尺寸稍微大一些,所以水中不会有任何缝隙。一些地方需要更复杂的ProBuilder网格来建造适当的体积。这些也必须设置为触发器,这可以通过ProBuilder窗口中的set Trigger选项来完成。注意,作为触发器的网格碰撞器必须是凸的。

而凹面网格会自动生成将其包裹起来的凸面版本,但是会导致它超出所需水体积的地方。弯曲的水桥就是一个例子,为此我制作了一个简化的凸碰撞体。

v2-be79faace6f47b61cccaa7cce39f3a60_b.jpg
水碰撞体

1.2 忽略触发器的碰撞

所有水体积对象都在Water层,应将其排除在运动球体和轨道摄影机的所有layer mask中。常规情况下,我们目前拥有的两个物理查询也仅用于常规碰撞器,而不是触发器。可以通过“Physics / Queries Hit ”项目设置来配置是否检测触发器。无论我们现在有什么,我们都不想使用代码来检测触发器,因将可以将配置明确化。

第一个查询在MovingSphere.SnapToGround中。将QueryTriggerInteraction.Ignore添加为射线投射的最终参数。

v2-8f412d4d7b2f78ea13dee28ce3293e53_b.jpg

其次,对OrbitCamera.LateUpdate中的box进行同样的操作。

v2-0e8f34fccad488316f82023ccc9e01d2_b.jpg

1.3 检测水

我们现在可以在水里移动,就好像水不存在一样。但是为了支持游泳,我们必须检测它。通过检查我们是否处于Water层上的触发区来做到这一点。首先在MovingSphere中添加一个Water Mask,以及一个swimming材质,我们将使用它来显示它在水中。

v2-5e56a1db9eedc142c1d7a3cae64fbaf3_b.jpg

v2-24537875b3be70e1e29064247b522929_b.jpg
Water mask 和swimming 材质设置

然后添加一个InWater属性,该属性指示该球体是否在水中。最初,我们将其设为简单的get / set属性,并在ClearState中将其重置为false。

v2-62e7b1636c190454cfa0cb7581928a6e_b.jpg

如果我们不攀爬,则在“Update”中选择swimming材质。

v2-e4a6aff459de090641ffd9fb139a7653_b.jpg

最后,通过添加OnTriggerEnter和OnTriggerStay方法完成对水的检测。它们像OnCollisionEnter和OnCollisionStay一样工作,但它们做用于碰撞器,并且具有Collider参数而不是Collision。两种方法都应检查碰撞器是否在Water层上,如果是,请将IsSwimming设置为true。

v2-86b67347cfb53b58e0bd8bbdba63469e_b.jpg

v2-1975473665db5df360faa859b47beed6_b.jpg
当球在水中的时候显示蓝色材质
何时调用trigger 方法?
所有on-trigger方法都在所有on-collision方法之前被调用。

2 浸入

仅仅知道我们的球体是否与水相交,还不足以使其正常游泳或漂浮。我们需要知道其中有多少被淹没了,然后我们可以用它来计算阻力和浮力。

2.1 浸入深度

让我们添加一个submergence 浮点类型的字段来跟踪球体的淹没状态。值零表示没有水接触,而值1表示完全在水下。然后更改InWater,使其仅返回浸水是否为正。在ClearState中将其设置回零。

v2-0d55c39214c78272e3e345cf3122bad8_b.jpg

更改触发器方法,以便它们调用新的EvaluateSubmergence方法,该方法现在仅将submergence 设置为1。

v2-df067a34cb9f94392b7fac051bd4c5b3_b.jpg

2.2 浸入范围

我们应该让淹没范围变为可配置化的。这样,就可以精确地控制何时球体算在水中以及何时完全浸入水中。可以从球体中心上方的偏移点开始测量,一直到最大范围。这样一来,即使我们接触水面,也可以在整个球体进入该区域之前将其完全淹没,或者完全忽略水坑之类的低水位。

v2-a03fc22481df63d83a97dd276e6e5e35_b.jpg
偏移和范围

使偏移量和范围可配置。使用0.5和1作为默认值,以匹配我们的半径0.5球体的形状。范围应为正。

v2-465ffe9c9d15f6f6bb4b646ed53586db_b.jpg

v2-ffd4d44cca946d58dc532871d7aa5f7e_b.jpg
浸入的偏移和范围

现在,我们必须使用Water Mask在EvaluateSubmergence中执行射线检测,从偏移点一直向下直至浸入范围。因为我们需要检测是否hit了水,因此请使用QueryTriggerInteraction.Collide。然后,浸入等于1减去击中距离除以范围。

v2-c3444e68973d773da567646ceaf105f2_b.jpg

要测试浸入值,为球执行临时着色。

v2-6055941814db4371975bf606911c5b8f_b.jpg

v2-f4743471a64f53a360a3900dc1aae633_b.gif
浸入 不正确

到球刚好完全浸入的那一刻都是没有问题的,但从那之后,因为我们从一个点投射的射线已经在水的碰撞器里面了,所以它会检测失败。但那也表面球体已经完全的浸入水中了,所以,如果射线没有击中任何东西的话,就设置submergence 为1。

v2-9c5dc666a1b6932c9e4a9d7d432a43d3_b.jpg

但是,由于物体位置与PhysX检测到触发时的位置不同,因此从水中移出时可能会导致无效的submergence为1,这是由于碰撞和触发方法的调用延迟所致。我们可以通过将射线的长度增加一个单位来防止这种情况。这不是完美的,但几乎可以解决所有情况,除非移动速度非常快。退出水时,这将导致submergence变为负数,这也没问题,因为这样也不算在水中。

v2-a50a054810fad83d7f0c57d7e5e34cf1_b.jpg

v2-6d96f69fdb7e6b401830f24d6f525037_b.gif
浸水,正确

现在我们可以去掉浸水的可视化了。

v2-b7993c99af4c3afb6a6a7c40a6c00fb9_b.jpg

请注意,此方法假设球体中心正下方有水。当球体碰到水体积的侧面或底部时(例如,碰到不真实的水墙时),情况可能并非如此。在这种情况下,我们需要立即进入完全淹没状态。

2.3 水阻力

在水中的运动比在陆地上更缓慢,因为水产生的阻力比空气大得多。加速度明显较慢,减速较快。让我们添加对阻力的支持,并通过添加一个water drag选项来进行配置,默认设置为1。范围从0到10是可以的,因为10会造成巨大的阻力。

v2-4d75123d25fc5561f4ee4f213b79bbdb_b.jpg

v2-a6356f1acbce192145647d77f65c3b44_b.jpg
水阻力

我们将使用简单的线性阻尼,类似于PhysX。将速度缩放1减去阻力乘以时间增量。在调用AdjustVelocity之前,请在FixedUpdate中执行此操作。我们首先应用阻力,以便始终可以加速。

v2-c0440c5ba93376ce76ba49b4f87895bc_b.jpg

请注意,这意味着如果水阻力等于1除以固定时间步长,则速度会在单个物理步长中下降为零。如果速度变大,速度将反转。由于我们将最大值设置为10,因此这不会成为问题。为了安全起见,你可以确保速度至少缩放为零。

如果我们没有完全浸没在水里,我们就不会有最大的阻力。所以需要把阻尼的浸没系数引入其中。

v2-769729e6df4966fe1d41785f37b81ac1_b.jpg

v2-7c5c413fd1b856a2dd086d55fa5d2e12_b.gif
水阻力10

2.4 浮力

水的另一个重要属性是事物倾向于将其漂浮在水中。因此,将可配置的浮力值添加到我们的球体中,最小值为零,默认值为1。想法是,浮力为零的物体像石头一样下沉,只是 被水拖慢了速度。浮力为1的对象处于平衡状态,完全消除了重力。浮力大于1的物体会浮到水面。2的浮力意味着它的上升和正常下降的速度一样快。

v2-2846ae52325db67b00270c3ee4811ca3_b.jpg

v2-1cf39d3cbbf0a33fe17aeb0290f0e412_b.jpg
浮力

我们通过检查不是攀爬但在水中并在FixedUpdate中实现这个功能。如果满足条件,则应用按1减去浮力的比例缩放的重力,再次将其考虑在内。这将覆盖重力的所有其他应用。

v2-5d8b3b73dd02d4f9313982d6faf1ab98_b.jpg

v2-c9119ff8d576e4308b11cf4ad6b074bd_b.gif
浮力1.5

请注意,实际上,向上的力随着深度的增加而增加,而在我们的例子中,一旦达到最大淹没深度,向上的力就会保持不变。这足以创造出令人信服的浮力,除非在极深的水域。

浮力似乎失败的唯一情况是球体最终距离底部太近。在这种情况下,地面弹跳被激活,抵消了浮力。如果我们在水中,我们可以通过中止SnapToGround来避免这种情况。

v2-ddc5ba676a2fadcf2a5146f2834b7010_b.jpg

3 游泳

现在我们可以在水中漂浮了,下一步就是支持游泳,其中应该包括潜水和浮潜。

3.1 游泳阈值

我们只有在水足够深的时候才能游泳,但我们不需要完全沉入水中。因此,让我们添加一个可配置的游泳阈值,它定义游泳所需的最小潜水深度。它必须大于0,所以使用0.01 1作为它的范围,0.5作为默认值。如果球体的下半部分在水下,它就能游动。还要添加一个游泳属性,指示是否达到游泳阈值。

v2-1beb845692b34799c7160074be44ac5a_b.jpg

v2-619321dddb651191a6fae223006a68c3_b.jpg
游泳阈值

调整Update,以便我们仅在游泳时使用游泳材质。

v2-0c8d79a0b3c5b4cf829e6f786e8bcfec_b.jpg

接下来,创建一个CheckSwimming方法,该方法返回我们是否正在游泳,如果是,则将地面接触计数设置为零,并使接触法线等于上轴。

v2-908be32ba9aace8ad6fac93ab87e5f96_b.jpg

检查我们是否接触地面时,在CheckClimbing之后立即在UpdateState中调用该方法。这样一来,除了攀登外,游泳优先。

v2-315cb031554f324d8ce32015db23d5df_b.jpg

然后从SnapToGround中删除检查是否在水中。当我们在水中而不是在游泳时,这使得捕捉动作再次起作用。

v2-ad2bc645e62c4402f88145cc851e1167_b.jpg

3.2 游泳速度

为游泳增加一个可配置的最大速度和加速度,默认设置为5。

v2-1858b0e0c801067b2ed14a316c5833b3_b.jpg

v2-0106d8af4e233e554ea12cb0dd83a705_b.jpg
最大的游泳速度和加速度

在AdjustVelocity中,检查攀爬后再检查是否在水中。如果是这样,请使用与通常情况相同的轴使用游泳加速度和速度。

v2-d93008938706f3d7753d99ee5e0b6b42_b.jpg

我们在水里的部分越深,就越应该依靠游泳的加速和速度而不是常规的速度。因此,我们将根据一个游泳参数在规则值和游泳值之间进行插值,这个参数就是潜水深度除以游泳阈值,其最大值被限制为1。

v2-a12b0d63d5d5075616f95c91fcac5afa_b.jpg

至于加速度是正常加速度还是空气加速度取决于我们是否在地面上。

v2-0b620674bcb1a43619339271bf93331c_b.jpg

v2-605ffb33411d5d6b630ca0be8557df42_b.gif
游泳,浮力1.1

3.3 潜水和浮潜

现在,我们可以像在地面或空中一样在游泳时移动,因此受控运动被限制在平面上。垂直运动目前由重力和浮力共同作用。为了控制垂直运动,我们需要第三个输入轴。通过将UpDown轴添加到我们的输入设置中(通过复制“Horizontal ”或“Vertical”)来支持这一点。为正按钮使用了空格(用于跳跃的键),为负按钮使用了X。然后在游泳时将playerInput字段更改为Vector3并将其Z分量设置为Update中的UpDown轴,否则设置为零。从现在开始,我们必须使用Vector3的ClampMagnitude版本。

v2-4b6c7c4649a30fa0288b2e239baf5a79_b.jpg

找到当前和新的Y速度分量,并在AdjustVelocity结束时使用它们来调整速度。这与X和Z相同,但仅在游泳时才执行。

v2-a11d55206fb03d94d843cdf0f4a87308_b.jpg

v2-284d38925d025a49ec5caa084795fcfd_b.gif
上下游泳,浮力为1

3.4 攀爬和跳跃

在水下攀爬或跳跃应该很困难。我们可以通过在游泳时忽略玩家的更新输入来拒绝这两种情况。必须明确地抑制攀爬的欲望。跳跃会重置本身。如果在下一次Update之前出现了多个物理步长,攀爬运动在游泳时仍然有可能保持活跃,但这也没关系,因为这是在过渡到游泳时发生的,所以准确的时间并不重要。为了爬出水面,玩家只需在按下攀爬按钮的同时游上去,然后攀爬就会在某个时候启动。

v2-470dee4d66d8914097ea5e72fa1046e3_b.jpg

虽然站在浅水里有跳的可能,但这还是有点困难。我们通过将跳跃速度减小1减去浸没除以游泳阈值,以最小为零来模拟这一点。

v2-ab97dd9f91bc0fcdd2fafb623963de3b_b.jpg

3.5 在流动的水中游泳

在本教程中我们不会考虑水流,但我们应该处理整体移动的水体积,因为它们是动画的,就像我们所站或攀爬的常规移动的几何体。为了使之成为可能,我们通过碰撞器来评估碰撞收敛,如果我们最终在游泳,就使用它的附着刚体作为连接体。如果在浅水区,我们会忽略它。

v2-7316bb85e6dadc1b8a86bf4fbacfc6b1_b.jpg

如果连接到水体,则不应在EvaluateCollision中将其替换为另一个水体。实际上,我们根本不需要任何连接信息,因此我们可以在游泳时跳过EvaluateCollision中的所有工作。

v2-ebc0008ebe141d0f4b974777a38a1460_b.jpg

v2-2810b201cb112d5a81734e851bada03d_b.gif
在运动的立方体“水”中,游泳加速度为10

4 漂浮物

现在我们的球体可以游泳了,如果有一些漂浮物可以互动,那就太好了。再次,我们必须自己对此进行编程,方法是将其支持添加到已经支持自定义重力的现有组件中。

4.1 浸没

像MovingSphere一样,向CustomGravityRigidbody添加可配置的浸入偏移,浸入范围,浮力,水阻和水面罩,就像MovingSphere一样,但我们不需要游泳加速度,速度或阈值。

v2-773a4b50610424832cffaa4728f4a209_b.jpg

v2-c83aaa190a945ea4d4a26f3a7f97a148_b.jpg
立方体的Submergence 设置,scale为0.25

接下来,我们需要一个submergence 字段。如果需要,可在应用重力之前在FixedUpdate的末尾将其重置为零。确定浸入时,我们还需要知道重力,因此也要在域上对其进行跟踪。

v2-cf0b3421109e3d9b52a3ab9bedd5b46d_b.jpg

然后添加所需的触发方法以及EvaluateSubmergence方法,该方法的作用与以前相同,只是我们仅在需要时才计算上轴并且不支持连接的物体。

v2-09578149d616e3a892965c07d8e87a97_b.jpg

即使漂浮在水中,物体仍然可以进入休眠状态。如果是这种情况,那么我们可以跳过评估浸没程度。因此,如果物体正在休眠,请不要在OnTriggerStay中调用EvaluateSubmergence。我们仍然在OnTriggerEnter中执行此操作,因为这样可以确保进行更改。

v2-cb3eb2d57cf1e08c7613795ee4928a42_b.jpg

4.2 漂浮

在FixedUpdate中,如果需要的话,应该应用水的阻力和浮力。在本例中,我们通过单独的AddForce调用来应用浮力,而不是将其与普通重力结合使用。

v2-d5bfec4ddf29c4604cb27482b7546c77_b.jpg

我们还将阻力应用于角速度,以使对象在漂浮时不会保持旋转。

v2-76ac95954582b166175ca25c03d4e87f_b.jpg

v2-16bd119efad9cfa44d0cfe102c7d7768_b.jpg
漂浮物

浮动对象现在可以在浮动时以任意旋转结束。通常,物体会以最轻的一面朝上的方式漂浮。我们可以通过添加可配置的浮力偏移矢量(默认设置为零)来模拟。

v2-5bd6fb54ea95dce935ad0de11ef4d164_b.jpg

然后,通过调用AddForceAtPosition而不是AddForce,在此时应用浮力而不是对象的原点,并将偏移量转换为单词空间作为新的第二个参数。

v2-df0e1f2a6e10250da1f850805fe3b520_b.jpg

由于重力和浮力现在作用于不同的点,因此它们会产生角动量,从而将偏移点推到顶部。较大的偏移会产生更强的效果,这会导致快速振荡,因此应将偏移保持较小。

v2-0d5506ba4c17a5a34f8f13ad3b992b6a_b.jpg

v2-01462124c2170c2a80c3d1648ad5612e_b.jpg
轻微的浮力偏移

4.3 和漂浮物交互

当在有漂浮物的水中游泳时,轨道摄像机会来回晃动,因为它试图停留在物体的前面。可以通过添加一个与常规图层类似的透视图层来避免这种情况,只是将轨道摄像机设置为忽略它。

v2-48a8794f80da859ab4021c2dd5ebd781_b.jpg
See-through透视层

这一层应该只用于小到可以忽略的对象,或者与很多对象交互的对象。

v2-2fe4a39cb2bb18b7187bf280424e98f8_b.gif
把漂浮物推开
当透明的物体挡住视线时,我们能让它们隐形吗?
是的,我们可以检测到它,可以用来更改对象的可视化。但是,这不是本教程的一部分。

4.4 固定漂浮物

我们目前的方法对于小的对象很有效,但是对于较大的和不统一的对象看起来就不那么好了。例如,当球体与大型浮动块相互作用时,它们应该保持更稳定。为了增加稳定性,我们必须在更大的区域内扩展浮力效应。这需要更复杂的方法,因此复制CustomGravityRigidbody并将其重命名为StableFloatingRigidbody。用偏移向量数组替换浮力偏移量。将submergence 转换成一个数组,并在Awake中创建它,其长度与偏移数组相同。

v2-b1bd8f4ec0542e94816183fe02ef3bde_b.jpg

调整EvaluateSubmergence,以便它分别评估所有浮力偏移的浸入度。

v2-7044477320328afd7594f750cb0e6503_b.jpg

然后让FixedUpdate也对每个偏移量应用阻力和浮力。阻力和浮力都必须除以偏移量,因此最大效果保持不变。对象所经历的实际效果取决于淹没总量。

v2-0316585a1e82f2c7be14f0554a8a121f_b.jpg

通常,对于任何盒子形状,四个点就足够了,除非它们很大或经常部分掉出水面。请注意,偏移量随对象缩放。同样,增加对象的质量使其更稳定。

v2-434c8fadeac6378b481da37b0040681e_b.jpg

v2-7d3346db3354f6baafeaa84abc259082_b.gif
4个稳定点的漂浮物

4.5 加速升空

如果一个点在离水面足够高的地方结束,那么它的光线投射将失败,这使得它被错误地认为完全淹没了。对于拥有多个浮力点的大型物体来说,这是一个潜在的问题,因为有些物体可能会浮在水面上,而另一部分仍在水下。结果就是最高点最终会悬浮起来。你可以通过将一个大而轻的物体部分推出水面来看到现象。

v2-508581f08c7fc502b416cd65d14ecc76_b.gif
被推离之后变为悬浮状态

问题存在的的原因是因为物体的一部分仍然接触水。为了解决这个问题,当射线投射无法检查该点本身是否在水体积之内时,我们必须执行一个额外的查询。这可以通过调用Physics.CheckSphere并将其位置和小半径(例如0.01)作为参数,然后加上遮罩和交互模式来完成。仅当该查询返回true时,我们才应将submergence 设置为1。但是,这可能会导致很多额外的查询,因此,让我们通过添加可配置的安全浮动开关将其设为可选。仅对于可以充分推入水中的大型物体才需要。

v2-49be11b88c006ac1c5b8f37337cf27eb_b.jpg

v2-ed9c9233f9f3903f04d328a901a30928_b.jpg

v2-53a1da1d6f7db64cb50f419fc8558958_b.gif
安全的浮动表现

下一章节,环境交互。

本文翻译自 Jasper Flick的系列教程

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值