这一节我们来讲一下“碰撞”。

什么是“碰撞”?

碰撞的本质是拥有体积的物体相遇。想想现实中的事物为什么可以发生碰撞,就是因为它们拥有一般情况下不会压缩的体积。

上一节我们说过

在现实生活中各种物品的体积大多是由它的具体外形决定的,且它们大多不规则。但是在游戏中我们使用一些近似的几何形状或几何体来表示物体的体积。在上一节中我们说过:

  • Sprite为Player提供“长相”,因为KinematicBody2D自身是没有长相的。
  • CollisionShape2D专门为节点添加碰撞形状,碰撞形状提供了一种类似体积的东西,用于检测碰撞。
  • 长相和体积可以完全一致,也可以不完全一致,所以灵活性就在于它们是分别由两个不同的节点提供的。

由于Godot中物理体的体积不是由长相也就是你赋予的Sprite的贴图形状决定,而是需要另外设定,所以我们拥有更灵活的选择。

什么是“碰撞响应”?

现实中两个物体发生碰撞后总是会有后续事情发生,而在游戏中,碰撞之后如何做,被称为“碰撞响应”。
在Godot中,很多时候,我们可以借助KinematicBody2D自身提供的几个运动和检测碰撞的方法来实现“碰撞响应”和对如何“碰撞响应”的控制。
这里你只需要记住,没有体积的物体是无法检测碰撞和进行碰撞响应的。
所以,我们从编写简单的Sprite移动到使用KinematicBody2D进行移动,是发生了质的变化。如果你只喜欢直观且简易的Sprite移动,那你就只能有长相,却无法拥有体积和碰撞,从而无法与环境进行物理交互。

碰撞检测与碰撞响应

path231.png
- 两个物理学物体发生碰撞,其本质是其CollisionShape(碰撞形状)发生碰撞,而每个物理学物体下可能有多个碰撞形状,而且它们可能是规则几何图形(例如由CollisionShape2D创建的矩形、圆形、胶囊等),或者是不规则几何图形(例如由CollisionPolygon2D创建的任意多边形等);
- 两个CollisionShape(碰撞形状)发生碰撞,会出现一个碰撞点,并由碰撞点在被碰撞形状表面与切线垂直产生一条法线(Normal);
- 经由法线我们可以获得相应的滑动、反弹或反射的方向向量。

大多数情况下我们使用KinematicBody2D也就是2D运动学物体来作为主动运动的物体,比如玩家。玩家在移动、跳跃的过程中与环境中其他的诸如地面、墙体等发生基本的体积碰撞。

2D碰撞物体

如果要单单模拟移动,可以使用Sprite和Node._process()以及Input.is_action_press()的搭配。这也是我们比较直觉可以采用的,这个搭配可以实现键盘甚至鼠标控制移动、旋转等操作。但是一旦涉及“碰撞检测”它就无能为力了。

image.png
image.png
Godot的好处就在于它的封装,我们不需要自己写碰撞检测的逻辑,Godot以节点的形式封装了专用于模拟和检测碰撞以及其他物理运动的CollisionObject2D[2D碰撞对象],它的下面又派生出Area2D[2D区域]和PhysicsBody2D[2D物理学物体],后者又分为经典的KinematicBody2D [2D运动学物体], RigidBody2D[2D刚体] , StaticBody2D[2D静态物体]。

但总的来说可以用于模拟和检测碰撞以及其他物理运动的2D节点就是四个:

Area2D 2D区域 一个运行时不可见的区域,可以检测其他Area2D、KinematicBody2D、RigidBody2D和StaticBody2D进入、重叠和退出等。
KinematicBody2D 2D运动学物体 一个自己没有长相,只提供了运动和碰撞模拟与检测相关方法的2D节点,可以用脚本进行运动的控制
RigidBody2D 2D刚体 一个不能直接由脚本控制运动,会通过你设定的质量和系统重力参数、其他物理学物体的碰撞所施加的外力、物体接触表面之间的摩擦等等模拟类似真实物理运动效果的2D节点。同样自己没有长相。
有四种模式:刚性、静态、角色和运动。
StaticBody2D 2D静态物体 代表现实中不能被移动的物体的2D节点

2D碰撞形状与2D形状

image.png 与2D碰撞形状有关的节点有CollisionShape2D和CollisionPolygon2D两个,它们以2D物理体子节点的形式提供碰撞形状。
CollisionShape2D需要为其shape(形状)属性设置一个Shape2D的具体子类型资源,才能提供碰撞形状。
image.png 而CollisionPolygon2D需要手绘一个多边形,赋予其polygon(多边形)属性。

处理运动

之前的几节中,我们都使用_process()帧处理过程来处理运动,但是在实际的游戏设计中,涉及运动应该采用_physics_process()也就是物理帧处理过程。因为前者每帧的间隔时间delta是与设备渲染时间有关的,不恒定,而后者是恒定的。对于单击游戏这或许没有多大的关系,但是如果是多人游戏,则使用_process()进行运动处理就可能出现由于玩家设备性能好坏而导致运动和操作上的不公平。

官方对处理KinematicBody2D运动的建议

image.pngimage.pngimage.png
- 使用_physics_process()而不是_process()
- 使用move_and_slide()时不要给速度向量乘以delta

Node._process()和Node._physics_process()初学者往往分不清该使用哪个,但是单单凭直觉“physics”一词,代表“物理”,就说明所有涉及物理运动(主要是碰撞)的模拟都应该用Node._physics_process(),而不涉及的都应该用一般的Node._process()。

二维向量

由于使用二维向量来表示位置信息,而运动基于位移,也就是二维向量的变化,所以处理运动必须涉及二维向量的内容。其实之前的几节实例中我们已经接触了二维向量Vector2和一些相关的方法来实现功能。

输入

游戏是人机交互工程,是需要用户输入才能不断响应的程序。在处理运动时,也是需要处理用户输入的。

总结

由以上的认识我们可以得出,学习和实现Godot的2D运动与碰撞,正确的搭配应该是:

Node._physics_process() 物理学过程 提供与设备无关的Delta,用于物理学物体和其运动的逐帧执行
Input.is_action_press() 动作被按下 结合项目设置中的InputMap或者脚本中的InputMap定义的Action来进行鼠标或键盘以及游戏手柄等的输入的处理
Vector2 二维向量 用于创建和代表二维向量,自身提供了很多方法来进行二维向量运算
4种CollisionObject2D 4种2D碰撞对象 提供了各种模拟物理运动或特性(比如摩擦、碰撞)的属性以及可用的方法

本节只是浅要的概述了一下物理体和碰撞的世界,实际要掌握牵涉甚多,这也是Godot学习中学习曲线陡然上升的第一个高峰。但是一旦您越过这个高峰,就会发现山的外面还有更高的山峰。狗头保命~说句实在话Godot并不简单,真的能做出游戏更不容易。