滑销联动演示分步演示¶
这是一个循序渐进的教程,解释了pymunk中包含的DEMO Slide_and_pinjoint.py。您可以在以下列表中找到它的截图 examples 。如果我在教程中遗漏了什么或者有什么不清楚的地方,把文件放在旁边可能是个好主意。

在我们开始之前¶
在本教程中,您需要:
Python (当然)
PYGAME(网址:www.pygame.org)
Pymunk
本教程和所包含的一些演示程序需要使用PyGame,但并不是只要求运行pymunk。在其他类似的库中,Pymunk应该也可以很好地工作,例如,您可以很容易地将本教程翻译为使用Pyglet。
《Chipmunk》是在2D物理类库《Chipmunk》的基础上建造的。Chipmunk本身是用C编写的,这意味着Chipmunk需要调用C代码。CFFI库对此很有帮助,但是,如果您使用的是我无法编译的平台,则可能需要您自己进行编译。好消息是,这是非常容易做的,事实上,如果你用Pip得到了Pymunk,安装它已经完成了!
安装了pymunk后,尝试从python提示符下导入它,以确保它可以正常工作并且可以导入:
>>> import pymunk
有关安装的更多信息可在此处找到: Installation
如果它不起作用或你有什么问题,请随时在Chipmunk论坛上发表帖子,直接联系我,或将你的问题添加到问题跟踪器中: Contact & Support
空洞的模拟¶
好的,我们开始吧。Chipmunk有几个核心概念,这一点在《Chipmunk文献》的引文中得到了很好的解释:
- 刚体
刚体具有对象的物理属性。(质量、位置、旋转、速度等)它本身没有形状。如果你以前对粒子做过物理学,刚体的主要不同之处在于它们能够旋转。
- 碰撞形状
通过将形状附加到实体,可以定义实体的形状。可以将多个形状附加到单个实体以定义复杂形状,或者如果不需要形状,则不附加任何形状。
- 约束/运动类型
可以在两个实体之间附着关节以约束其行为。
- 空间
空间是Chipmunk的基本模拟单位。您可以将实体、形状和关节添加到空间,然后将空间作为一个整体进行更新。
Chipmunk的文档可以在这里找到:http://chipmunk-physics.net/release/ChipmunkLatest-Docs/它是针对c库的,但它是对Pymunk文档的一个很好的补充,因为它们的概念是相同的,只是Pymunk更易于使用。
可以在以下位置找到Pymunk的API文档: API参考 。
无论如何,我们现在准备编写一些代码::
import sys
import pygame
import pymunk #1
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
space = pymunk.Space() #2
space.gravity = (0.0, 900.0)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
sys.exit(0)
screen.fill((255,255,255))
space.step(1/50.0) #3
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
该代码将显示一个空白窗口,并将运行一个空白空间的物理模拟。
我们需要引入Pymunk才能使用它。
然后我们创造一个空间,并把它的重力设置为好的东西。记住,重要的是屏幕上看起来好的东西,而不是真实世界的价值。900将会是一个很好看的模拟,但是当你准备好完整的代码后,你可以自由地进行实验。
在我们的游戏循环中,我们在空间上调用Step()函数。每次调用时,Step函数在时间上将模拟向前推进一步。
备注
最好保持步长不变,而不是根据帧速率进行调整。在步长不变的情况下,物理模拟效果会好得多。
落球¶
最容易处理(和绘制)的形状是圆形。因此,我们的下一步是让一个球偶尔产卵。在许多示例演示中,所有代码都放在main()函数中的一个大堆中,因为它们非常小和容易,但我将在本教程中提取一些方法,以使其更容易理解。首先,将球添加到空格的函数::
def add_ball(space):
mass = 3
radius = 25
body = pymunk.Body() # 1
x = random.randint(120, 300)
body.position = x, 50 # 2
shape = pymunk.Circle(body, radius) # 3
shape.mass = mass # 4
shape.friction = 1
space.add(body, shape) # 5
return shape
我们首先创建球的主体。
我们设定了它的位置
为了让它与物体碰撞,它需要有一个(或多个)碰撞形状(S)。
所有物体的转动惯量都必须设定好。在大多数情况下,让Pymunk处理形状的计算是最容易的。所以我们设置每个形状的质量,然后当添加到空间时,物体将自动获得适当的质量和力矩设置。另一种选择是设置每个形状的密度,或者也可以直接在身体上设置值(甚至可以在之后调整它们)。
为了让球滚动,我们在形状上设置了摩擦力。(默认情况下为0)。
最后,我们将身体和形状添加到空间中,以将其包含在模拟中。请注意,主体必须始终添加到空间之前或与附加到其上的任何形状同时添加。
现在我们可以创建球了,我们想要展示它们。我们可以使用内置的pymunk_util包直接绘制整个空间,也可以手动绘制。Pymunk中包含的调试绘图功能非常适合于轻松快速地组合一些东西,而例如,一款经过打磨的游戏很可能会想要制作自己的绘图代码。
如果要手动绘制,则绘制函数可能如下所示::
def draw_ball(screen, ball):
p = int(ball.body.position.x), int(ball.body.position.y)
pygame.draw.circle(screen, (0,0,255), p, int(ball.radius), 2)
然后以这种方式调用(假设我们收集了一个名为Balls的列表中的所有球形状)::
for ball in balls:
draw_ball(screen, ball)
然而,因为我们在本例中使用了pyGame,所以我们可以改用已经包含在Pymunk中的DEBUG_DRAW方法来简化一点。首先需要导入它,然后我们必须创建一个DrawOptions对象,其中包含以下选项(在使用PyGame的情况下,在哪个表面上绘制):
import pymunk.pygame_util
...
draw_options = pymunk.pygame_util.DrawOptions(screen)
在那之后,当我们想画出我们所有的形状时,我们就会这样做::
space.debug_draw(draw_options)
Pymunk附带的大多数示例都使用这种绘制方法。
使用ADD_BALL函数和DEBUG_DRAW调用以及一些生成球的代码,您应该会看到几个球落下。耶!
import sys, random
random.seed(1) # make the simulation the same each time, easier to debug
import pygame
import pymunk
import pymunk.pygame_util
#def add_ball(space):
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = (0.0, 900.0)
balls = []
draw_options = pymunk.pygame_util.DrawOptions(screen)
ticks_to_next_ball = 10
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
sys.exit(0)
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
space.step(1/50.0)
screen.fill((255,255,255))
space.debug_draw(draw_options)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
main()
一动不动的L¶
落球很无聊。除了基本的重力,我们看不到任何物理模拟,每个人都可以在没有物理库帮助的情况下计算重力。所以让我们添加一些球可以落在上面的东西,两条静态的线组成一个L。就像对于球,我们从一个函数开始,在空格中添加一个L::
def add_static_L(space):
body = pymunk.Body(body_type = pymunk.Body.STATIC) # 1
body.position = (300, 300)
l1 = pymunk.Segment(body, (-150, 0), (255, 0), 5) # 2
l2 = pymunk.Segment(body, (-150, 0), (-150, -50), 5)
l1.friction = 1 # 3
l2.friction = 1
space.add(body, l1, l2) # 4
return l1,l2
我们创造了一个“静态”的身体。重要的一步是永远不要把它添加到空间中,就像动态的球体一样。注意静态主体是如何通过设置主体的BODY_TYPE来创建的。很多时候,使用空间中已经存在的静态实体会更容易 (space.static_body ),但我们将在稍后使L形状动态化。
这里创建了一个线形形状。
设置摩擦力。
同样,我们只将片段添加到空间中,而不是身体。
因为我们使用Space.DEBUG_DRAW来绘制空间,所以我们不需要为段执行任何特殊的绘制代码,但我仍然在这里包含一个可能的绘制函数,只是为了展示它可能是什么样子::
def draw_lines(screen, lines):
for line in lines:
body = line.body
pv1 = body.position + line.a.rotated(body.angle) # 1
pv2 = body.position + line.b.rotated(body.angle)
p1 = to_pygame(pv1) # 2
p2 = to_pygame(pv2)
pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])
为了得到直线旋转时的位置,我们使用了这种计算。Line.a是该线的第一个端点,line.b是第二个端点。目前这些线是静止的,没有旋转,所以我们不需要做额外的计算,但我们很快就会让它们移动和旋转。
这是一个小函数,可以将坐标从土拨鼠转换到土拨鼠世界。现在我们有了它,我们也可以在DrawBall()函数中使用它。
def to_pygame(p):
"""Small helper to convert pymunk vec2d to pygame integers"""
return round(p.x), round(p.y)
有了完整的代码,我们应该类似于下面的内容,现在我们应该看到中间倒置的L形状,Will Ball正在产卵并击中该形状。
import sys, random
random.seed(1) # make the simulation the same each time, easier to debug
import pygame
import pymunk
import pymunk.pygame_util
#def to_pygame(p):
#def add_ball(space):
#def add_static_l(space):
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = (0.0, 900.0)
lines = add_static_L(space)
balls = []
draw_options = pymunk.pygame_util.DrawOptions(screen)
ticks_to_next_ball = 10
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
sys.exit(0)
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
space.step(1/50.0)
screen.fill((255,255,255))
space.debug_draw(draw_options)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
main()
关节(1)¶
静态的L造型很无聊。因此,让我们通过添加两个关节来使它更令人兴奋,一个关节可以绕它旋转,另一个关节防止它旋转太多。在此部分中,我们只添加旋转关节,在下一个部分中,我们将约束它。因为我们的静态L形状将不再是静态的,所以我们还将该函数重命名为Add_L()。**
def add_L(space):
rotation_center_body = pymunk.Body(body_type=pymunk.Body.STATIC) # 1
rotation_center_body.position = (300, 300)
body = pymunk.Body()
body.position = (300, 300)
l1 = pymunk.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pymunk.Segment(body, (-150.0, 0), (-150.0, -50.0), 5.0)
l1.friction = 1
l2.friction = 1
l1.mass = 8 # 2
l2.mass = 1
rotation_center_joint = pymunk.PinJoint(
body, rotation_center_body, (0, 0), (0, 0)
) # 3
space.add(l1, l2, body, rotation_center_joint)
return l1, l2
这是旋转中心体。它的唯一用途是充当关节中的静态点,以便直线可以围绕它旋转。如你所见,我们从不给它添加任何形状。
L的形状现在将在世界上移动,因此它不再是一个静态的物体。在这里,我们看到了将质量设置在形状上而不是身体上的好处,不需要计算力矩应该有多大,Pymunk会自动计算重心。
销联接允许两个对象绕单个点旋转。在我们的例子中,其中一个物体将粘在世界上。
关节(2)¶
在前一部分中,我们添加了销联接,现在是约束旋转的L形状以创建更有趣的模拟的时候了。为此,我们修改了Add_L()函数::
def add_L(space):
rotation_center_body = pymunk.Body(body_type = pymunk.Body.STATIC)
rotation_center_body.position = (300,300)
rotation_limit_body = pymunk.Body(body_type = pymunk.Body.STATIC) # 1
rotation_limit_body.position = (200,300)
body = pymunk.Body()
body.position = (300,300)
l1 = pymunk.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pymunk.Segment(body, (-150.0, 0), (-150.0, -50.0), 5.0)
l1.friction = 1
l2.friction = 1
l1.mass = 8
l2.mass = 1
rotation_center_joint = pymunk.PinJoint(body, rotation_center_body, (0,0), (0,0))
joint_limit = 25
rotation_limit_joint = pymunk.SlideJoint(body, rotation_limit_body, (-100,0), (0,0), 0, joint_limit) # 2
space.add(l1, l2, body, rotation_center_joint, rotation_limit_joint)
return l1,l2
我们加上一具身体..。
创建滑动关节。它的行为类似于销联接,但具有最小和最大距离。这两个物体可以在最小和最大之间滑动,在我们的例子中,其中一个物体是静态的,这意味着只有连接了形状的物体才会移动。
收尾¶
你可能注意到了,我们从不删除球。这会使模拟需要越来越多的内存,占用越来越多的CPU,这当然不是我们想要的。因此,在最后一步中,我们添加了一些代码,以便当球在屏幕下方时从模拟中删除它们。**
balls_to_remove = []
for ball in balls:
if ball.body.position.y < 0: # 1
balls_to_remove.append(ball) # 2
for ball in balls_to_remove:
space.remove(ball, ball.body) # 3
balls.remove(ball) # 4
让球循环,检查身体的位置是否小于0。
如果是这样的话,我们把它添加到我们要移除的球的列表中。
要从空间中移除一个物体,我们需要移除它的形状和身体。
然后我们把它从我们的球列表中删除。
现在,完成了!你应该在屏幕中央有一个倒置的L形状,填满意志球,倾倒释放它们,然后向后倾斜,然后重新开始。您可以检查pymunk中包含的lide_and_pinjoint.py,但它并不完全遵循本教程,因为我将几个块分解为函数,以便于以教程的形式进行操作。
如果有任何不清楚的地方,不工作的人可以随时在GitHub上提出问题。如果你对另一个你想读的教程有一个想法,或者你想在Chipmunk中看到一些示例代码,请把它写在某个地方(比如在Chipmunk论坛)
本教程的完整代码为:
import sys, random
random.seed(1) # make the simulation the same each time, easier to debug
import pygame
import pymunk
import pymunk.pygame_util
def add_ball(space):
"""Add a ball to the given space at a random position"""
mass = 3
radius = 25
inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
body = pymunk.Body(mass, inertia)
x = random.randint(120,300)
body.position = x, 50
shape = pymunk.Circle(body, radius, (0,0))
shape.friction = 1
space.add(body, shape)
return shape
def add_L(space):
"""Add a inverted L shape with two joints"""
rotation_center_body = pymunk.Body(body_type = pymunk.Body.STATIC)
rotation_center_body.position = (300,300)
rotation_limit_body = pymunk.Body(body_type = pymunk.Body.STATIC)
rotation_limit_body.position = (200,300)
body = pymunk.Body(10, 10000)
body.position = (300,300)
l1 = pymunk.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pymunk.Segment(body, (-150.0, 0), (-150.0, -50.0), 5.0)
l1.friction = 1
l2.friction = 1
l1.mass = 8
l2.mass = 1
rotation_center_joint = pymunk.PinJoint(body, rotation_center_body, (0,0), (0,0))
joint_limit = 25
rotation_limit_joint = pymunk.SlideJoint(body, rotation_limit_body, (-100,0), (0,0), 0, joint_limit)
space.add(l1, l2, body, rotation_center_joint, rotation_limit_joint)
return l1,l2
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = (0.0, 900.0)
lines = add_L(space)
balls = []
draw_options = pymunk.pygame_util.DrawOptions(screen)
ticks_to_next_ball = 10
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
sys.exit(0)
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
screen.fill((255,255,255))
balls_to_remove = []
for ball in balls:
if ball.body.position.y > 550:
balls_to_remove.append(ball)
for ball in balls_to_remove:
space.remove(ball, ball.body)
balls.remove(ball)
space.debug_draw(draw_options)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
main()