游戏开发论坛

 找回密码
 立即注册
搜索
查看: 10288|回复: 1

Unity超级角色控制器(三):物理API分析与功能实现

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2016-1-28 14:15:13 | 显示全部楼层 |阅读模式
fholm.jpg

  GameRes游资网授权发布 文/瀚阳

  到目前为止,我讲了几个物理API,但是我们还没有谈到细节。相信机智的读者看到标题之后都已经猜到了,这就是接下来要讨论的。我们会讨论函数的可用性,分析会遇到的问题,以及相应的解决方案。

  物理API

  由于很多函数都有很多变种,所以这里就不花太多时间在物理文档上。我不打算枯燥地把所有API都讲一遍,它们之间的差别无非就是光线是否打中物体就结束之类的。

  Raycast: 打出一条指定方向与长度(也许无限远)的射线。如果有一个对象被打中了,就可以从RaycastHit结构体得到有用的信息:打中哪里,打中点表面的法线如何等等。因为它只能打出无限细小的射线,因此不会用于做碰撞处理。

  CapsuleCastAll: 第一眼看上去,这个函数用在角色控制器上是非常理想的(由于它使用的是胶囊体形状)。值得注意的是,这是一个投射,它只能检测法线面上投射的表面,背面是无法检测出来的。也就是说,投射无法检测出包围了胶囊体初始位置的物体,也就是那些与初始位置相交的对象。如果我们想将其用于角色控制器的开发,这是一个必须要攻克的缺陷。

  CheckCapsule: 这次我们有了一个解决刚刚提到的问题的候选人。CheckCapsule看上去正是可以解决CapsuleCastAll无法检测到的初始位置的问题。不幸的是,它只能返回布尔值,而不是一组碰撞体,缺少了我们所需要的信息。

  CheckSphere: 与上面一样,只是换了球形。

  Linecast: 标准的投射函数。只是换了一种定义射线初始位置、方向、长度的方式。

  OverlapSphere: 现在我们终于找到了。OverlapSphere的行为就跟名字一样。请注意文档中的这一行:

  注意:目前它只能检测出碰撞物体的包围盒,而不是真正的碰撞体。

  我真的不知道它为什么要这么写。因为我已经测试过Box Collider、Sphere Collider、Mesh Collider,用起来确实是检测出了真正的碰撞体而不是包围盒。这里我所理解的包围盒是轴对齐包围盒。我只能认为是文档错误。

  RaycastAll: 与Raycast一样,除了不会在第一个碰撞的对象就停下来之外。

  SphereCastAll:
与CapsuleCastAll一样,有着同样无法检测初始位置相交对象的缺点。SphereCast也不能保证返回正确的碰撞法线。因为这是投射一个球出去,它可以与网格的边相交。当与网格的边碰撞时,hit.normal返回的是边所连接的两个顶点的法线插值。由于CapsuleCast也只是投射一个扫略体,因此与网格的边碰撞时也是同样处理。

  除了上面提到的工具以外,Unity还提供了Rigidbody.SweepTestAll方法。经过测试,它的行为看起来与投射方法差不多。包围了面片的碰撞体无法被检测出来。比起SweepTestAll,我更倾向于使用CapsuleCastAll和SphereCastAll,因为它们有着更多的可选项(比如选择初始位置),但是SweepTest对于方形的角色很有用,因为没有BoxCast方法。

  网格碰撞体

  在我们进一步之前,我们讲讲网格碰撞体(Mesh Collider)。到目前为止,我们只研究了基础形状(Box、Sphere、Capsule等等)。但是在实际的关卡当中,关卡会用到网格碰撞体。

  与基础形状不同的是,基础形状都通过预定义的参数来定义其形状(球体的半径、盒子的高度等等),一个网格碰撞体的碰撞数据是通过3D网格来定义的。网格碰撞体主要分两类: 凸多边形与凹多边形。这篇文档很好地解释了两者的异同。

  凸多边形所有面片必须是封闭的,而且Unity限制了多边形数量上限为255,用它来表示错综复杂的关卡地形是不理想的。凹多边形可以是任意形状,但是缺点是不保证封闭。也就是说比起固体来讲,它们更多的是一块面片。这意味着我们再也不会检测出来某个对象在凸多边形面片内部。这也带来了一个相位问题。相位会在角色移动速度很快的时候发生。就是两帧之间直接就穿过去了,没有发生任何碰撞。而凸多边形使得问题更容易发生了。

1.jpg

  角色控制器一帧的运动,他的速度大的足以让他一帧就直接穿过了墙体

  假设我们就紧贴着网格碰撞体,而且法线正对着我们的方向,我们能够移动的最大距离是半径的两倍。想象一下我们碰撞处理的做法是将对象紧贴着墙面,因此这种情况很容易发生。如果你的角色大概2米高,半径为0.5米。那么角色的最大移动速度为每帧1米。如果游戏运行在30帧,那么每秒30米,每小时108千米。看上去很快,但是对于索尼克这类游戏来说还不够。

2.jpg

  因为角色紧贴着表面,不能速度快过半径两倍,否则会发生相位。

  一个方法就是让物理引擎每帧运行多次。也可以使用CapsuleCastAll来检测每一帧起点与终点之间的碰撞体。我们会在后续的文章中讲讲这部分的内容。

  初次实现

  今天我们只关注单个类,这是最简单的。我将会跳过控制器的最基础的结构体,而是进一步会深入更多的细节。

  控制器会经历三个阶段: 移动(Movement)、反推(Pushback)、处理(Resolution)。在移动阶段,我们计算所有我们角色的移动逻辑,然后相应地改变他得位置。紧接着会运行PushBack函数,来确保他没有与任何几何体发生交叉。最后,我们会做必要的处理步骤。这里包括了斜坡的处理等等的情况。

3.jpg
图中展示了控制器的移动以及反推

  在我们开始之前,要注意一下,这次不像之前的控制器,这次是使用了三个OverlapSpheres,一个个叠起来,对胶囊体进行模拟。这个控制器可以用任意多个球体–高瘦的角色可能需要不止三个,而矮圆的角色可能少于三个。现在让我们看看代码。刚开始的时候我们的控制器代码还相对简单,只有一条代码:

  1. transform.position += debugMove * Time.deltaTime;
复制代码

  这使得你可以通过inspector设置角色控制器每帧运动多远。当我们构造真正的角色的时候,这一行会被实际的移动逻辑替代。现在只是作为调试工具使用。第二阶段就是反推。这里的目标就是确定是否与其他碰撞体发生交叉,如果发生了,就将其推到最近的表面。基本的实现可以看之前的文章。而这一次,算法变得稍微复杂了一点点。前半段与之前大致相同,我们使用OverlapSphere找到某个发生碰撞的最近表面的点。接着,要看看OverlapSphere得到的法线在哪一边。我们通过从球体原点到碰撞体表面打射线来判断。由于光线投射只能检测法线朝着投射方向的表面。因此这次投射会返回true或者false,这样就知道我们的原点是在内部还是外部。值得注意的是,代码中使用的是用了很小半径的SphereCast来替代光线投射。这样就避免了光线直接打在网格线段上的情况。

4.jpg
角色的”脚”与斜坡发生OverlapSphere,找到了最近点,然后朝最近点打射向(红色箭头)

  在应用回退向量将角色推回去之前,我们还是需要做最后检查来确保我们是否与某个对象发生了碰撞。因为OverlapSphere会返回所有碰撞体,然后一个个地反推。这会使得在与多个碰撞体发生碰撞的情况下,上一个碰撞得到的反推会使得与这一个碰撞体不再交叉。我们解决这个问题的方法是检查我们球体的原点与碰撞体最近点的距离。如果距离大于半径,我们就直接沿着法线往外推,我们就认为这种情况下,我们上一个反推使得不再与这个碰撞体交叉。

5.jpg

  最下方的球体的OverlapSphere与绿色的斜坡以及蓝色的地面发生交叉,而分别的最近点为靛和蓝。斜坡的反推先起作用,使得球体不再与地面发生碰撞。

  第三阶段就不像前两个阶段那样清晰。可以认为就是清理或者反应逻辑。这里需要执行两个主要的逻辑是:坡度限制以及钳住地面。每个对Unity内建的Character Controller熟悉的同学应该都了解坡度限制:如果角色尝试向指定坡度更大的地方走去,他就会被像一堵墙一样挡住。钳住地面则是Unity角色控制器所不具备的能力,而且相当重要。当水平走过不平的路面时,控制器将不会紧贴着地面。在真实世界当中,我们通过双腿每次的轻微上下来保持平衡。但是在游戏世界中,我们需要特殊处理。与真实世界不同的是,重力不是总是作用在控制器身上。当我们没有站在平面上时,会添加向下的重力加速度。当我们在平面上时,我们设置垂直速度为0,表示平面的作用力。由于我们站在平面上的垂直速度为0,当我们走出平面时,需要时间来产生向下的速度。对于走出悬崖来说,这么做没问题,但是当我们在斜坡或者不平滑的路面行走时,会产生不真实的反弹效果。为了避免有视觉问题,在地面与非地面之间的振幅会构成逻辑问题,特别是在地面上与掉落时的差别。

6.jpg

  左边的图显示了角色在不平滑的路面移动时,钳住地面的效果。而右边则是没有钳住地面,从而导致角色很跳。每个红色的大叉都代表了向下的重力归零。

  这个问题在我们的第三阶段得到解决,就是之前提到的钳住地面,就像名字描述的那样,会通过从脚部向下SphereCast来调整角色位置。显然,有很多情况下都不希望角色钳住地面,比如起跳或者在地面比较远的上方都不能算上站在上面。你可能注意到我会常常讨论角色是否站在平面上。同时也容易注意到ProbeGround函数在主循环中调用了多次。知道角色是否在平面上是非常重要的。我不打算在控制器类中提供检测角色是否着地的接口,因为这很大程度取决于你的游戏。但是,我会提供一种查询角色脚底下有什么(以及更加丰富的信息)的接口。如何使用则取决于你,但是这个系列的下一章,我会提供一个使用这个控制器以及ProbeGround数据的角色的例子。而SlopeLimit方法应该足够简单去理解,而且我还写了不少注释(我还没正式开始,但是在提交文件之前就计划好了)。提到熟悉的功能,那些熟悉Unity控制器的同学应该看出我的控制器少了一个功能: StepOffset。我是打算处理这个问题,但是实际上比我想象的要复杂不少,也许是我还没想到一个简洁的方案。对于大多数应用来讲,这是一个必要的功能。以上就大概是我的角色控制器的内容。下次,我会用一个用到今天详细介绍的控制的例子角色,同时也会提供源代码。如果我的任何代码有问题,请与我联系,我会将其修改。

  相关阅读:

  Unity自定义角色控制器(一):碰撞检测

  Unity超级角色控制器 (二):碰撞效果的实现

5

主题

263

帖子

1113

积分

金牌会员

Rank: 6Rank: 6

积分
1113
发表于 2016-1-28 19:48:10 | 显示全部楼层
介绍不系统呢。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-20 07:58

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表