tutorial_physics_2d - seraph526/godot-se GitHub Wiki

物理和碰撞Physics & Collision (2D)

介绍 (Introduction)

我们的世界是由实质的物质组成的。在真实的世界中,一架钢琴是不能穿过墙到房间的,需要通过门进入房间。游戏世界和现实世界一样,吃豆人不能穿过它所在的迷宫墙(虽然它能从左边走出场景,从右边回到场景上) 无论如何,移动精灵是件非常好玩的事,但是它们需要碰撞属性。所以让我们开始这个教程吧。

形状 (Shapes)

Godot中基本的2D世界基本的碰撞体是Shape2D有许多种类型的形状,它们都继承自Shape2D基类。


#create a circle
var c = CircleShape2D.new()
c.set_radius(20)

#create a box
var b = RectangleShape2D.new()
b.set_extents(Vector2(20,10))

形状的主要用途是用来检测碰撞和重叠或得到分辨率信息。形状通常是凸多边形,(除了凹多边形,凹多边形只是线段的列表,用来检测碰撞)。碰撞检测可以很容易的通过内置的函数实现:


#check if there is a collision between two shapes, each with a transform
if b.collide(b_xform,a,a_xform):
   print("OMG Collision!")

访问不同的Shape2d api,Godot会返回正常的碰撞和碰撞信息。图形间的所有碰撞和变换都可以这么做,甚至是得到contact信息,motion casting等等。

变换形状 (Transforming Shapes)

如在之前碰撞函数里看到的,在godot中,2D形状可以使用Matrix32做变换,当元件被缩,移动或旋转时,都可以做碰撞检测。唯一的限制是只能等比缩放。哪果,将圆或胶囊形状,缩放为椭圆形,就不会正常工作。这是使用SAT碰撞算法的一个限制,所以确保你使用的形状是按等比缩放的。

问题开始了

虽然这听上去不错,但实际的情况是在大部分场景中,只使用碰撞检测是不够的。在游戏开发的过程中,越来越多的问题被发现。

太多组合!

游戏有许多许多要碰撞的物体。典型的实现方式是测试每两个物体间的碰撞,如以下的方式:


for i in colliders:
    for j in colliders:
         if (i.collides(j)):
              do_collision_code()

但这种方法效率相当差。假如游戏中只有100个物体要做碰撞检测,那么每帧就会有100*100=10000次碰撞要检测,检测次数非常多。

视觉辅助

大多情况下,通过代码创建图形是不够的。如我们需要在可视化的条件下将其放置在一个精灵的上面,绘制一个碰撞多边形等等。显而易见的,我们需要节点在场景中创建合适的碰撞形状。

碰撞分辨率

假设我们解决了碰撞问题,我们可以很容易和快捷的指出哪个形状覆盖在哪个形状之上。如果有很多动态物体在四处移动,或遵循牛顿定律移动,解决多物体的碰撞从代码角度是相当困难的。

介绍:Godot的物体引擎!

为了解决上述的所有问题,Godot引入了物体引擎和碰撞引擎,而且很好的融入到了场景系统之中,它已经允许不同的程序层级。内置的物理系统可以被用来:

  • 简单的碰撞检测: 请查看 Shape2D API.
  • 场景动力学:操作形状(shapes),碰撞(collisions),粗测阶段(broadphase). 查看 Area2D,StaticBody2D,KinematicBody2D.
  • 场景物理: 作为节点的刚体和约束. 查看 RigidBody2D, 和 joint节点.

测量单位

将2D物体引擎整合到游戏中通常都会遇到一个问题,通常这些引擎都会使用米做为测量单位。Godot使用一个内置的2d物体引擎,程序上被设置为使用像素(px)作为计量单位。所以,所有的单位和默认值都是使用像素为计量单位的,这使得开发变得非常直接。

碰撞的2D物体

CollisionObject2D是所有2D碰撞物体的基类(virtual类),Area2D, StaticBody2D, KinematicBody2D and RigidBody2D都是继承自这个类。这个节点包含了一个shape列表(Shape2D)和一个相关的变换(transform).这也意味着Godot中所有的碰撞物体可在以不同的变换中使用多个形状(偏移/缩放/旋转).要记住前面提到的,非等比缩放将会导致圆形或胶囊形碰撞检测失效。

静态2D物体

在物理引擎中最简单的物体是静态物体。它提供静态碰撞,其它的物体可以和其发生碰撞,但静态物体本身不同移动或当与其他物体发生碰撞时有任何形式的交互。它只是呆在那里等待着别的物体的碰撞。 创建一个这样的静态物体还不够,因为还缺少碰撞:

从前面的观点,我们知道了CollisionObject2D派生的节点有一个内部的碰撞的形状和变换列表,但是,要怎么编辑它们?有两个特殊的节点做这件事。

碰撞检测2D图形

这是一个帮助节点,它必须为CollisionObject2D继承节点(Area2D,StaticBody2D,KinematicBody2D,RigidBody2D)的直接子节点。 此节点本身什么也不做,但当它作为上述提到的几个节点的子节点时,它为其填加了碰撞形状。可以创建任意数量的CollisionShape2D子节点,也就是说,父物体可以简单的拥有多个碰撞形状。当填加/删除/移动/编辑时,它会更新其父他点的形状列表。 运行时,虽然这些节点不存在(不可以通过get_node()的方式访问),因为他们只是编辑器的辅助,要访问运行时创建的形状,直接使用CollisionObject2D API。 这里是一个示例,场景来自platformer demo,包含一个CollisionObject2D 的子节点Area2D和coin精灵:

触发器

CollisionShape2D或CollisionPolygon2D可以被设置为触发器。在刚体或动力学物体中使用触发器时,它们将变为无碰撞的物体(物体不能和其发生碰撞)。它们只是像鬼魂一样四处移动。这在以下两种使用场景时非常有用:

  • 取消在一个特殊的形状中取消碰撞
  • 获得一个区域,只发出用物体进入或离开此区域的事件,而不发生碰撞效果

多边形2D碰撞

这和CollisionShape2D很像,除了需要设置形状,多边形可以被编辑(由用户绘制)以便决定其形状。多边形可以是凸多边形或凹多边形,两者都没关系。 现在继续我们的话题,下面的场景中有一个StaticBody2D节点,这个静态物体是sprite的子节点(也就是说,当spritet移动时,collision也会移动)。反过来,CollisionPolygon是staticbody的子节点,它填加一个碰撞形状到StaticBody2d中去。

事实上,CollisionPolygon会把当前形状分解为凸多边形(形状只能为凸多边形,还记得么?)并把它们填加到CollisionObject2D节点:

动力学2D物体

动力学物体(Kinematic)是body的一个特例,它完全由用户控制,完全不受物理引擎的影响(其它类型的物体,如character或riridbody,和静态物体是一样的。动力学物体有两个主要的用途:

  • 模拟动作:当这些物体被手动移动时,不管是通过代码还是通过动画播放器(AnimationPlayer)(运行模式被设置为fixed!),物理系统会自动计算他们的线速度和角速度。这一功能在做移动平台或通过动画播放器控制的物体时(如门,桥,旷野等等)。作为一个示例,2d/platformer demo中使用他们来移动平台。
  • 动力学角色:KinematicBody2D也有一个接口用来移动物体(move()函数),当碰撞表现被测试时。这使得在想要实现一个角色在游戏场景的效果时非常有用,但其并不需要高级的物体系统。针对这点,这里有篇特别的教程:tutorial for this exists

刚体2D物体

这类物体模拟牛顿物理系统。它有质量,摩擦,弹性和0,0坐标模拟中心质量。当需要真正的物理系统时,请使用RigidBody2D节点。刚体物体的行为受重力或其他物体的影响。 刚体通常一直处于激活状态,但是,当其最终停止,并且不再运动时,它们会进入sleep状态,直到其他物体唤醒它们。这样会节省大量的CPU. RigidBody2D节点被创建后,不断的更新变换其位置,线速度和角速度。节点不能被缩放,但是,缩放它的子节点也会正常工作。 还有一点,在游戏中十分常见,改变刚体节点的行为,使其像人物一样运动(不会旋转)。StaticBody或KinematicBody遵循不同的条件(如,被冷冻光线击中的敌人将会成为StaticBody) 最好的与RigidBody2D交互的方法是在forece integration回调函数中。此刻,物理引擎与场景同步,允许完全编辑内部参数(internal parameters)(另外,它可能运行在一个线程中,直到一下帧前是不会起作用的)。要实现上述内容,以下的函数一定要被重写:

func _integrate_forcres(state):
   [use state to change the object]

“state”参数是Physics2DDirectBodyState类型。请不要在加调函数以外的作用域使用“state”,这会导致一个错误。

通信报告

通常,RigidBody2D不会追踪通信,因为如果有大量的刚体在场景中时需要大量的内存。要得到通信报告,只要简单的增加“contacts reported”属性的值,从0到一个有意义的值(取决于你想要获得多少)。之后,这些通信的数量可以通过Physics2DDirectBodyState.get_contact_count或其他相关的函数得到。 通过信号(signals)检测通信也是可以的(信号和Area2d中的一样,下面会做描述),可以通过其布尔属性设置。

Area2D

区域在Godot物理引擎里有两种角色:

  1. 重写物体进入的空间参数(重力,重力方向,重力类型,密度等等)
  2. 检测刚体或动力学物体进入或离开区域的时机 第二种方式比较普遍。要让其工作“monitoring”属性要被开启(默认开启)。这种节点会发出两种类型信号:
#Simple, high level notification
body_enter(body:PhysicsBody2D)
body_exit(body:PhysicsBody2D)

#Low level shape-based notification
#notifies which shape specifically in both the body and area are in contact
body_enter_shape(body_id:int,body:PhysicsBody2D,body_shape_index:int,area_shape_index:idx)
body_exit_shape(body_id:int,body:PhysicsBody2D,body_shape_index:int,area_shape_index:idx)

物理全局变量

一些全局变量可以通过项目设置做更改,以便控制2D物理引擎的工作情况。

固定进程回调(Fixed Process Callback)

物理引擎可能会创建多个进程以提高效率,所以可能最高会用全帧率执行物理运算。因为这个原因,当访问物理变量如位置(position),线速度(linar velocity)等等。在当前帧正在运行哪些东西可能不具有代表性。 为了解决这一问题,Godot有一个固定进程回调(fixed processs callback),它像进程一要,但不是每个物理帧都会调用(默认60次/秒)。在这段时间内,物理引擎处于同步状态,可以被无延时访问。 开启固定进程回调,使用set_fixed_process()函数,如:

extends KinematicBody2D

func _fixed_process(delta):
   move( direction * delta )

func _ready():
   set_fixed_process(true)

射线和运动查询(Casting Rays and Motion Queries)

从代码探索场景是最常见的需求。发射射线是最常见的实现这一需求的方法。最简单的方法是使用RayCast2D节点,它每帧都会发射射线,并记录交叉点信息。 此刻,还没有高阶api接口实现这一功能。故必须直接使用物理服务。因此,要使用Physics2DDirectspaceState类。要获得它,要按照如下的步骤:

  1. 必须在_fixed_process()回调中使用,或在_integrate_forces()中使用
  2. 必须获得空间或物理服务的2D RIDs 以下的代码可以正常工作:
func _fixed_process(delta):
    var space = get_world_2d().get_space()
    var space_state = Physics2DServer.space_get_direct_state( space )

通信报告(Contact Reporting)

记住不是每一对物体都会发布通信。静态物体是消极的,当它发生碰撞时不会发出通信信息。动力学物体只有在碰撞对象为刚体(rigid body)或人物(character body)时才会发出通信。Area2D也是消极的,当碰撞的对象为另一个Area时,也不会发出通信信息。更详细的说明见下表:

当物体发生碰撞时,哪个物体会接收信息?

Type RigidBody CharacterBody KinematicBody StaticBody Area
RigidBody Both Both Both Rigidbody Area
CharacterBody Both Both Both CharacterBody Area
KinematicBody Both Both None None Area
StaticBody RigidBody CharacterBody None None None
Area Area Area Area None None