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

总目录:https://gitee.com/python_zzy/csdn-articles/blob/master/pymunk/README.md

1 简介

1.1 物理引擎

物理引擎通过为刚性物体赋予真实的物理属性的方式来计算运动、旋转和碰撞反应。

通过一种叫做“物理引擎”的东西,我们可以在程序中模拟现实生活中的物体行为。例如一个球下落后的轨迹,两个物体相撞后会发生怎样的变化,这些可以用物理力学知识解释的现象,大都可以通过物理引擎进行模拟。

1.2 pymunk

pymunk是基于Chipmunk2d的一个2D物理引擎模块。与Box2d这样的物理引擎相比,pymunk模块的设计更加符合python编程者的习惯,因而很容易学习。

pymunk的Github页面是:https://github.com/viblo/pymunk/
pymunk文档页面是:https://www.pymunk.org/en/latest/
读者可以通过pip工具安装pymunk:

pip install pymunk

由于pymunk只提供了物理引擎模拟相关的内容,而没有提供方法渲染模拟的结果,所以本教程中还使用了游戏编程模块pygame用于显示(如果读者不了解pygame也不影响)。

pip install pygame-ce

可以通过pymunk.version查看pymunk的版本:

>>> import pymunk
>>> pymunk.version
'6.8.1'

1.3 关于本教程

本教程需要读者有一定的物理知识(力学方面的)。涉及到物理的大部分知识会在本教程中提及,但是如果读者毫无物理基础可能不好理解。

在运行本教程的代码时,尽量避免使用Python自带的IDLE编辑器,因为IDLE上显示不了Chipmunk的一些报错信息。

本教程总目录:https://gitee.com/python_zzy/csdn-articles/blob/master/pymunk/README.md

2 基础功能

本章介绍pymunk的一些简单功能。

2.1 第一个示例

下面展示使用pymunk的一个简单示例。

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 time

    options = pymunk.SpaceDebugDrawOptions()
    while True:
        time.sleep(0.01) # 停顿0.01s
        space.debug_draw(options) # 显示空间的内容
        space.step(0.01) # 刷新空间

这段代码运行后,会发现打印出了一系列文字,这是调用space.debug_draw的结果。debug_draw方法用于显示物理空间的内容。

2.2 空间

在导入pymunk之后,首先要创建一个“空间”。

space = pymunk.Space()

你可以把这个空间理解为一个2D的平面,上面可以添加各种物体。
当然,你也可以创建多个pymunk空间,这些空间是相互独立的一个个平面,不会相互干扰。

由于空间是动态的,会发生各种物理事件,因此需要不断调用Space.step方法进行刷新。Space.step方法接收一个参数表示运行的时间。例如,调用一次space.step(0.01),相当于让pymunk物理空间运行了0.01秒。循环中space.debug_draw方法用于显示pymunk物理空间运行的结果,它需要一个SpaceDebugDrawOptions对象作为参数,读者无需过多了解。

当然,如果使用常规的debug_draw方法只会打印一堆数据,很难清晰地看出pymunk的模拟结果,最好的方法是让这个模拟结果显示在窗口上。为了便于显示pymunk的绘制结果,pymunk模块对python的一些渲染库提供了支持。pymunk包含matplotlib_util, pygame_util, pyglet_util子模块用于辅助渲染。这些模块中都有一个DrawOptions类,实例化后可作为参数传递给debug_draw方法。

本教程使用pygame_util中的DrawOptions来显示绘制的结果。为了方便那些不懂pygame的读者,让他们专注于pymunk本身而不是有关pygame的代码,作者写了一个辅助渲染的工具,代码如下。读者无需考虑这段代码的内容,只需将下面这段代码复制到与你的python文件同一文件夹下,命名为util.py。本教程中的大部分示例将会使用util.py来显示pymunk的运行结果。关于这段代码的解释会在教程的后面进行。

## util.py
import pygame as pg
import sys
from pymunk.pygame_util import DrawOptions

background = (255, 255, 255) # white
fps = 60

screen = pg.display.set_mode((1000, 600))
draw_options = DrawOptions(screen)

clock = pg.time.Clock()

def run(space, func=None):
    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                pg.quit()
                sys.exit()

        if func:
            s = str(func())
        else:
            s = "FPS: {}".format(clock.get_fps())

        pg.display.set_caption(s)
        screen.fill(background)
        
        space.debug_draw(draw_options)
        space.step(1 / fps)

        pg.display.flip()
        clock.tick(fps)

在使用util.py后,示例代码变成了这样,有关debug_draw和step的部分全部被util.py封装了:

import pymunk

space = pymunk.Space() # 创建一个pymunk物理空间

body = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建一个静态的“身体”
body.position = (100, 100) # 设置身体的位置

shape = pymunk.Circle(body, 20) # 创建一个半径为20像素的圆形的形状,并绑定其“身体”
shape.mass = 1 # 设置圆形的质量为1

space.add(body, shape) # 将身体和形状加入pymunk空间

if __name__ == "__main__":
    import util
    util.run(space) # 这里调用了util.py用于显示pymunk的运行结果

运行结果:

窗口中有一个灰色的圆形,中间有一条黑色的线,指示形状的方向。这个形状的颜色是不能改变的,这是为了方便编程者测试物理引擎的效果,如果想要用pymunk来做游戏或其他应用,当然不能直接使用debug_draw。

2.3 身体和形状

在创建完空间之后,我们可以向空间中添加物体。这个物体在pymunk中由两部分构成:身体和形状。
身体指的是pymunk.Body。这里的Body表示的是一个“刚体”,也就是不会发生形变的物体(不会像弹簧那样容易被压缩,不会碎裂等)。Body类有一个body_type参数表示刚体的类型,包括pymunk.Body.STATIC(静态),pymunk.Body.DYNAMIC(动态,是默认的类型),pymunk.Body.KINEMATIC(运动体)。
STATIC类型的Body是完全静态的,它不会受力而运动,也无法设置它的速度;如果不设置body_type,默认的类型是DYNAMIC,DYNAMIC类型的Body会因为受力而运动。关于不同类型的Body将在后面详细介绍。
pymunk.Shape类则表示一种形状。形状的类可以有很多种,包括圆形(Circle)、线段(Segment)、多边形(Poly),它们都继承pymunk.Shape类。上面这个示例中展示的是Circle类。
一切形状都需要绑定一个Body对象才能被pymunk运行。实例化一个形状对象时,需要将其对应的Body对象作为参数传递给对应的形状类(也可以暂时将对应的Body设为None,但是更新pymunk空间时必须进行绑定)。此外还有一些参数,不同形状各不相同,比如这里的Circle类需要提供一个圆形的半径作为参数。关于不同形状将在后文详细介绍。

2.4 物理属性

pymunk物体具有一些可以设置或进行访问的物理属性,包括面积、质量、密度、惯性矩、摩擦系数、弹性系数等。本节介绍一些基本的物理属性。
由于pymunk是一个2D的引擎,所以没有“体积”的概念。所有物理公式中有关体积的内容都用面积(area)来替代。形状对象都含有一个area属性,返回该形状的面积大小。area属性不能被更改,只能被获取。
物理学上,质量(mass)是指一个物体中所含物质的多少,国际单位为千克。Body对象和Shape对象都包含一个mass属性,用于设置或获取物体的质量。
物理学上,密度(density)表示单位体积的物质的质量。密度的公式为:密度 = 质量 / 体积 (ρ=m/V)(体积在pymunk中指的是面积)。比如,铁的密度比棉花大,那么相同体积的铁和棉花,铁的质量就比棉花的质量更大,换句话说,等体积的铁比棉花更重。Shape对象包含一个density属性,用于设置或获取物体的密度。
由于质量、密度、体积(面积)之间存在一定的关系,当设置了密度属性后,物体的质量属性也会随之更改。因此只需要设置质量/密度中的一个属性即可。如果要更改物体的质量,建议设置Shape对象的mass属性而不是Body对象的mass属性;如果设置Body对象的mass属性,pymunk不会自动计算相关的属性值,如密度。
如果不设置一个DYNAMIC物体的质量,那么会报错。STATIC和KINEMATIC类型的物体无需设置质量。

>>> shape = pymunk.Circle(None, 10)
>>> print(shape.area) # 面积
314.1592653589793
>>> shape.area = 1 # 无法设置形状的面积,因为在实例化Circle对象时就已经定义好了
Traceback (most recent call last):
...
AttributeError: can't set attribute 'area'
>>> shape.density = 1 # 设置密度
>>> print(shape.mass)
314.1592653589793
>>> shape.mass = 100 # 设置质量
>>> print(shape.density)
0.3183098861837907

关于“惯性矩”的概念读者不需要了解太多,这指的是物体抵抗旋转的能力。惯性矩越大的物体越不容易发生旋转。Body对象和Shape对象都包含一个moment属性,用于获取和设置物体的惯性矩(有时候这个属性表示的就是力矩)。惯性矩与物体的形状等都有关系,它会在设置了质量或密度后自动进行计算,一般情况下不需要被更改,否则会出现一些奇怪的物理现象。但如果是想要实现一个永远不会翻转的物体(比如平台跳跃游戏中的玩家),可以将moment设为float(“inf”)
Body对象有一个position属性表示刚体的位置。不同形状的position对应不同的锚点,比如设置圆形(Circle)形状对应Body的position,相当于设置圆形圆心的位置;关于不同形状的位置锚点将在后文介绍。

2.5 设置重力和阻尼

pymunk空间默认重力为0,但可以被赋予重力。这个重力不一定是竖直向下的,你可以更改为向上的,甚至是朝向任意方向。通过设置pymunk的gravity属性可以设置空间的重力。gravity是一个2D矢量(有方向的量,又叫作向量),表示pymunk空间在x方向和y方向的重力。例如:

space.gravity = (0, 1000)

pymunk中,如果要让一个物体受力运动,那么必须将物体的body_type设置为pymunk.Body.DYNAMIC,否则施力不会有任何效果。由于这是body_type的默认值,所以在传递Body参数的时候留空即可。

import pymunk

space = pymunk.Space()
space.gravity = (0, 1000) # 添加重力

body = pymunk.Body() # 动态“身体”,DYNAMIC是默认的
body.position = (100, 100)

shape = pymunk.Circle(body, 20)
shape.mass = 1

space.add(body, shape)

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

运行后,可以看到圆形快速下落。

重心是物体受到重力的作用点。可以通过Body.center_of_gravity属性设置和获取物体的重心。默认物体的重心是(0, 0),也就是和Body.position重合。

如果物体运动时想要让它受到空气阻力,那么可以设置space.damping属性,使物体受到阻尼的影响。damping属性默认值为1,也就是没有空气阻力造成的损失。如果设为0.8,那么物体运动时每秒会损失20%的速度。

import pymunk

space = pymunk.Space()
space.damping = 0.6 # 物体每秒损失40%的速度

body = pymunk.Body()
body.position = (100, 100)
body.velocity = (50, 0) # 设置物体速度

shape = pymunk.Circle(body, 20)
shape.mass = 1

space.add(body, shape)

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

这个示例展示了阻尼对运动的影响,可以看到小球移动越来越慢了。其中body.velocity属性的设置是为了赋予身体一个初速度,将在后面详细介绍。

3 身体

本章介绍身体类(pymunk.Body)。

3.1 对物体施加力和冲量

力能够改变物体的运动状态,力包括大小、方向、作用点这几个要素。Body.apply_force_at_local_point和Body.apply_force_at_world_point方法用于给物体施加力使物体运动。由于物体受到的力在pymunk每次刷新完会自动删除,因此必须在pymunk空间每次更新之前调用这些方法(在循环中每次调用space.step之前调用)。

apply_force_at_local_point(force: Tuple[float, float], point: Tuple[float, float] = (0, 0))None
apply_force_at_world_point(force: Tuple[float, float], point: Tuple[float, float])None

apply_force_at_local_point和apply_force_at_world_point方法都需要提供两个参数,第一个是物体受到的力,第二个参数是力的作用点的坐标,两个参数都是2D矢量。两个方法的区别在于力的作用点的坐标是如何计算的。…local_point使用局部坐标,而…world_point使用世界坐标。局部坐标相对于身体的position属性表示的位置,而世界坐标相对于整个pymunk空间。比如一个空间内position位于(100, 100)的物体,如果在空间内(10, 10)的位置施力(local_point模式),那么它实际上在空间内(110, 110)坐标的位置施力;如果是world_point模式下在空间内(10, 10)位置施力,实际上就是在空间内(10, 10)的位置施力。

import pymunk

space = pymunk.Space()

body = pymunk.Body() # 创建一个动态的“身体”
body.position = (100, 100)

shape = pymunk.Circle(body, 20)
shape.mass = 1

space.add(body, shape)

def func():
    body.apply_force_at_local_point((10, 0), (0, 0)) # 给物体施加力

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

运行效果:

可以看到,一个圆形不断向右加速运动。

在这个示例中,定义了一个func函数传递给util.run。这个函数会在每次刷新pymunk空间之前调用(也就是在space.step方法前面调用,详见util.py的代码),也就是上文说的“在pymunk空间每次更新之前调用这些方法”。

apply_impulse_at_local_point(impulse: Tuple[float, float], point: Tuple[float, float] = (0, 0))None
apply_impulse_at_world_point(impulse: Tuple[float, float], point: Tuple[float, float])None

除了对物体施力,还可以通过Body对象的apply_impulse_at_local_point和apply_impulse_at_world_point方法对物体施加冲量。冲量描述物体受力持续单位时间的影响。比如对物体施加5Ns的冲量,其效果相当于对这个物体施加5N的力,持续1s的效果。由于冲量的概念中已经有了时间的效果,所以一般不用像施加力一样循环调用。物体速度 = 冲量 / 物体质量。如果对一个静止的2kg物体施加100Ns的冲量,它将以50m/s速度匀速直线运动。对物体施加冲量相当于在瞬时赋予了物体一个初速度。

3.2 身体类型

Body有不同的类型,包括STATIC, DYNAMIC, KINEMATIC,这些类型都作为pymunk.Body的类变量。创建Body的时候,可以通过body_type关键字参数指定Body的类型。创建Body后,也可以通过Body对象的body_type属性获取和更改Body的类型。

STATIC类型的Body是静态的,它不会受力而运动,也无法设置它的速度。静态类型的Body显示为灰色。

如果不设置body_type,默认的类型是DYNAMIC,DYNAMIC类型的Body会因为受到其他物体施加的力(包括重力)而运动。DYNAMIC类型的Body显示为蓝色。

在这里插入图片描述

此外还有一个KINEMATIC类型,表示一种质量无限大的物体。KINEMATIC类不会受到物理引擎中其他物体的影响,但是你可以通过代码上的设置使它运动。也就是说,这样的物体不会因为受到其他物体施加的力而运动(因为它的质量太大了,没有力拉得动它);但是,可以通过Body.velocity属性调节它的速度使它运动。 在一个平台跳跃游戏中,KINEMATIC类型的Body可以作为那种来回移动的平台,它虽然是运动的但不受玩家的影响。KINEMATIC类型的Body显示为绿色。

在这里插入图片描述

例如,如果想要让一个KINEMATIC类型的物体以(10, 0)速度运动,可以调用:

body_of_a_kinematic.velocity = (10, 0)

3.3 睡眠

当一个动态的物体落到地面上一段时间直至它不再动弹,或者它的速度过于缓慢,完全可以把它当做一个静态物体来对待的时候,我们可以认为这个物体进入了“空闲”状态。如果pymunk还要一直刷新一个空闲的物体,那么这会白白耗费一定的时间。为此,Body对象提供了sleep方法,用于直接让一个物体进入“睡眠”状态,进入睡眠状态的物体不再被刷新状态,即使有速度也不会移动。但如果一个睡眠状态下的物体受到其他操作(例如被施力、更改属性、被其他物体撞击),那么物体的睡眠状态会解除。

在调用sleep方法前,必须要设置Space对象的sleep_time_threshold属性,表示物体进入空闲后多长时间进入睡眠状态,否则调用sleep会报错。如果将这个属性设为0.5,那么当一个物体进入空闲状态0.5秒后,会自动进入睡眠状态。进入睡眠状态后的物体在debug_draw时显示为灰色。

如果想让一个进入睡眠状态的物体解除睡眠状态,可以调用Body.activate()方法。

3.4 Body常用实例属性

Body对象有一些属性表示物体当前的状态,通常可以直接设置这些属性的值来改变Body对象的位置和运动状态。(Vec2d表示2D矢量)

属性类型解释
anglefloat物体围绕重心的旋转角度(弧度)
angular_velocityfloat物体旋转的角速度(弧度)
center_of_gravityVec2d(2D矢量)物体重心位置(默认是(0,0)表示与Body.position属性重合)
forceVec2d对物体作用于重心施加的力(只包括用apply_force_*方法施加的力,不包括与其他物体碰撞产生的力)
kinetic_energyfloat物体具有的动能
massfloat物体质量
momentfloat物体惯性矩
positionVec2d物体位置
rotation_vectorVec2d物体的旋转向量,默认为(1,0)(pymunk绘制Circle时圆形中间那一条黑线的指示方向)
torquefloat施加在物体上的扭矩(pymunk空间每次刷新后清除此值)
velocityVec2d物体的速度

比如,如果想要给物体提供一个初速度,但是不想用施加冲量这么麻烦的方式;此时可以调用下面这段代码,这样直接就给物体赋予一个(10, 0)的速度。

body.velocity = (10, 0)

再比如,如果想要让一个物体以某种速度旋转,如果通过施力的方式则十分麻烦;此时可以直接修改物体的角速度:

body.angular_velocity = 3.14 / 2

然后物体将以90°每秒(90 角度 = π/2 弧度)的速度顺时针转动。

3.5 通过回调函数管理身体行为

身体类包含一些属性,如position_func, velocity_func,允许你直接通过修改回调函数改变物体的行为。比如position_func,当物体在每次space.step中移动位置时pymunk会调用这个函数。如果直接修改这个函数,那我们就能实现在身体移动时做出一些额外的处理,相当灵活。
比如有这样一个场景:写游戏中可能会出现想要“暂停”某个物体,此时就可以通过修改position_func的回调函数来实现这个功能。

import pymunk

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

body = pymunk.Body() # 创建一个动态的“身体”
body.position = (100, 100)

shape = pymunk.Circle(body, 20)
shape.mass = 1

space.add(body, shape)

pause = True # 定义一个标记,判断物体是否被暂停
def position_func(body, dt):
    if not pause:
        pymunk.Body.update_position(body, dt) # 如果物体没有被暂停就移动物体

body.position_func = position_func # 修改body的position_func

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

默认情况下,如果没有设置身体的position_func属性,body会调用静态方法update_position来更改物体的位置。position_func需要两个参数,分别表示身体对象,以及物理空间step的时间值。上面的示例通过一个pause变量实现了对物体是否暂停的控制,如果将pause的值改为False,那么小球正常下落。
类似地,velocity_func属性是pymunk在改变身体的速度时调用的,Body类也有一个对应的静态方法update_velocity。这个可能比position_func更加常用。

velocity_func(body : Body, gravity, damping, dt)

velocity_func需要包含4个参数。分别是body对象,物体受到的重力大小,物体运动时受到的阻尼量(默认为1,例如阻尼为0.9,那么物体在移动时每秒损失10%的速度,可用于在物理引擎中添加空气阻力,2.5节已经介绍过),以及物理空间step的时间值。
比如要实现一个失重状态下的物体(不受重力),可以这样:

def zero_gravity(body, gravity, damping, dt):
	Body.update_velocity(body, (0, 0), damping, dt) # 刷新速度时重力设为(0,0)了
body.velocity_func = zero_gravity # 修改body.velocity_func回调函数

有时候物体运动速度过快或者space.step提供的参数数值太大会产生“隧道效应”(tunneling)。例如一个面积并不大的圆形以极快的速度穿过一个只有1个像素宽的墙壁,由于速度过快,圆形一次移动的距离太长,可能还没有参与碰撞检测就已经越过了墙壁,实现了“穿墙”。为了避免这种现象,可以通过改变velocity_func来限制物体的最大速度。

def limit_velocity(body, gravity, damping, dt):
    max_velocity = 1000 # 物体的最大速度
    pymunk.Body.update_velocity(body, gravity, damping, dt)
    l = body.velocity.length # 获取速度向量的长度
    if l > max_velocity: # 如果速度向量的长度大于了最大速度
        scale = max_velocity / l
        body.velocity = body.velocity * scale # 修改物体的速度为max_velocity
body.velocity_func = limit_velocity

这段代码的目的是为了限制物体速度,如果物体速度超过了最大速度就修改物体的速度。代码中body.velocity.length是pymunk.Vec2d向量对象的一个属性,将在后文详解。由于速度是一个向量,所以限制物体速度时略微麻烦。

3.6 Body对象属性参考

class pymunk.Body(mass: float = 0, moment: float = 0, body_type: int = DYNAMIC) → None
创建一个刚体。mass表示质量,moment表示惯性矩,body_type是刚体的类型,可选值有:pymunk.Body.STATIC(静态), pymunk.Body.DYNAMIC(动态,默认), pymunk.KINEMATIC(动态,但不受力的影响)。
身体对象可以被复制或被pickle转储。
activate() → None
唤醒一个“睡眠”的物体。
property angle: float
物体围绕重心的旋转角度(弧度)
property angular_velocity: float
物体旋转的角速度(弧度)
apply_force_at_local_point(force: Tuple[float, float], point: Tuple[float, float] = (0, 0)) → None
给物体施力,force表示施加的力,point表示力的作用点(坐标相对于物体的position)
apply_force_at_world_point(force: Tuple[float, float], point: Tuple[float, float]) → None
给物体施力,force表示施加的力,point表示力的作用点(坐标相对于整个空间)
apply_impulse_at_local_point(impulse: Tuple[float, float], point: Tuple[float, float] = (0, 0)) → None
给物体施加冲量(力作用于物体一段时间的效果),force表示施加的冲量,point表示冲量的作用点(坐标相对于物体的position)
apply_impulse_at_world_point(impulse: Tuple[float, float], point: Tuple[float, float]) → None
给物体施加冲量,force表示施加的冲量,point表示冲量的作用点(坐标相对于整个空间)
property body_type: int
身体的类型
property center_of_gravity: Vec2d
身体的重心位置(相对于身体的position)
property constraints: Set[Constraint]
物体附加的约束集合(详见后文)
copy() → T
复制身体(是深层复制,即deep copy)
each_arbiter(func: Callable[[…], None], *args: Any, **kwargs: Any) → None
遍历当前所有仲裁器并依次调用func回调函数。该回调函数需要一个参数表示仲裁器对象。还可以将*args, **kw参数传递给func。关于仲裁器详见后文。
property force: Vec2d
施加在物体重心上的力(每次space.step后重置)
property id: int
身体的标识符(独一无二的)。如果复制身体或用pickle模块转储身体会得到新的标识符。
property is_sleeping: bool
判断身体是否处于“睡眠”状态。处于睡眠状态的身体不再刷新,但仍然参与碰撞检测。
property kinetic_energy: float
身体具有的的动能
local_to_world(v: Tuple[float, float]) → Vec2d
将局部坐标转换为世界坐标。局部坐标是相对于Body.position的坐标,而世界坐标相对于整个空间。前文apply_force_at_local_point和apply_force_at_world_point已有提及。
world_to_local(v: Tuple[float, float]) → Vec2d
将世界坐标转换为局部坐标。
property mass: float
物体质量
property moment: float
物体的惯性矩(有时候指的就是力矩)。有点像物体旋转时候的“质量”。
property position: Vec2d
物体的位置
property position_func
刷新身体的位置时所调用的回调函数,详见上文。
position_func(body, dt) -> None
property rotation_vector: Vec2d
身体的旋转向量
property shapes: Set[Shape]
附加在身体上的形状集合(所有形状都是弱引用的)
sleep() → None
让身体进入睡眠状态。如果身体已经处于睡眠状态(可通过is_sleeping属性查询)或者未设置space.sleep_time_threshold,会报错。
sleep_with_group(body: Body) → None
让身体和给定的body同时进入睡眠状态。当身体唤醒时,与之关联的body会被同时唤醒。如果给定body参数为None,效果和sleep()一样。
property space: Space | None
身体被添加进去的空间。None表示这个身体没有被加入空间。
property torque: float
施加在身体上的扭矩。(每次space.step时重置)
property velocity: Vec2d
身体的速度
velocity_at_local_point(point: Tuple[float, float]) → Vec2d
返回物体在某一点上的速度(使用局部坐标)
velocity_at_world_point(point: Tuple[float, float]) → Vec2d
获取物体在某一点上的速度(使用世界坐标)
property velocity_func
修改身体的速度时所调用的回调函数,详见上文。
velocity_func(body : Body, gravity, damping, dt)

4 数学运算辅助

在物理引擎中常常会用到一些python没有直接提供的数学运算类型和方法,包括向量、边界框、变换矩阵,本节将介绍这些内容。

4.1 向量

向量在物理上常被称做矢量,指的是有方向的量。在坐标系中表示一个向量需要同时表示出向量的方向和大小。为了便于表示和运算,往往将向量分解成一个x方向和y方向上的两个量,等价于原本的向量。例如一个位于第一象限,大小为2的向量,与x轴正方向夹角为30°,则这个向量可以被分解成 (根号3, 1)。

pymunk.Vec2d对象表示一个向量。创建一个向量对象需要提供两个参数,分别是向量的x和向量的y;同时向量对象具有两个属性x和y。如果想要获取一个向量对象的x坐标,可以通过Vec2d对象的x属性,也可以像元组一样对待这个向量,0索引位置表示向量的x坐标,1索引位置表示向量的y坐标。

>>> Vec2d = pymunk.Vec2d
>>> v = Vec2d(3, 4)
>>> v.x, v.y
(3, 4)
>>> v[0], v[1]
(3, 4)

向量可以进行加法、减法运算,也就是把对应的x和y进行相加或相减。

>>> Vec2d(1, 2) + Vec2d(3, 4)
Vec2d(4, 6)
>>> Vec2d(1, 2) - Vec2d(3, 4)
Vec2d(-2, -2)

向量可以乘以或除以一个单独的数字。

>>> Vec2d(1, 2) * 2
Vec2d(2, 4)
>>> Vec2d(1, 2) / 2
Vec2d(0.5, 1.0)
>>> Vec2d(1, 2) // 2 # 地板除
Vec2d(0, 1)

对向量进行取负操作相当于把向量x,y全部取相反数。

>>> -Vec2d(1, 2)
Vec2d(-1, -2)

取向量的绝对值或通过向量的length属性可以获取向量的长度。

>>> v = Vec2d(3, 4)
>>> abs(v)
5.0
>>> v.length
5.0

旋转向量可以使用rotated方法和rotated_degrees方法,分别以弧度和角度作为旋转度数的单位。

>>> Vec2d(1, 0).rotated(3.1415926 / 2)
Vec2d(2.6794896585028633e-08, 0.9999999999999997) # 约等于(0,1)
>>> Vec2d(1, 0).rotated_degrees(90)
Vec2d(6.123233995736766e-17, 1.0) # 约等于(0,1)

normalized方法将向量“归一化”,也就是把向量进行缩放至长度为1。


>>> Vec2d(3, 4).normalized()
Vec2d(0.6, 0.8)

scale_to_length方法将向量进行缩放,至长度等于给定数值。

>>> Vec2d(0.6, 0.8).scale_to_length(5)
Vec2d(3.0, 4.0)

pymunk有很多地方使用了向量。例如物体速度属性velocity,物体位置属性position,这些都是Vec2d对象。不过如果将它们的值设为二维元组(x, y)也可以被pymunk接受。

4.2 Vec2d对象属性参考

class pymunk.Vec2d(x: float, y: float)
创建一个二维向量对象。
property angle: float
向量的夹角度数(弧度)。以(1, 0)方向的向量为0度,值为正数表示逆时针旋转(按照数学上的右-上坐标系)。
property angle_degrees: float
向量的夹角度数(角度)。
convert_to_basis(x_vector: Tuple[float, float], y_vector: Tuple[float, float]) → Vec2d
将x_vector和y_vector作为向量基底,转换当前的向量,得到一个新的向量。
cpvrotate(other: Tuple[float, float]) → Vec2d
通过复数乘法将两个向量相乘。
cpvunrotate(other: Tuple[float, float]) → Vec2d
cpvrotate的逆。
cross(other: Tuple[float, float]) → float
求两个向量的叉积。公式:v1.xv2.y - v1.yv2.x
dot(other: Tuple[float, float]) → float
求两个向量的点积。公式:v1.xv2.x + v1.yv2.y
get_angle_between(other: Tuple[float, float]) → float
获取两个向量的夹角(弧度)
get_angle_degrees_between(other: Vec2d) → float
获取两个向量的夹角(角度)
get_dist_sqrd(other: Tuple[float, float]) → float
获取两个向量表示的坐标之间的距离的平方(两个向量横纵坐标之差的平方和)。公式:(v1.x-v2.x) ** 2 + (v1.y-v2.y) ** 2
get_distance(other: Tuple[float, float]) → float
获取两个向量表示的坐标之间的距离(通过勾股定理)。公式:sqrt((v1.x-v2.x) ** 2 + (v1.y-v2.y) ** 2)
get_length_sqrd() → float
获取向量长度的平方
property int_tuple: Tuple[int, int]
将向量通过round()进行四舍五入后得到的整数元组。
interpolate_to(other: Tuple[float, float], range: float) → Vec2d
在两个向量之间取线性插值。
property length: float
获取向量长度
normalized() → Vec2d
将向量归一化(进行缩放,使长度为1)。
normalized_and_length() → Tuple[Vec2d, float]
返回一个归一化后的向量,以及向量归一化之前的长度。
perpendicular() → Vec2d
将向量逆时针旋转90°得到的新的向量。(按照数学上的右-上坐标系)
perpendicular_normal() → Vec2d
返回perpendicular()得到的向量归一化后的结果
projection(other: Tuple[float, float]) → Vec2d
将当前向量投影至给定向量。
rotated(angle_radians: float) → Vec2d
旋转向量(弧度)
rotated_degrees(angle_degrees: float) → Vec2d
旋转向量(角度)
scale_to_length(length: float) → Vec2d
缩放向量,使其长度等于给定数值
x: float
y: float
向量的横、纵坐标
static unit() → Vec2d
创建一个(0, 1)的向量
static ones() → Vec2d
创建一个(1, 1)的向量
static zero() → Vec2d
创建一个(0, 0)的向量

4.3 矩形边界框

pymunk.BB表示一个边界框(bounding box)。你可以把它理解为一个矩形,其中包含了这个矩形的各个坐标和长度信息。

pymunk.BB类似于pygame.FRect(如果你学过pygame)

要创建一个边界框,需要提供边界框的左、下、右、上边的位置作为参数。这些参数同时也是BB对象的属性。

>>> BB(left=1, bottom=5, right=20, top=10)
BB(left=1, bottom=5, right=20, top=10)

这表示一个左上角坐标位于(1, 10),宽为19,高为5的矩形。注意:边界框在物理引擎中没有任何意义,它并不代表一个矩形的形状,它的作用仅仅是辅助计算。

>>> BB(right=5, top=10)
BB(left=0, bottom=0, right=5, top=10)

这样创建一个边界框也可以被接受。

area()方法用于获取边界框的面积。center()方法用于获取边界框的中心点的位置。

>>> from pymunk import BB
>>> bbox = BB(right=10, top=5)
>>> bbox.area()
50.0
>>> bbox.center()
Vec2d(5.0, 2.5)

注意:pymunk的坐标系和数学上的右-上坐标系是一样的,因此你不能使用BB(10, 5)这样的写法。这种写法会创建出这样一个边界框BB(left=10, bottom=5, right=0, top=0),它的宽度和高度是负值。这样的写法不影响area()和center()函数的使用,但是会影响一些其他函数。

BB.contains方法用于判断另一个边界框是否被此边界框完全包围。BB.intersects方法用于判断两个边界框是否相交。contains是intersects的一种特殊情况。

>>> b1 = BB(left=0, right=10, bottom=0, top=5)
>>> b2 = BB(left=1, right=10, bottom=2, top=4)
>>> b1.contains(b2)
True
>>> b1.intersects(b2)
True

BB.merge方法返回一个同时包含两个边界框的一个最小的边界框。比如,b1.merge(b2)得到的边界框同时包含b1和b2这两个边界框。

>>> b1 = BB(left=0, right=10, bottom=0, top=5)
>>> b2 = BB(left=0, right=30, bottom=-10, top=0)
>>> b1.merge(b2)
BB(left=0.0, bottom=-10.0, right=30.0, top=5.0)

BB在pymunk中也有一定应用。Shape对象包含一个bb属性,表示这个形状的矩形边界框。

4.4 BB对象属性参考

class pymunk.BB(left: float = 0, bottom: float = 0, right: float = 0, top: float = 0)
创建2D边界框对象。
bottom: float
边界框的底部y坐标
top: float
边界框的顶部y坐标
left: float
边界框的左侧x坐标
right: float
边界框的右侧x坐标
area() → float
返回边界框的面积
center() → Vec2d
返回边界框的中心点坐标
clamp_vect(v: Tuple[float, float]) → Vec2d
返回给定向量被限制在边界框之内的部分
contains(other: BB) → bool
判断当前边界框是否完全包含给定边界框
contains_vect(v: Tuple[float, float]) → bool
判断当前边界框是否完全包含给定向量
expand(v: Tuple[float, float]) → BB
返回一个最小的边界框,使其同时包含当前边界框和给定向量。
intersects(other: BB) → bool
判断两个边界框是否相交。
intersects_segment(a: Tuple[float, float], b: Tuple[float, float]) → bool
判断给定线段是否与边界框相交。a,b分别是线段的起点和终点。
merge(other: BB) → BB
返回一个同时包含两个边界框的最小边界框。
merged_area(other: BB) → float
计算一个同时包含两个边界框的最小边界框,返回此边界框的面积
segment_query(a: Tuple[float, float], b: Tuple[float, float]) → float
沿着线段的起始点到终点,遍历线段上的每一个点,当线段上的点与边界框相交时,返回遍历到的点位于线段的哪个位置(0-1之间的浮点数)。如果始终没有相交,返回float(“inf”)
static newForCircle(p: Tuple[float, float], r: float) → BB
返回一个p为圆心,r为半径的圆的矩形边界框

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

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: pymunk是一个用于物理模拟的Python库,您可以使用它来绘制圆弧。要绘制圆弧,首先需要创建一个空间,然后使用pymunk.Arc()函数创建一个弧形对象。可以通过设置弧形对象的位置、半径和角度来控制弧形的形状。最后,可以使用pymunk.Space.add()函数将弧形对象添加到空间中,并使用pymunk.pygame_util.draw()函数在Pygame窗口中绘制弧形。 示例代码: ``` import pymunk import pymunk.pygame_util import pygame # 创建Pygame窗口 screen = pygame.display.set_mode((600, 600)) # 创建pymunk空间 space = pymunk.Space() # 创建圆弧 arc = pymunk.Arc(space.static_body, 50, 0, 180, (300, 300)) space.add(arc) # 循环渲染 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 绘制圆弧 screen.fill((255, 255, 255)) pymunk.pygame_util.draw(screen, space) pygame.display.flip() # 退出 pygame.quit() ``` ### 回答2: pymunk是一个强大的物理引擎库,它允许我们在Python中模拟物理世界和物体的运动。然而,pymunk本身并不提供绘制功能,因此我们需要借助其他库(例如pygame)来进行绘制。 要使用pymunk绘制圆弧,我们可以按照以下步骤进行: 1. 首先,我们需要导入所需的库,包括pymunk和pygame。确保这些库已经安装在你的Python环境中。 2. 创建一个绘制窗口。可以使用pygame的相关函数来创建一个窗口,并设置窗口的大小和标题。 3. 创建一个pymunk的空间(Space)对象。pymunk的空间用于模拟物理环境和物体的运动。 4. 创建一个pymunk刚体(Body)对象。刚体用于表示物体,并设置刚体的质量、惯性和形状。 5. 创建一个pymunk的形状(Shape)对象。形状用于定义刚体的形状和碰撞特性。 6. 添加刚体和形状到空间中。通过空间的add函数将刚体和形状添加到空间中。 7. 循环绘制。使用pygame的绘制函数在窗口中绘制圆弧。可以使用pymunk刚体和形状的属性来获取圆弧的位置和大小。 8. 在每次循环结束后更新空间,以便物体的运动会被模拟。 9. 在绘制循环中添加事件处理,以便可以关闭窗口或按下键盘时退出程序。 绘制圆弧的具体代码可以根据实际需求进行调整,但以上步骤提供了一个基本的框架来绘制圆弧。希望这些信息对你有帮助! ### 回答3: Pymunk是一个用于物理模拟和碰撞检测的Python库,它是Chipmunk物理引擎Python绑定。 在pymunk中,要绘制圆弧,可以借助 Shape类,并设置圆弧的半径、起始角度和终止角度。 首先,我们需要创建一个空的空间(Space)来进行物理模拟和碰撞检测,可以通过如下代码创建一个空间: ```python space = pymunk.Space() ``` 接下来,我们创建一个静态的圆弧形状(StaticBody),设置其位置和半径: ```python body = pymunk.Body(body_type=pymunk.Body.STATIC) body.position = (0, 0) # 设置圆弧的位置 radius = 50 # 设置圆弧的半径 ``` 然后,我们通过引入 pymunk.constraints 包来创建一个旋转关节(RotaryLimitJoint)来约束圆弧的运动范围,这样圆弧就不会旋转超出我们指定的角度范围: ```python constraint = pymunk.constraints.RotaryLimitJoint(body, body, start_angle, end_angle) ``` 其中 start_angle 和 end_angle 分别为起始角度和终止角度,可以根据需要设置。 最后,将圆弧形状和旋转关节添加到空间中,用于物理模拟: ```python space.add(body, constraint) ``` 通过这样的步骤,我们就成功创建了一个pymunk绘制的圆弧。你可以根据需要调整圆弧的位置、半径和角度范围。 pymunk提供了许多其他的功能和选项,例如弹簧约束、摩擦等,在实际应用中可以根据需要进行使用和配置。希望这些信息对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值