滑销联动演示分步演示¶
这是一个分步教程,解释了Pymunk中包含的演示slide_and_pinjoint.py。您会在列表中找到它的屏幕截图 examples .如果我错过了教程中的内容或不清楚的内容,最好将该文件放在附近。

在我们开始之前¶
在本教程中,您需要:
Python (当然)
PYGAME(网址:www.pygame.org)
Pymunk
本教程和包含的一些演示需要Pygame,但不需要只运行Pymunk。Pymunk也应该与其他类似的库一起工作,例如,您可以轻松地将此教程翻译为使用Pyglet。
Pymunk构建在2D物理库Munk 2D之上。Munk 2D本身是用C编写的,这意味着Pymunk需要调用c代码。Cffi库对此有帮助,但是,如果您所在的平台我无法在其上进行编译,则您可能需要自己进行。好消息是,这非常容易做到,事实上,如果您通过Pip安装了Pymunk,它就已经完成了!
安装Pymunk后,尝试从Python提示符导入它,以确保它正常工作并且可以导入::
>>> import pymunk
有关安装的更多信息可在此处找到: Installation
如果它不起作用或您遇到某种问题,请随时在花栗鼠论坛上发表帖子、直接联系我或将您的问题添加到问题跟踪器中: Contact & Support
空洞的模拟¶
好吧,开始吧。Munk 2D(以及Pymunk)有几个核心概念,这些概念在Munk 2D文档的引用中得到了很好的解释:
- 刚体
刚体具有对象的物理属性。(质量、位置、旋转、速度等)它本身没有形状。如果你以前对粒子做过物理学,刚体的主要不同之处在于它们能够旋转。
- 碰撞形状
通过将形状附加到实体,可以定义实体的形状。可以将多个形状附加到单个实体以定义复杂形状,或者如果不需要形状,则不附加任何形状。
- 约束/运动类型
可以在两个实体之间附着关节以约束其行为。
- 空间
空间是Munk 2D中的基本模拟单元。您将实体、形状和关节添加到空间,然后更新整个空间。
Munk 2D的文档可以在此处找到:https://viblo.github.io/Munk2D/它用于c-library,但是Pymunk文档的很好补充,因为概念是相同的,只是Pymunk更适合使用Python。
可以在以下位置找到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函数在时间上将模拟向前推进一步。
备注
最好保持步进大小不变,不要根据帧率进行调整。如果步进大小保持不变,RST模拟将工作得更好。
落球¶
最容易处理(和绘制)的形状是圆形。因此,我们的下一步是偶尔让球产卵。在许多示例演示中,所有代码都在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,我们可以使用 debug_draw
方法已经包含在Pymunk中以简化一点。它首先需要导入,接下来我们必须创建一个 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_类型来创建静态主体。很多时候,使用空间中已经存在的静态物体会更容易 (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
第二个。目前这些线是静态的,并且没有旋转,所以我们真的不必进行这种额外的计算,但我们很快就会让它们移动和旋转。这是一个将坐标从Pymunk转换到PymGame世界的小功能。现在我们有了它,我们可以在
draw_ball()
功能也是如此。
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
循环球并检查是否
body.position
小于0
.如果是这样的话,我们把它添加到我们要移除的球的列表中。
要从空间中移除一个物体,我们需要移除它的形状和身体。
然后我们把它从我们的球列表中删除。
现在,完成了!你应该在屏幕中间有一个倒L形,里面充满了球,翻转而释放它们,翻转而重新开始。您可以检查Pymunk中包含的slide_and_pinjoint.py,但它并不完全遵循本教程,因为我将几个模块分解为函数,以便更容易以教程形式遵循。
如果有什么不清楚的地方,请随时在Github上提出问题。如果您对想阅读的另一个教程有想法,或者想在Pymunk中看到一些示例代码,请将其写在某个地方(例如在花栗鼠论坛中)
本教程的完整代码为:
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
body = pymunk.Body()
x = random.randint(120,300)
body.position = x, 50
shape = pymunk.Circle(body, radius, (0,0))
shape.mass
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()