概述¶
基础知识¶
有4个基本类,你将在Pymunk使用。
- Rigid Bodies (
pymunk.Body
) 刚体具有对象的物理属性。(质量、位置、旋转、速度等)它本身没有形状。如果你以前对粒子做过物理学,刚体的主要不同之处在于它们能够旋转。刚体通常与游戏中的精灵具有1:1的相关性。您应该组织您的游戏,以便您使用刚体的位置和旋转来绘制您的精灵。
- Collision Shapes (
pymunk.Circle
,pymunk.Segment
andpymunk.Poly
) 通过将形状附着到实体,可以定义实体的形状。可以将多个形状附加到单个实体以定义复杂形状,或者如果不需要形状,则不附加任何形状。
- Constraints/Joints (
pymunk.constraint.PinJoint
,pymunk.constraint.SimpleMotor
and many others) 可以在两个实体之间附着约束以约束其行为,例如,在两个实体之间保持固定距离。
- Spaces (
pymunk.Space
) 空间是Pymunk的基本模拟单位。您可以向空间添加实体、造型和约束,然后将空间作为一个整体进行更新。它们控制所有刚体、图形和约束相互作用的方式。
实际的模拟是由Space完成的。将应模拟的对象添加到时空后,使用 pymunk.Space.step()
功能。
为您的物理对象建模¶
对象形状¶
你在屏幕上看到的东西并不一定要与实际物理物体的形状完全相同。通常,用于碰撞检测(和其他物理模拟)的形状是屏幕上绘制的图形的简化版本。即使是高端的AAA游戏也会将碰撞形状与屏幕上绘制的图形分开。
将碰撞形状和绘制的内容分开是很好的做法,原因有很多。
使用更简单的碰撞形状速度更快。因此,如果您有一个非常复杂的对象,例如松树,也许将其碰撞形状简化为三角形以提高性能是有意义的。
使用更简单的碰撞形状可以使模拟效果更好。假设你的地板是石头做的,中间有一个小裂缝。如果你在地板上拖一个盒子,它就会卡在裂缝上。但如果你把地板简化成一个平面,你就不必担心东西卡在裂缝里了。
使碰撞形状比实际对象更小(或更大)会使游戏效果更好。让我们假设你有一个玩家控制的飞船在射击类游戏中。很多时候,如果将碰撞形状设置得比应该基于外观的形状小一点,玩起来会更有趣。
您可以在 using_sprites.py Pymunk中包含的示例。在那里,物理形状是一个三角形,但绘制的是一个金字塔中的3个盒子,上面有一条蛇。另一个例子是 platformer.py 例如,玩家被画成一个穿红灰相间的女孩。然而,物理形状只是几个叠加在一起的圆形。
重心¶
创造物体形状的一个重要部分是确保它的重心在它应该的位置。在大多数情况下,您希望它位于形状的中心(S),但就像在现实生活中一样,如果重心不在实际的中心,它可以创建有趣的对象。
以下是几个示例,如何轻松地将中心放置在形状的一角:
但是请注意,圆是在中心处自动创建的,并且辅助对象创建了一个长方体 pymunk.Poly.create_box()
它的重心也会在中间。
重心可以通过两种不同的方式移动:
Segment(body, (0,0), (6,6))
可以更改为Segment(body, (-3,-3), (-3,-3))
。重心可以直接在身体上调整:
body.center_of_gravity = (3,3)
多边形形状可以使用
pymunk.Transform
。Poly(body, [...], pymunk.Transform.translation(3,3)
质量、重量和单位¶
有时,Pymunk的用户可能会困惑于一切都是用什么单位定义的。例如,物体的质量是以克还是公斤为单位?Pymunk是无单位的,并不关心你使用哪个单位。如果以秒为单位传递给期望时间的函数,则时间单位为秒。如果将像素传递给期望距离的函数,则距离的单位是像素。
那么派生单位就是上述的组合。因此,在秒和像素的情况下,速度的单位是像素/秒。
(这与其他一些物理引擎不同,这些引擎可以有您应该使用的固定单位)
先看后现实主义¶
《愤怒的小鸟》中的小鸟有多重?没关系,这是卡通片!
与这些单元一起,设置模拟时的另一个关键洞察力是记住这是一个模拟,在许多情况下,外观和感觉比实际的真实感重要得多。例如,如果你想模拟一款鳍状物游戏,鳍状物和启动器的真正威力并不重要,重要的是这款游戏给人的感觉是正确的,对你的用户来说使用起来很有趣。
有时,从真实的单位开始是有意义的,让你感觉到与重力相比,质量应该有多大。
当然也有例外,当你真的想要现实而不是外观时。最终,这取决于您作为一名Pymunk用户的决定。
游戏循环/向前移动时间¶
游戏循环中最重要的部分是将dt参数保留为 pymunk.Space.step()
函数常量。恒定的时间步长使模拟更加稳定可靠。
有几种方法可以做到这一点,有些方法比另一些方法更复杂。哪一种最适合特定的程序取决于要求。
一些好文章:
对象隧道¶
有时,一个物体可以穿过另一个物体,尽管它不应该这样做。通常发生这种情况是因为对象移动得太快,以至于在调用space.step()时对象会从一边移动到另一边。
有几种方法可以缓解这个问题。有时,多做一件事可能是个好主意。
确保物体的速度永远不会太高。要做到这一点,一种方法是使用自定义速度函数,该函数在有过快移动趋势的身体上内置限制:
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 body_to_limit.velocity_func = limit_velocity
根据要求,将速度限制在多个帧上可能更有意义。则限制函数可能如下所示::
def limit_velocity(body, gravity, damping, dt): max_velocity = 1000 pymunk.Body.update_velocity(body, gravity, damping, dt) if body.velocity.length > max_velocity: body.velocity = body.velocity * 0.99
对于项目符号等对象,请使用空格查询,如space.Segment_Query或space.Segment_first。
在对space.Step的调用中使用较小的dt值。一种简单的方法是在应用程序中多次调用space.Step。这也将有助于使整体模拟更加稳定。
仔细检查所有物体的重心是否在合理的点上。
模拟不稳定?¶
有时,模拟可能不会按预期运行。在极端情况下,它可能会“爆炸”,零件会毫无逻辑地移动到任何地方。
如果发生这种情况,可以尝试以下几种方法:
使所有物体具有相似的质量。物理引擎更容易处理重量相近的物体。
不要让两个质量无限的物体互相接触。
将重心放在形状的中间,而不是边缘。
非常薄的形状可能会表现出奇怪的行为,尽量让它们变得更宽一些。
有一个固定的时间步长(请参阅本指南的其他部分)。
使用较小的DT多次调用Space.Step函数,而不是只调用一次,而是使用较大的DT。 Space.step )
如果使用电动关节,请确保设置其最大力。否则,它的力量将近乎无限。
仔细检查所有物体的重心是否在合理的点上。
(这些建议中的大多数都适用于大多数物理引擎,而不仅仅是Pymunk。)
性能¶
可以提高性能的各种提示:
在启用优化的情况下运行Python(将禁用各种有用但非关键的断言)。
python -O mycode.py
如果可能,请使用PyPy而不是CPython。看见 Benchmarks 关于速度差异的一些例子。
调整
Space.iterations
财产。如果可能的话,让物体睡着
Space.sleep_time_threshold
。减少回调方法(如冲突回调或定制更新函数)的使用。这些代码比默认的内置代码慢得多。
请注意,许多情况下,实际模拟足够快,但在每个步骤之后读出结果并手动操作对象可能会有很大的开销和性能成本。
复制和加载/保存皮尔芒克对象¶
大多数Pymunk对象可以从标准库中复制和/或使用PICLE保存。由于实现是泛型的,因此它还可以使用其他序列化器库,如 jsonpickle (与Pickle相反,只要使用Pickle基础设施,jsonPickle就可以与json进行串行化)。
请参阅 copy_and_pickle.py 有关如何保存、加载和复制派芒克对象的示例。
请注意,用于代码保存的Pymunk版本必须与加载保存的对象时使用的版本相同。
更多信息¶
作为对Pymunk文档的补充,阅读 Chipmunk docs 。它是为Chipmunk制作的,但金色鼠构建在Chipmunk之上,并分享了大多数概念,主要的区别是,金色鼠是从Python中使用的,而Chipmunk是一个C库。
关于Pymunk为什么存在的背景故事,我在自己的网站https://www.viblo.se/projects/pymunk/上有一篇关于背景和历史的简短帖子