Python 物理引擎pymunk最完整教程(中)

上一篇文章:Python 物理引擎pymunk最完整教程(上)
总目录:https://gitee.com/python_zzy/csdn-articles/blob/master/pymunk/README.md

5 形状

本章介绍形状类(pymunk.Shape)以及相关子类。

5.1 基本形状

pymunk支持的形状包括圆形(pymunk.Circle)、线段(pymunk.Segment)、多边形(pymunk.Poly),它们都继承于形状类(pymunk.Shape)。

pymunk.Circle(body: Body | None, radius: float, offset: Tuple[float, float] = (0, 0))
pymunk.Segment(body: Body | None, a: Tuple[float, float], b: Tuple[float, float], radius: float)
pymunk.Poly(body: Body | None, vertices: Sequence[Tuple[float, float]], transform: Transform | None = None, radius: float = 0)

创建一个Circle(圆形)对象需要三个参数,其对应的身体对象,半径,偏移量(可选)。创建一个身体对象时body属性可以设置为None,但是把身体加入空间之前必须通过Shape.body属性为其指定一个Body对象。radius表示半径,即圆形的半径。偏移量offset是圆形相对于其对应Body对象的position属性在x,y方向上的位移;如果不设置offset,那么圆心处位于Body.position。

创建一个Segment(线段)对象需要的参数分别是:其对应的身体对象,线段的起始点坐标(是局部坐标,相对于Body.position),线段的终点坐标(局部坐标),线段的半径(可选)。这个半径比较特殊,相当于是给线段赋予了宽度,如果Segment的半径设为10,你可以想象这个线段是一个半径为10的圆的圆心从线段起始点移动到线段终点所留下的轨迹。例如下面这个线段的效果是这样的(窗口宽度为1000像素):

shape = pymunk.Segment(body, (10, 10), (1000 - 10, 10), 10)


创建一个Poly(多边形)对象时,需要提供这些参数:其对应的Body对象,多边形各个顶点的坐标的列表(局部坐标),形状的变换参数(后文详解),多边形的半径。绘制多边形时,会将提供的各个点的坐标按列表里的顺序连接形成一个多边形。多边形的半径和线段的半径类似,可以将半径为radius的多边形理解为一个半径等于radius参数的圆围绕多边形的边缘运动一圈留下的轨迹(当然还包括多边形内部的区域)。
创建Poly时需要注意:如果你创建的多边形是一条线段,那么应使用Segment而不是Poly,否则,如果这个Poly是动态的身体那么会报错。

5.2 摩擦系数

如果物体和物体发生碰撞,例如一个物体在地面上滑动,此时就需要考虑摩擦力的因素。下面的代码是一个没有摩擦力的物体滑动示例。

import pymunk

space = pymunk.Space()
space.gravity = (0, 1000)

b = pymunk.Body()
b.position = (100, 100)
shape = pymunk.Poly(b, [(0, 30), (30, 30), (30, 0), (0, 0)]) # 创建一个正方形
shape.mass = 1
space.add(b, shape)

b2 = pymunk.Body(body_type=pymunk.Body.STATIC) # 地面是静态的
b2.position = (0, 550)
shape2 = pymunk.Segment(b2, (0, 0), (1000, 0), 1) # 创建一条线段表示地面
shape2.mass = 1
space.add(b2, shape2)

b.apply_impulse_at_local_point((50, 0), (0, 0)) # 给物体施加冲量使其运动

if __name__ == "__main__":
    import util
    util.run(space)

运行代码后,可见正方形落到了地面上,向右匀速滑动。
需要注意的是,代码中的Segment表示创建一条线段,这条线段的Body必须是静态(STATIC)的,否则它也会随着重力而下落。使用Poly这里创建的是一个正方形。此处不使用Circle形状作为示例是因为Circle在有摩擦力的情况下会发生滚动,而正方形就不会。
一般形状是没有摩擦力的,正方形会一直运动下去直到从线段上掉下去。我们可以通过形状对象的friction属性设置形状表面的摩擦系数。摩擦系数是一个非负数,0表示没有摩擦力。
在上面的示例中加入下面两行代码:

shape.friction = 0.05
shape2.friction = 0.15 # 设置摩擦系数

运行代码后,发现正方形减速滑动了一段距离后最终停止。
需要注意的是,设置摩擦系数时两个发生摩擦的形状都应该有大于0的摩擦系数。如果一方是绝对光滑的(摩擦系数为0),而另一方是粗糙的,那么物体仍然不会受到摩擦力的影响。
如果将正方形换成一个圆形的形状,会发现粗糙圆形在粗糙线段的表面发生了滚动。

5.3 弹性系数

物体撞击到地面后可能会被弹起,实现这样的功能需要设置物体的弹性系数。形状对象的elasticity属性表示了物体的弹性系数。和摩擦系数类似,只有两个发生碰撞的形状都具有弹性系数时,一方才能被弹起。

import pymunk

space = pymunk.Space()
space.gravity = (0, 1000)

b = pymunk.Body()
b.position = (100, 100)
shape = pymunk.Poly(b, [(0, 30), (30, 30), (30, 0), (0, 0)]) # 创建一个正方形
shape.mass = 1
space.add(b, shape)

b2 = pymunk.Body(body_type=pymunk.Body.STATIC) # 地面是静态的
b2.position = (0, 550)
shape2 = pymunk.Segment(b2, (0, 0), (1000, 0), 1) # 创建一条线段表示地面
shape2.mass = 1
space.add(b2, shape2)

shape.elasticity = 0.8
shape2.elasticity = 1 # 设置弹性系数

if __name__ == "__main__":
    import util
    util.run(space)

运行后,物体下落后在地面处发生反弹。
如果将发生碰撞的两个物体的弹性系数都设为1,那么物体在某一高度下落后将会以相同的高度弹回去。

5.4 形状变换

pymunk支持对多边形对象Poly进行缩放、旋转之类的变换操作,这些变换操作都以矩阵的形式进行。矩阵通过乘法运算可以将不同的变换操作组合起来,例如一个用于缩放的矩阵和一个用于旋转的矩阵相乘可以得到一个新的矩阵,这个矩阵同时有缩放和旋转效果。有兴趣的读者可以自行了解矩阵仿射变换:wiki百科

pymunk.Transform用于创建一个变换矩阵。如果不提供任何参数,得到的是一个恒等变换的矩阵(相当于数字1,任何变换矩阵乘以恒等变换矩阵都得到它本身,相当于没有变换效果的一个矩阵)。

如果要创建使形状移动的变换矩阵,可以用Transform.translated方法。这个方法需要提供两个参数:移动的x坐标和y坐标,并返回一个新的矩阵。类似地,还有Transform.scaled方法,需要提供一个浮点数表示形状的缩放比例。Transform.rotated则表示旋转一定角度,单位为弧度。

使用变换矩阵时,将变换矩阵对象传递给Poly类的transform参数。

t = Transform().rotated(3.14 / 4).scaled(1.5) # 旋转90°,大小缩放为原来的1.5倍
shape = pymunk.Poly(body, [(0, 0), (0, 100), (100, 100), (100, 0)], transform=t)

最终创建出的Poly对象效果如下:

如果是不同的变换矩阵想要进行组合,可以使用矩阵乘法运算符:@。例如:new_transform = transform1 @ transform2

5.5 Shape对象属性参考

class pymunk.Shape(shape: Shape) → None
pymunk形状基类
class pymunk.Circle(body: Body | None, radius: float, offset: Tuple[float, float] = (0, 0))
创建圆形。body表示其对应的身体,radius表示半径,offset表示形状相对于Body位置的位移。
class pymunk.Poly(body: Body | None, vertices: Sequence[Tuple[float, float]], transform: Transform | None = None, radius: float = 0)
创建多边形。body表示其对应的身体,vertices表示多边形各个顶点的局部坐标的列表,transform表示变换参数,radius表示半径。
class pymunk.Segment(body: Body | None, a: Tuple[float, float], b: Tuple[float, float], radius: float)
创建线段。body表示其对应的身体,a和b分别表示线段的起点和终点的局部坐标,radius表示半径。
property area: float
获取形状的面积大小。
property bb: BB
获取形状的边界框,返回一个边界框对象
property body: Body | None
形状关联的身体
cache_bb() → BB
更新并返回形状的边界框
property center_of_gravity: Vec2d
获取形状的重心。这个重心和Body.center_of_gravity不一样,这里的重心是pymunk通过计算得到的,Body.center_of_gravity是由自己设置的。你可能会调用body.center_of_gravity = shape.center_of_gravity来确定物体的重心。
property collision_type: int
碰撞种类(详见后文)
copy() → T
复制形状(是深层复制,即deep copy)
property density: float
形状的密度。设置后自动计算形状的质量和惯性矩。
property elasticity: float
弹性系数。0表示没有弹性,1表示完美反弹(反弹高度和下落高度相等)
property filter: ShapeFilter
碰撞过滤器(详见后文)
property friction: float
摩擦系数。0表示没有摩擦力。
property mass: float
物体质量。设置后自动计算Body的惯性矩。
property moment: float
物体惯性矩。是通过计算得到的,不需要设置。
point_query(p: Tuple[float, float]) → PointQueryInfo
对某个坐标点进行查询(详见后文)
segment_query(start: Tuple[float, float], end: Tuple[float, float], radius: float = 0) → SegmentQueryInfo
对某个线段进行查询,start和end分别表示线段的起始点坐标(详见后文)
property sensor: bool
形状是否为传感器。传感器只会调用碰撞时的回调函数,而不会产生真正的碰撞。(详见后文)
shapes_collide(b: Shape) → ContactPointSet
获取一个形状与另一个形状的碰撞信息(详见后文)
property space: Space | None
获取形状关联的空间
property surface_velocity: Vec2d
形状的表面速度。当其他形状与这个形状发生摩擦时(必须存在摩擦系数),其他形状会获得这个形状的表面速度。这可以被用于制作传送带或者玩家在地面上移动之类的功能。
update(transform: Transform) → BB
返回一个形状变换后的边界框。
Circle.unsafe_set_offset(o: Tuple[float, float]) → None
设置圆形的偏移位置(相对于Body.position的位移)。这个方法是“不安全”的,因为它的作用相当于直接让一个东西瞬移,是在现实物理世界中不可能实现的,如果使用不当可能会模拟出一些奇怪的物理现象。
Circle.unsafe_set_radius(r: float) → None
改变圆形的半径。(不安全,理由同上)
property Circle.offset: Vec2d
圆形的偏移位置
property Circle.radius: float
圆形的半径
property Segment.a: Vec2d
线段的起始点
property Segment.b: Vec2d
线段的终点
property Segment.normal: Vec2d
线段的法线
property Segment.radius: float
线段的半径(厚度)
Segment.set_neighbors(prev: Tuple[float, float], next: Tuple[float, float]) → None
设置prev和next来设定线段的端点坐标来将多个线段连接起来。pref和next都是空间坐标
Segment.unsafe_set_endpoints(a: Tuple[float, float], b: Tuple[float, float]) → None
更改线段的起点和终点。
Segment.unsafe_set_radius(r: float) → None
更改线段的半径(厚度)。
static Poly.create_box(body: Body | None, size: Tuple[float, float] = (10, 10), radius: float = 0) → Poly
静态方法,用于方便地创建一个矩形的Poly对象,且中心点位于身体的重心。size表示矩形尺寸。
static Poly.create_box_bb(body: Body | None, bb: BB, radius: float = 0) → Poly
静态方法,用于方便地创建一个矩形的Poly对象(通过边界框对象创建)
Poly.get_vertices() → List[Vec2d]
获取多边形各顶点的局部坐标(也就是创建Poly时传递的verticles参数)
property Poly.radius: float
矩形的半径
Poly.unsafe_set_radius(radius: float) → None
更改多边形的半径
Poly.unsafe_set_vertices(vertices: Sequence[Tuple[float, float]], transform: Transform | None = None) → None
更改多边形的各顶点坐标和变换参数。

5.6 Transform对象属性参考

class pymunk.Transform(a: float = 1, b: float = 0, c: float = 0, d: float = 1, tx: float = 0, ty: float = 0)
创建一个变换矩阵,各参数表示矩阵的各个值。也可以通过Transform属性的形式访问这些值。矩阵形式如下:
[ a c t x b d t y 0 0 1 ] \begin{bmatrix} a & c & tx \\ b & d & ty \\ 0 & 0 & 1 \end{bmatrix} ab0cd0txty1
矩阵可以通过矩阵乘法运算符"@"进行乘法操作。
static identity() → Transform
创建一个如下的恒等变换的矩阵。(虽然恒等变换是“变换”,但其实没有任何效果)
[ 1 0 0 0 1 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} 100010001
static rotation(t: float) → Transform
创建一个如下的旋转矩阵。t是旋转度数(弧度)
[ c o s ( t ) − s i n ( t ) 0 s i n t ( t ) c o s ( t ) 0 0 0 1 ] \begin{bmatrix} cos(t) & -sin(t) & 0 \\ sint(t) & cos(t) & 0 \\ 0 & 0 & 1 \end{bmatrix} cos(t)sint(t)0sin(t)cos(t)0001
static scaling(s: float) → Transform
创建一个如下的缩放矩阵。s是缩放的比例。
[ s 0 0 0 s 0 0 0 1 ] \begin{bmatrix} s & 0 & 0 \\ 0 & s & 0 \\ 0 & 0 & 1 \end{bmatrix} s000s0001
static translation(x: float, y: float) → Transform
创建一个如下的移动矩阵。x和y是横方向和纵方向的位移。
[ 1 0 x 0 1 y 0 0 1 ] \begin{bmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \end{bmatrix} 100010xy1
rotated(t: float) → Transform
旋转矩阵
scaled(s: float) → Transform
缩放矩阵
translated(x: float, y: float) → Transform
移动矩阵

6 约束

物理引擎中可能有时候需要实现“机械臂”、两个相连的物体之类的效果。此时可以在物体之间施加“约束”,使得物体不能随意变化。pymunk.constraints模块用于实现这个功能。通常不需要直接导入这个模块,因为这个模块内的所有内容已经被导入到pymunk主模块中。

6.1 创建PinJoint约束

约束类都继承于pymunk.constraints.Constraint类。其中最简单的一种约束PinJoint类,用于实现把两个物体用一根“棍子”连接的效果。
PinJoint需要以下参数:需要进行约束的两个身体对象,两个约束点的位置(局部坐标)。创建完约束后,需要将约束对象添加到空间中。示例如下:

import pymunk

space = pymunk.Space()
space.gravity = (0, 200) # 创建空间并设置重力

def create_circle(position, radius=20):
    '''创建一个圆形,并返回body对象'''
    body = pymunk.Body() # 创建动态身体
    body.position = position # 设置身体的位置

    shape = pymunk.Circle(body, radius) # 创建圆形
    shape.mass = 1 # 质量
    shape.friction = 0.7 # 摩擦力

    space.add(body, shape) # 添加到空间中
    return body

b1 = create_circle((100, 100))
b2 = create_circle((200, 100)) # 创建两个圆形
c = pymunk.PinJoint(b1, b2, (20, 0), (-20, 0)) # 创建两个圆形的约束,约束点位于b1的右侧和b2的左侧
space.add(c) # 将约束添加到空间中

b1.apply_impulse_at_local_point((50, 50), (0, 0)) # 对物体施加脉冲使其运动

body = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
body.position = (0, 550)
shape = pymunk.Segment(body, (0, 0), (1000, 0), 3)
space.add(body, shape)

if __name__ == "__main__":
    import util
    util.run(space)

在这里插入图片描述

运行后,创建出两个圆形,施加了PinJoint约束。可以看到无论两个物体如何移动,中间像是连接了一根棍子一样,设置的两个约束点之间的距离不会发生改变。由于圆形的半径是20,设置的两个约束点的位置是(20, 0)和(-20, 0),因此表示的就是用PinJoint式的约束将第一个圆形的最右边和第二个圆形的最左边连接起来。不要忘记的是,约束对象必须被Space.add方法附加到空间。

6.2 更多约束类

除了PinJoint约束,还有一些其他的约束类。读者可以在pymunk.examples.constraints中查看不同的约束效果。

SlideJoint与PinJoint约束类似,不过用于约束的“棍子”长度是可以收缩的,可以设置约束点之间距离的最大值和最小值。下面展示的动图示例均是pymunk.examples.constraints中的示例,与6.1的示例代码不同。
在这里插入图片描述

c = pymunk.SlideJoint(b1, b2, (20, 0), (-20, 0), 40, 80) # 约束点间距范围40-80

除了被约束的身体b1和b2,PivotJoint约束只需要提供一个使用世界坐标的约束点的坐标作为参数,被约束的两个物体和这个点的距离是固定不变的。
在这里插入图片描述

c = pymunk.PivotJoint(b1, b2, (150, 110)) # (150, 110)为附加的约束点,使用世界坐标

GrooveJoint需要提供一条线段起点和终点坐标,以及另一个点坐标作为参数,线段起点终点坐标是相对于第一个身体的局部坐标,另一个点的坐标是相对于第二个身体的局部坐标。提供的线段用于约束第一个身体,提供的点用于约束第二个身体。第二个身体的约束点将始终在提供的线段上运动。
在这里插入图片描述

c = pymunk.GrooveJoint(b1, b2, (50, 50), (50, -50), (-50, 0))
# 前两个参数表示线段的起点和终点,第三个参数表示点

DampedSpring用于模拟弹簧的效果。创建一个DampedSpring需要提供弹簧的两个约束点(和PinJoint类似),还要依次提供以下参数:弹簧的原始长度(弹簧位于原始长度时不发生形变,不会对弹簧两侧的物体施加力),弹簧系数(杨氏模量,描述弹簧发生形变的容易程度,值越大越不容易形变),弹簧的阻尼系数(阻尼越大,使弹簧发生变化需要施加的力越大,弹簧的伸缩、压缩、恢复形状都会变得困难)
在这里插入图片描述

c = pymunk.DampedSpring(b1, b2, (30, 0), (-30, 0), 20, 5, 0.3)

DampedRotarySpring效果和弹簧类似,但是以角度的形式工作。弹簧的长度在这里变成了两个形状的相对旋转角度(两个形状的angle属性之差)。例如设置两个形状的相对旋转角度为90°(3.14/2弧度),那么当这两个形状的相对旋转角度不为90°时,就会像弹簧收缩一样进行旋转,直到相对旋转角度为90°。DampedRotarySpring需要的参数:正常的相对旋转角度,弹簧系数,弹簧的阻尼系数。由于相对旋转角度以弧度为单位,在数值上往往比较小,所以设置的弹簧系数和阻尼系数一般要比DampedSpring更大。
为了方便显示旋转角度,下面的示例使用了pymunk.Segment,且固定了线段的支点。
请添加图片描述

c = pymunk.DampedRotarySpring(b1, b2, 0, 3000, 60) 
# 相对角度保持0弧度

DampedRotarySpring在两个物体相对旋转角度不同时会反复旋转调整直到角度一致,而下面介绍的RotaryLimitJoint则始终将两个物体的相对角度固定在一个范围内。需要提供物体间的最小相对角度和最大相对角度作为参数。如果两个参数相同,那么物体间的相对角度就是一个固定的值。
在这里插入图片描述

c = pymunk.RotaryLimitJoint(b1, b2, math.pi / 2, math.pi / 2)
# 相对角度始终保持为90度

RatchetJoint实现类似于“扳手套筒”的棘轮效果,这个概念可能不是很好理解。创建一个RatchetJoint需要提供两个参数,第一个是角度的初始偏移,第二个是棘轮的锯齿的圆心角度数(弧度)。例如锯齿的圆心角为90°(3.14/2弧度),它能实现这样一个效果:每当b1物体按正方向旋转90°了时,按照同一方向旋转b2物体;最后会停止,停止后b1与b2物体的相对旋转角度总是保持为90°的倍数。如果按相反的方向旋转b1物体,b2物体则不会旋转。如果要让棘轮的旋转方向变成与之前相反的,可以把3.14/2的圆心角度数改为-3.14/2,变成负数。
如果设置了初始角度偏移(单位为弧度,一般设为0),那么b1在计算旋转角度时会减去这个偏移量;例如设为3.14/3,那么当b1旋转了3.14/3时,在RatchetJoint的计算时会认为b1旋转角度为0。
在这里插入图片描述

c = pymunk.RatchetJoint(b1, b2, 0, math.pi/2)

GearJoint需要提供两个参数,初始角度偏移和角速度比例作为参数。它能保证两个身体的旋转速度比例不变。例如角速度比例设为2,那么b1旋转的角速度始终是b2角速度的2倍。
请添加图片描述

c = pymunk.GearJoint(b1, b2, 0, 2)
# 控制b1角速度为b2的2倍

GearJoint控制两个物体的角速度的比值不变,而下面的SimpleMotor用于控制两个物体的角速度之差不变,也就是控制相对角速度不变。SimpleMotor只需要一个参数表示控制的两个物体的相对角速度。
请添加图片描述

c = pymunk.SimpleMotor(b1, b2, math.pi)
# 相对角速度为180度每秒

6.3 Constraint对象属性参考

class pymunk.constraints.Constraint(constraint: pymunk._chipmunk.ffi.CData)
约束基类
class pymunk.constraints.PinJoint(a: Body, b: Body, anchor_a: Tuple[float, float] = (0, 0), anchor_b: Tuple[float, float] = (0, 0))
使物体的两个锚点(约束点,使用局部坐标)之间距离固定不变。anchor_a和anchor_b为两个锚点。这些参数也作为对象的属性。
class pymunk.constraints.SlideJoint(a: Body, b: Body, anchor_a: Tuple[float, float], anchor_b: Tuple[float, float], min: float, max: float)
使物体两个锚点之间的距离控制在一定范围内,可以用于制作链条这样的模型。anchor_a和anchor_b为两个锚点(局部坐标)。min和max分别是锚点间距的最小值和最大值。这些参数也作为对象的属性。
class pymunk.constraints.PivotJoint(a: Body, b: Body, *args: Tuple[float, float] | Tuple[Tuple[float, float], Tuple[float, float]])
使两个物体的两个锚点距离控制为0(或者只有一个锚点)。如果args只提供一个2D向量作为参数,那么表示一个世界坐标,物体控制与这个坐标的距离不变(这个锚点会变,但是物体与锚点距离始终为创建PivotJoint时物体与锚点之间的距离);如果提供两个2D向量作为锚点参数,那么表示两个局部坐标,分别相对于a, b两个身体,pymunk会控制这两个点的距离为0
class pymunk.constraints.GrooveJoint(a: Body, b: Body, groove_a: Tuple[float, float], groove_b: Tuple[float, float], anchor_b: Tuple[float, float])
使一个物体沿着另一个物体的某个轴滑动。groove_a和groove_b是相对于身体a的局部坐标,表示一条线段的起点和终点。anchor_b是相对于身体b的锚点。身体b的锚点会被控制在线段上运动。这些参数也作为对象的属性。
class pymunk.constraints.DampedSpring(a: Body, b: Body, anchor_a: Tuple[float, float], anchor_b: Tuple[float, float], rest_length: float, stiffness: float, damping: float)
实现阻尼弹簧的效果。anchor_a和anchor_b是弹簧的两侧连接的点,分别是相对于身体a和b的两个局部坐标。rest_length是弹簧的原长度,stiffness表示弹簧系数(杨氏模量,描述弹簧发生形变有多困难),damping表示弹簧的阻尼系数。这些参数也作为对象的属性。
class pymunk.constraints.DampedRotarySpring(a: Body, b: Body, rest_angle: float, stiffness: float, damping: float)
和DampedSpring类似,但是弹簧的效果以相对旋转角度的形式呈现。当身体a, b的相对旋转角度不为rest_angle时,身体会旋转进行调整直到恢复原位。提供的这些参数也作为对象的属性。
class pymunk.constraints.RotaryLimitJoint(a: Body, b: Body, min: float, max: float)
控制两个物体的相对角度在一定范围内。min为相对角度的最小值,max为相对角度的最大值。这些参数也作为对象的属性。
class pymunk.constraints.RatchetJoint(a: Body, b: Body, phase: float, ratchet: float)
旋转棘轮,工作原理类似于套筒扳手。phase表示初始角度偏移,ratchet表示棘轮齿的圆心角度数。这些参数也作为对象的属性。
class pymunk.constraints.GearJoint(a: Body, b: Body, phase: float, ratio: float)
控制两个身体的角速度之比不变,始终为ratio。phase表示初始角度偏移,ratio表示角速度之比。这些参数也作为对象的属性。
class pymunk.constraints.SimpleMotor(a: Body, b: Body, rate: float)
控制两个物体的相对角速度不变,始终为rate。这些参数也作为对象的属性。
property a: Body
property b: Body
创建约束时,提供的两个被约束身体。
property collide_bodies: bool
与此约束关联的物体是否参与碰撞检测,默认为True。
copy() → T
复制约束(深层复制)
property error_bias: float
关节误差在一秒后仍未固定的百分比。这与空间的碰撞偏差属性完全相同,但适用于关节的固定误差(拉伸),而不是重叠碰撞。默认值为pow(1.0 - 0.1, 60.0),这意味着它将每1/60秒纠正10%的错误。
property impulse: float
在最近一次调用space.step时,施加在约束上的冲量。用这个属性除以space.step的步长值可以得到施加在约束上的力。
property max_bias: float
约束可以应用错误校正的最大速度。默认为float(“inf”)
property max_force: float
约束可以施加在物体上的最大力。默认为float(“inf”)
property pre_solve: Callable[[Constraint, Space], None] | None
property post_solve: Callable[[Constraint, Space], None] | None
分别表示两个带有constraint和space作为参数的回调函数,分别在约束求解器开始运行之前和运行结束之后调用。
property PinJoint.distance: float
两个锚点之间的控制距离。
property DampedSpring.force_func
用于计算弹簧施加的力的函数(不包括阻尼)。默认为静态属性spring_force。

spring_force(spring: DampedSpring, dist: float)float

dist表示弹簧的长度。
property DampedRotarySpring.torque_func: Callable[[DampedRotarySpring, float], float]
用于计算弹簧施加的扭矩的函数(不包括阻尼),和DampedSpring.force_func类似。默认为静态属性spring_torque。

spring_torque(spring: DampedRotarySpring, relative_angle: float)float

relative_angle表示两个身体的相对角度。
property RatchetJoint.angle: float
身体b的angle属性与身体a的angle属性之差。

7 碰撞检测

有时候可能需要判断两个形状、形状与某一点、形状与某一线段是否发生了接触,并获取相关信息,这被称作“碰撞检测”。

7.1 碰撞处理器

在开始处理形状之间的碰撞检测之前,先要为参加碰撞的形状设置“碰撞类别”。所有形状都有一个collision_type属性(是一个整数)表示碰撞类别,默认为1。例如需要实现这样的功能:玩家子弹可以和敌人发生碰撞,但不会与玩家的形状发生碰撞;敌人子弹可以和玩家发生碰撞,但不会与敌人发生碰撞。在这个例子中,玩家、敌人、玩家子弹、敌人子弹可以被设为几个不同的碰撞类别。

space.add_collision_handler方法用于在空间中添加一个碰撞处理器,返回一个CollisionHandler对象。

import pymunk

space = pymunk.Space()
space.gravity = (0, 500)

b1 = pymunk.Body()
b1.position = (100, 100)
shape1 = pymunk.Circle(b1, 20) # 创建圆形
shape1.mass = 1
shape1.elasticity = 0.8 # 设置弹性系数
space.add(b1, shape1)

b2 = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
b2.position = (0, 550)
shape2 = pymunk.Segment(b2, (0, 0), (1000, 0), 3)
shape2.elasticity = 0.8
space.add(b2, shape2)

def begin(arbiter, space, data):
    print("begin")
    # 物体刚发生接触
    return True # 正常处理此次碰撞;如果返回False,不会调用后续的pre_solve函数

def pre_solve(arbiter, space, data):
    print("pre_solve")
    # 可以在这里修改碰撞时的一些属性,如弹性、摩擦力(后文详解)
    return True # 正常处理此次碰撞;如果返回False,不会调用后续的post_solve函数

def post_solve(arbiter, space, data):
    print("post_solve")
    # 调用这个方法之前已经处理了碰撞时的信息,如弹力、摩擦力
    # 可以在这里播放碰撞的音效,计算物体被“破坏”的程度

def separate(arbiter, space, data):
    print("separate")
    # 物体已经发生了分离

shape1.collision_type = 0
shape2.collision_type = 1 # 设置shape1和shape2的碰撞类别

handler = space.add_collision_handler(0, 1) # 监测0和1类别的碰撞信息
handler.begin = begin # 当两个类别的物体刚发生接触时,会调用begin属性对应的回调函数
handler.pre_solve = pre_solve # 预处理物体的碰撞信息
handler.post_solve = post_solve # 处理了物体的碰撞信息后调用
handler.separate = separate # 当两个发生过接触的物体分开时(不再发生接触),会调用separate

if __name__ == "__main__":
    import util
    util.run(space)

这段代码首先设置了两个形状的碰撞类别,然后用add_collision_handler函数创建一个碰撞处理器CollisionHandler对象。可以通过设置几个关键的回调函数为这个碰撞处理器赋予一些行为。接下来的代码中设置了几个属性:begin, pre_solve, post_solve, separate,分别表示这几个回调函数。当物体刚开始碰撞时,会调用begin函数,然后在物体碰撞期间循环依次调用pre_solve函数,post_solve函数。当物体分离时(不再发生碰撞,或者在碰撞期间被删除),会调用separate函数。
运行这段代码后,小球(shape1)在下落到地面(shape2)上然后弹起的过程中,依次打印出下面这些内容:

begin
pre_solve
post_solve
separate

最后小球完全落到了地面上静止不动了,程序反复打印出以下内容:

pre_solve
post_solve
pre_solve
post_solve
...

在设置的这些回调函数中,begin和pre_solve函数必须要返回一个布尔值表示是否继续处理碰撞,如果继续处理则需返回True。如果begin函数返回False,那么下面的pre_solve和post_solve将不再调用,碰撞信息将不再被处理,小球碰到地面后会直接穿过去。如果pre_solve函数返回False,下面的post_solve函数不会被调用,也会造成同样的效果。但是如果pre_solve函数返回了False,下一次刷新时如果小球和地面仍然发生碰撞,那么会再次调用pre_solve函数;而如果begin函数返回了False,即使下一次刷新时小球和地面发生了碰撞也不会再调用pre_solve函数或begin函数进行处理。无论begin函数和pre_solve函数返回什么,当两个物体不再发生碰撞时都会调用separate函数。

为便于读者理解,这里展示处理碰撞的流程图:

True
True
True
False
False
True
False
True
False
False
CollisionHandler首次检测到碰撞
CollisionHandler.begin
CollisionHandler.pre_solve
形状未处于睡眠状态且形状不是传感器(见后文)
直接解决碰撞(应用摩擦、反弹等物理效果)
CollisionHandler.post_solve
下一次step时仍然检测到碰撞
忽略碰撞
下一次step时仍然检测到碰撞
CollisionHandler.separate
pass(什么也不做)

读者或许注意到了begin, pre_solve, post_solve…这些方法需要提供一些参数。正是在这些参数里面储存了碰撞有关的信息,也可以通过修改这些信息来改变碰撞造成的效果。
最重要的是第一个参数,表示一个碰撞仲裁器对象pymunk.Arbiter。仲裁器对象中包含了一些碰撞信息,如碰撞时两个物体组合的摩擦系数,弹性系数,两个物体碰撞的联系点等等。第二个参数表示发生碰撞的pymunk空间,是调用Space.add_collision_handler方法时所对应的空间,一般不太会用到。第三个参数data是由编程者自己设定的;CollisionHandler方法中有一个data属性(默认是一个空字典),编程者可以根据需要设定这个属性,往里面加入一些数据,data属性会在调用begin等方法时将data字典作为参数传递。
下面主要介绍碰撞仲裁器pymunk.Arbiter。

7.2 仲裁器

在pymunk中,两个碰撞形状的相关信息被储存在一个“仲裁器”对象pymunk.Arbiter中。当身体发生碰撞时,会创建相关的仲裁器。仲裁器对象完全由pymunk空间处理,因此不要创建别的变量引用它们。

只要有物体发生碰撞就会创建对应的仲裁器。仲裁器对象的创建优先于CollisionHandler.begin。

True
True
True
False
False
True
False
False
碰撞检测
是否产生了新的碰撞
创建仲裁器
是否有相关的CollisionHandler
CollisionHandler首次检测到碰撞(接上图)
pass
不创建新的仲裁器
是否有相关的CollisionHandler
CollisionHandler下一次step时检测到碰撞为True(接上图)
直接解决碰撞

仲裁器在两个物体发生分离后删除。分离有两种情况,一种是物体因物理运动而分离;第二种是物体在碰撞期间被代码删除而分离。如果两个物体在分离后不久又发生了碰撞,则会创建新的仲裁器。这也是一个不要随便用别的变量储存仲裁器的原因。

True
False
检测到物体分离
是否有相关的CollisionHandler
CollisionHandler.separate
删除仲裁器

即使不创建CollisionHandler对象,也能调用碰撞时创建的Arbiter对象。Body.each_arbiter方法用于遍历与该Body对象相关所有的仲裁器,允许编程者传递一个回调函数来处理每个仲裁器的信息。

Body.each_arbiter(func: Callable[[...], None], *args: Any, **kwargs: Any)None

传递给each_arbiter方法的回调函数必须包含一个位置参数表示仲裁器对象,还可以包含一些别的参数,这些参数可以在调用each_arbiter时以args和kwargs的形式传递,最终会传递给回调函数func。

func(arbiter, *args, **kwargs) -> None

示例如下:

import pymunk

space = pymunk.Space()
space.gravity = (0, 500)

b1 = pymunk.Body()
b1.position = (100, 100)
shape = pymunk.Circle(b1, 20) # 创建圆形
shape.mass = 1
space.add(b1, shape)

b2 = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
b2.position = (0, 550)
shape = pymunk.Segment(b2, (0, 0), (1000, 0), 3)
space.add(b2, shape)

def func(arbiter):
    print(arbiter.shapes) # 打印出发生碰撞的形状

def update():
    b1.each_arbiter(func) # 遍历所有仲裁器并调用func函数分别处理

if __name__ == "__main__":
    import util
    util.run(space, update) # 在每次space.step前都会调用update函数

运行后,当圆形落到地面(线段)上时,由于二者发生碰撞而创建了仲裁器,通过each_arbiter方法遍历仲裁器并依次调用func方法;在func方法中,通过仲裁器对象的shapes属性打印出发生碰撞的形状列表。
Arbiter.is_first_contact属性用于判断两个发生碰撞的物体是否为第一次接触,这个属性也比较常用。比如想要实现当物体发生碰撞时扣除血量、播放一段音效等这些只需要在碰撞期间发动一次的效果,可以使用这个属性来判断(当然有时候也可以用CollisionHandler.begin)。

7.3 CollisionHandler对象属性参考

class pymunk.CollisionHandler(_handler: Any, space: Space)
碰撞处理器对象。一般通过Space.add_collision_handler方法进行创建。
property data: Dict[Any, Any]
传递给回调函数的数据。默认是一个空字典,且不能被设置属性的方式替换,只能修改其中的键、值。
property begin: Callable[[Arbiter, Space, Any], bool] | None
表示一个回调函数,首次发生碰撞时调用。该回调函数需要三个参数,分别表示:发生碰撞时的仲裁器Arbiter,pymunk空间,与data属性相同的数据。返回一个布尔值表示是否进行接下来的碰撞处理。
property pre_solve: Callable[[Arbiter, Space, Any], bool] | None
表示一个回调函数,发生碰撞时调用(如果是首次发生碰撞,则在begin方法后面调用;如果begin方法返回了False将不再调用此函数)。返回一个布尔值表示是否需要解决碰撞。
property post_solve: Callable[[Arbiter, Space, Any], None] | None
表示一个回调函数,解决碰撞后调用(如果pre_solve方法返回了False,将不再调用此函数)。
property separate: Callable[[Arbiter, Space, Any], None] | None
表示一个回调函数,已经碰撞的两个物体分离(或者其中某个物体被pymunk空间删除)时调用。无论begin方法和pre_solve方法返回了True还是False,物体分离时都会调用这个函数。

7.4 Arbiter对象属性参考

class pymunk.Arbiter(_arbiter: pymunk._chipmunk.ffi.CData, space: Space)
仲裁器对象。
property contact_point_set: ContactPointSet
返回一个ContactPointSet对象用于描述碰撞时的接触点集。发生碰撞时,会有一对或两对接触点(ContactPoint),每对接触点包含两个子接触点,接触点对象ContactPoint的属性point_a和point_b表示这两个子接触点的坐标。
接触点的计算方式比较奇怪,使用了特殊的碰撞检测算法(GJK, EPA)。读者可以自行查看pymunk.examples.collisions来查看不同形状发生碰撞时的接触点。不过,当形状只是边缘接触时,发生碰撞的接触点就是两个形状边缘的交点(如果是两条平的边缘接触,那么会有两个接触点,分别是最左侧的接触点和最右侧的接触点)。关于算法细节,有兴趣的读者可查看:https://gitee.com/python_zzy/csdn-articles/blob/master/pymunk/collision.txt
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Arbiter.contact_point_set这个属性表示一个ContactPointSet对象,这个对象包含一个points属性表示接触点的列表,其中包含1至2个接触点对象ContactPoint。
关于接触点之间的属性可能有点乱,用下面的树状图表示属性关系:

Arbiter对象
contact_point_set: ContactPointSet
points: List[ContactPoint]
每个ContactPoint对象
point_a: Vec2d
normal: Vec2d
point_b: Vec2d
distance: float

其中,ContactPointSet.normal属性表示碰撞的法线向量,方向和point_a到point_b的方向是一致的。ContactPoint.distance属性表示point_a和point_b两个坐标点之间的距离(因为是碰撞的深度,所以是负数)。
此外,如果想要直接获取两个形状的接触点集,而不用通过Arbiter,可以调用Shape.shapes_collide方法;这个方法接受一个参数表示另一个Shape对象,调用后直接返回两个Shape对象碰撞的接触点集ContactPointSet。
property friction: float
碰撞对的摩擦力
property is_first_contact: bool
判断两个身体是否第一次接触。这可以用于:在两个身体碰撞时播放音效。
property is_removal: bool
如果CollisionHandler.separate是由于对被删除而被调用的,则在CollisionHandler.separate()回调期间返回True。
property normal: Vec2d
碰撞的法向量
property restitution: float
碰撞对的弹性系数(默认为两个发生碰撞的形状弹性系数elasticity相乘)
property shapes: Tuple[Shape, Shape]
发生碰撞的两个形状
property surface_velocity: Vec2d
碰撞对的表面速度(默认为第一个物体表面速度减去第二个物体表面速度,在碰撞方向上的分速度)
property total_impulse: Vec2d
在当前step时施加在物体身上用来解决碰撞的冲量。
property total_ke: float
碰撞时损失的能量(包括静摩擦力,但不包括动摩擦力)。此属性只能从CollisionHandler.post_solve或Body.each_arbiter中调用。

7.5 添加碰撞处理器的其他方法

除了Space.add_collision_handler方法,还有一些别的方法用于添加碰撞处理器。

Space.add_wildcard_collision_handler(collision_type_a: int) → CollisionHandler

add_wildcard_collision_handler用于创建一个通配符碰撞处理器,只需要一个collision_type作为参数,但是它可以匹配所有其他的碰撞类型。这可以用于实现一个炸弹,它在碰撞到一切类型的事物时都将其摧毁。

Space.add_default_collision_handler() → CollisionHandler

add_default_collision_handler方法返回(而不是创建)默认的碰撞处理器。默认碰撞处理器用于管理那些没有通过add_collision_handler和add_wildcard_collision_handler方法设置过的其他碰撞。

7.6 形状查询

通过CollisionHandler处理形状碰撞相对灵活,但如果有时候不需要修改形状碰撞时产生的物理效果,也就是只需要修改CollisionHandler.post_solve里面的内容,那么就显得不太方便。此时可以使用形状查询,查询形状的碰撞信息(只能查询不能修改)。可以查询点与形状、线段与形状、形状与形状。本节介绍单个形状与点、线段的碰撞查询。

Shape对象提供了point_query和segment_query方法,分别用于查询形状和点的碰撞信息,以及形状和线段的碰撞信息。

Shape.point_query(p: Tuple[float, float]) → PointQueryInfo

p表示一个点的坐标(局部坐标),将返回这个点与形状的碰撞信息,是一个PointQueryInfo对象。PointQueryInfo对象的属性如下:
distance: float
表示点与形状的距离,在下面的两幅图中表示为黑色线段的长度。如果查询点在形状之外(没有发生碰撞),那么这个值是一个正数,表示的是点与形状最近的距离。如果查询点在形状之内(发生了碰撞),那么这个值是一个负数,表示的是点与形状之外的空间最近的距离。
在这里插入图片描述

在这里插入图片描述
point: Vec2d
表示上面的distance属性表示的线段与形状的交点,在上面的两幅图中表示黑色线段与圆形边缘的交点坐标(世界坐标)。如果查询点在形状之外,那么这个点表示形状上与查询点距离最近的点。如果查询点在形状之内,那么这个点表示形状之外的空间与查询点距离最近的点。
gradient: Vec2d
distance计算函数的梯度(distance关于查询点坐标的导数)。公式为:
lim ⁡ Δ x → 0 f ( p + p × Δ x ) − f ( p ) Δ x \lim_{\Delta x \to 0} \frac{f(p + p \times \Delta x) - f(p)}{\Delta x} limΔx0Δxf(p+p×Δx)f(p)
其中p为查询点坐标,f为根据查询点坐标计算到distance的函数。
shape: Shape | None
查询的形状


Shape.segment_query用于查询线段和形状的碰撞信息。

segment_query(start: Tuple[float, float], end: Tuple[float, float], radius: float = 0) → SegmentQueryInfo

start是开始查询的点(局部坐标),end是结束查询的点(局部坐标),相当于查询start到end这条线段。segment_query支持radius参数表示线段的半径(厚度)。返回的结果是一个SegmentQueryInfo对象,这个对象的属性如下:
alpha: float
沿着线段方向遍历线段上每个点,直到点与线段相交时,遍历过的点连成的线段占整条线段长度的比例。查询是有方向的,如果start这个点位于图形内,那么alpha直接返回0.0(因为是“沿着线段方向”进行查询的)。如果有一条线段,它在图形内的部分占线段长度的1/4,且start坐标位于图形外,那么alpha=0.75(因为沿着线段方向查询完了这个线段上3/4的点才查询到位于形状之内的点);如果将线段的start和end互换,start坐标位于图形内了,那么alpha=0.0。alpha范围在0-1之间,如果线段与图形没有相交,那么alpha=1.0。
point: Vec2d
如果start坐标位于图形之外,那么返回线段与形状相交的点(局部坐标)。如果start坐标位于图形之内,那么直接返回end坐标。
normal: Vec2d
位于point的曲面法向量。(法向量所在直线垂直于过point的形状的切线)
shape: Shape | None
查询的形状

7.7 在空间中查询

有时候需要判断一个形状和其他所有形状是否发生了碰撞,而不是单个形状的碰撞查询。此时可以使用Space对象的几个查询方法。
point_query(point: Tuple[float, float], max_distance: float, shape_filter: ShapeFilter) → List[PointQueryInfo]
查询point附近符合范围内的所有形状列表(形状必须被添加到空间中才能被查询到),包括标记为传感器的形状(传感器将在后文解释)。max_distance表示形状与点之间的最大距离。如果max_distance是负数,则表示碰撞到一定深度以下才能被匹配;如果max_distance为0,表示形状和点必须相交。shape_filter是形状过滤器,后文详解。
point_query_nearest(point: Tuple[float, float], max_distance: float, shape_filter: ShapeFilter) → PointQueryInfo | None
查询point附近符合范围内的,且与point距离最近的形状(形状必须被添加到空间中才能被查询到),不包括标记为传感器的形状(传感器将在后文解释)。如果没有查询到返回None。
segment_query(start: Tuple[float, float], end: Tuple[float, float], radius: float, shape_filter: ShapeFilter) → List[SegmentQueryInfo]
以给定半径沿着线段从头到尾查询空间,返回所有匹配的SegmentQueryInfo对象的列表,包括标记为传感器的形状(传感器将在后文解释)。关于SegmentQueryInfo参见7.5。可以通过SegmentQueryInfo.shape属性查看匹配到的形状对象。
segment_query_first(start: Tuple[float, float], end: Tuple[float, float], radius: float, shape_filter: ShapeFilter) → SegmentQueryInfo | None
与segment_query类似,但是查询到的是首个匹配到的形状,不包括标记为传感器的形状(传感器将在后文解释)
shape_query(shape: Shape) → List[ShapeQueryInfo]
返回与给定形状相交的所有形状查询信息列表,包括标记为传感器的形状。ShapeQueryInfo包含两个属性:碰撞的形状shape、碰撞的联系点集contact_point_set。
bb_query(bb: BB, shape_filter: ShapeFilter) → List[Shape]
返回边界框附近的所有形状列表,包括传感器形状。

下面给出一个point_query的示例:

import pymunk

space = pymunk.Space()
space.gravity = (0, 500)

def add_circle():
    b = pymunk.Body()
    b.position = (100, 100)
    s = pymunk.Circle(b, 20) # 创建圆形
    s.mass = 1
    s.elasticity = 0.8 # 设置弹性系数
    space.add(b, s)

body = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
body.position = (0, 550)
shape = pymunk.Segment(body, (0, -10), (1000, 10), 3)
shape.elasticity = 0.8
space.add(body, shape)

count = 0 # 计数器
def update():
    global count
    
    count += 1
    if count > 60:
        count = 0
        add_circle() # 定时添加圆形

    info = space.point_query_nearest( # 查询与点距离最近的形状
        (500, 300), # 需要查询的点
        float("inf"), # 图形与点的最大距离
        pymunk.ShapeFilter() # 形状过滤器(后文详解)
        )
    if info is not None and not isinstance(info.shape, pymunk.Segment): # 匹配到的形状不为线段(地面)
        print(info.shape.body.position) # 打印圆形的位置

    for s in space.shapes: # 遍历添加到空间中的所有形状
        if s.body.position.y > 700:
            space.remove(s, s.body) # 删除掉出屏幕外的形状

if __name__ == "__main__":
    import util
    util.run(space, update) # 每次step前调用update

读者也可以查看pymunk.examples.point_query,此程序会显示出离鼠标最近的圆形。

7.8 形状过滤器

为了便捷地实现区分碰撞类别,可以使用形状过滤器。将创建的形状过滤器对象传递给查询方法(仅限于Space.point_query, Space.point_query_nearest, Space.segment_query, Space.segment_query_first),或者修改Shape对象的filter属性。

pymunk.ShapeFilter(group: int = 0, categories: int = 4294967295, mask: int = 4294967295)

在介绍ShapeFilter使用之前,读者首先要了解基本的位运算知识。
python中定义一个整数可以以二进制的形式定义,如下所示:

>>> 0b1
1
>>> 0b10
2
>>> 0b100
4

在对应的二进制数字前面加上前缀"0b",python会将其识别为二进制的数字。
二进制之间有一些基本的位运算,例如"与"。对两个数字进行与运算,相当于把这两个数字先转换为二进制的形式,位数较少的那个数字的不足位数用0填充。然后对两个数字的各个数位上的数字进行比较,如果都是1,那么将结果中的这一位用1填充,否则用0,示例如下:

>>> a = 0b111001
>>> b = 0b100111
>>> c = a & b # 与运算
>>> bin(c) # 转换为二进制字符串
'0b100001'

ShapeFilter通过按位与运算进行形状过滤。

在创建形状过滤器时,需要提供一个categories参数表示该形状的类别,为了方便一般将其写为二进制数字的形式。
这里以“玩家、敌人、玩家子弹、敌人子弹、墙壁”之间的碰撞为例,先要通过categories参数在ShapeFilter上把这5种类型的形状分类。

player_filter = pymunk.ShapeFilter(
    categories=0b00001) # 玩家
enemy_filter = pymunk.ShapeFilter(
    categories=0b00010) # 敌人
player_bullet_filter = pymunk.ShapeFilter(
    categories=0b00100) # 玩家子弹
enemy_bullet_filter = pymunk.ShapeFilter(
    categories=0b01000) # 敌人子弹
wall_filter = pymunk.ShapeFilter(
    categories=0b10000) # 墙壁

创建完过滤器后,将过滤器设置为Shape对象的filter属性,这样就在形状对象上应用了过滤器。

player_shape.filter = player_filter
...

接下来需要进行设置:玩家和敌人子弹发生碰撞,敌人和玩家子弹发生碰撞,墙壁可以与玩家和敌人发生碰撞,但不会与子弹发生碰撞。此时就需要设置ShapeFilter的mask属性来改变过滤掩码。如下所示:

player_filter = pymunk.ShapeFilter(
    categories=0b00001, mask=0b11000) # 玩家
enemy_filter = pymunk.ShapeFilter(
    categories=0b00010, mask=0b10100) # 敌人
player_bullet_filter = pymunk.ShapeFilter(
    categories=0b00100, mask=0b00010) # 玩家子弹
enemy_bullet_filter = pymunk.ShapeFilter(
    categories=0b01000, mask=0b00001) # 敌人子弹
wall_filter = pymunk.ShapeFilter(
    categories=0b10000, mask=0b00011) # 墙壁

用表格来表示:

对象categoriesmask
玩家Player0b00001 (1)0b11000 (4, 5)
敌人Enemy0b00010 (2)0b10100 (3, 5)
玩家子弹Player Bullet0b00100 (3)0b00010 (2)
敌人子弹Enemy Bullet0b01000 (4)0b00001(1)
墙壁Wall0b10000 (5)0b00011 (1, 2)

在进行player的碰撞检测时,会将检测到的其他形状的ShapeFilter.categories与player的ShapeFilter.mask进行按位与运算,如果运算结果的布尔值为True,那么才进行碰撞处理,否则忽略此次碰撞。需要注意的是,设置了ShapeFilter之后,两个不会发生碰撞的物体之间不会产生物理效果;如果墙壁和小球的ShapeFilter互不碰撞,那么小球会直接穿墙而过。
默认的mask是0b1111…(共32个1),这意味着可以和任何形状发生碰撞。

除了categories和mask这两个最常用的参数,ShapeFilter还支持一个group参数,默认为0。拥有相同的group(除了默认的0)的形状之间不会发生碰撞。

Space对象支持的所有查询方法(已在7.6列举出,除了shape_query方法)都支持shape_filter作为一个形状过滤器参数,在查询的时候进行应用。

形状过滤器对象包含属性:categories, group, mask,此外还包含两个静态方法ALL_MASK()和ALL_CATEGORIES,都会返回一个0b1111…(共32个1)的整数,用于和任何形状发生碰撞。例如,如果想要得到一个不与0b1这个类别发生碰撞的mask,可以用0b1与ALL_MASK()进行异或运算(异或运算符:“^”),将运算的结果作为mask参数

7.9 传感器

Shape对象有一个sensor属性,是一个布尔值,表示这个形状是否为“传感器”。标记为传感器的对象不会发生碰撞,但是可以在部分查询方法中被查询到。如果形状为传感器,在CollisionHandler检测到碰撞时会调用begin, separate和pre_solve,但是不会应用物理效果解决碰撞,也不会调用post_solve(参见7.3的流程图)。

在Space对象的查询方法中,只有point_query_nearest和segment_query_first这种查询“最近”的结果不包括传感器形状,其他查询结果里都包括传感器形状。

下一篇文章:https://blog.csdn.net/qq_48979387/article/details/140590359

  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值