在碰撞中将能量从一个 object 转移到另一个(在 pymunk/chipmunk 中)
Transfer of energy from one object to another in collision (in pymunk/chipmunk)
这个问题是关于 pymunk 的,但我知道那里有更多的 Chimpunk 用户;所以如果你的答案涉及 C/Chipmunk 代码,没关系。虽然我不会写 C 代码,但如果我阅读别人的代码,我通常可以弄清楚是怎么回事。
设置
我正在模拟 top-down 滑动 object 游戏(想想冰壶或沙狐球)。
我已经对代码的相关部分做了一个最小的例子(在问题的末尾),但它的核心是:
- 创建了两个相同的物理 object(按照卷曲术语,我称它们为 'stones')
- 它们的位置具有相同的 x 值但不同的 y 值(一个在另一个上方的直线上)。
- 使用 apply_impulse(目前,虽然尝试了其他方法),下面的石头 'launched' 直接在另一块石头上
我希望达到的目标
当石头碰撞时,下面的石头应该突然停下来(或者可能会反弹一点——我还没有完全了解这个细节),而它的全部或大部分能量都转移到了上面的石头,它将开始向上移动。
我得到的是什么
当石头碰撞时,下面的石头并没有停止,而是开始将上面的石头推上屏幕。
好像下层比上层质量大,但它们是用相同的功能创建的,所以它们应该是相同的。
我已经上传了一个 .gif 到 imgur 来说明这一点,如果它有帮助的话:
http://imgur.com/a/FF6Xq
它比实际 运行 运行脚本时的帧率低,但它仍然说明了正在发生的事情。
我试过的
通读 pygame 文档,尝试识别所有可能相关的 body 和形状属性,我尝试以各种组合调整以下所有内容:
- body.mass
- shape.friction
- shape.elasticity
- 'ground friction'(在top-down场景中模拟地面摩擦力的枢轴约束max_force)
- 发射下部石头的'power'(传递给apply_impulse的参数之一)
- 使用 apply_force 代替 apply_impulse
调整 any/all 使石头的行为发生明显的预期变化,但 none 改变了碰撞时一块石头推动另一块石头的根本问题。
我已阅读有关使用 pymunk.CollisionHandler() 的信息,但尚未尝试使用它。从文档中,我觉得这主要是为了给碰撞添加额外的效果,而不是首先修改碰撞时发生的基本物理现象。但我可能有误解,欢迎任何建议。
我看过几个 pymunk 演示。最值得注意的是,名为 newtons_cradle.py 的演示展示了我想要的行为。这是对其中一个有五个球悬挂在一排的小玩意的模拟;当用户向后拉一端的一个球时,它会击中该行的其余球,能量会转移到另一侧的球上。
newtons_crade.py 与我的代码只有两个主要区别:
- 是'side-view'(所以重力大于0)
- 而不是使用 apply_impulse 或 apply_force,仅使用重力将球推向其他球(受约束约束)。
遗憾的是,在我的 top-down 设置中无法使用重力。
所以问题可能出在我对 apply_impulse/apply_force 的使用上,但我看不到任何修改这些使用方式的方法(我已经尝试过各种功率和质量的组合,以及调整约束的设置).
即使给我指出正确的方向——即关于我可能阅读的其他内容、我可能尝试修改的其他内容的一些建议——我将不胜感激。我不是 pymunk/chipmunk 中第一个尝试此操作的人,但我找不到示例。至少在 pymunk 方面不是;如果C/Chipmunk中有很好的例子我可以学习,那也很有用。
谢谢大家的宝贵时间。
最小示例代码
没有必要研究代码来理解问题,但我已将其张贴在这里以防万一。虽然被精简以仅显示代码的核心,但它是一个完整的脚本并且可以是 运行。它在 Python 3.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import pygame
from pygame.locals import *
import pymunk
import pymunk.pygame_util
def add_and_tether_stone(space,sx,sy):
"""Creates a stone and its corresponding shape, and tethers it with constraints that simulate ground friction and govern spin."""
#body
mass = stone_mass
radius = stone_radius
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment)
body.position = sx,sy
#shape
shape = pymunk.Circle(body, radius)
shape.friction = stone_friction
shape.elisticity = stone_elisticity
space.add(body, shape)
#constraints
fpiv = pymunk.constraint.PivotJoint(space.static_body,body,(0.0,0.0),(0.0,0.0))
fpiv.max_force = ground_friction
fpiv.max_bias = 0.0
fmot = pymunk.constraint.SimpleMotor(space.static_body,body,0)
fmot.max_force = 5000000 #arbitry 'very high' value clamps down on the high rotation imparted by apply_impulse or apply_force
space.add(fpiv)
space.add(fmot)
return body,shape,fpiv,fmot
def launch_stone(body,power):
"""Launches a stone in the manner of a player taking a shot."""
body.apply_impulse_at_world_point((0,power),(0,0)) #force(x,y),offset(x,y)
def main():
global ground_friction,stone_mass,stone_radius,stone_friction,stone_elisticity
running = True
#PyGame setup
pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
sheet = pygame.Surface((500,500))
sheetcolor = (0,0,0)
sheet.fill(sheetcolor)
sheet = sheet.convert()
sheetblit = (0,0)
screen.blit(sheet,sheetblit)
#PyMunk setup
space = pymunk.Space() #space.damping defaults to 1.0, and space.gravity defaults to (0.0, 0.0).
draw_options = pymunk.pygame_util.DrawOptions(sheet) #used only for the pygame_util debug draw mode
#Constants to Tweak
stone_mass = 1.4
stone_radius = 20
power = 340 #in a full implementation, this would vary with player input
ground_friction = 4.5
stone_friction = 2.0
stone_elisticity = 1.0
#Setup for the minimal example: add two stones and launch one at the other.
stone_a = add_and_tether_stone(space,40,260)
stone_b = add_and_tether_stone(space,40,21)
launch_stone(stone_b[0],power)
while running:
for event in pygame.event.get(): #listen for controls (all the controls except 'esc' have been removed for the minimal example)
if event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
#Draw, update physics, and advance
sheet.fill(sheetcolor)
space.debug_draw(draw_options) #from pymunk.pygame-util (handy!)
screen.blit(sheet,sheetblit)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
再次感谢大家的宝贵时间。
也许我在你的代码中遗漏了一些东西(我这里只有命令行 python 所以我不能 运行 你的脚本),但我无法重现你的问题。
这是我试过的一个简短代码,它似乎可以按您的要求工作:
import pymunk
s = pymunk.Space()
b1 = pymunk.Body(1,10)
b1.position = 0,0
b2 = pymunk.Body(1,10)
b2.position = 0,10
c1 = pymunk.Circle(b1, 1)
c1.elasticity = 1.0
c2 = pymunk.Circle(b2, 1)
c2.elasticity = 1.0
j1 = pymunk.constraint.PivotJoint(s.static_body, b1, (0,0),(0,0))
j1.max_force = 4.5
j1.max_bias = 0
j2 = pymunk.constraint.PivotJoint(s.static_body, b2, (0,0),(0,0))
j2.max_force = 4.5
j2.max_bias = 0
j3 = pymunk.constraint.SimpleMotor(s.static_body,b1,0)
j3.max_force = 5000000
j4 = pymunk.constraint.SimpleMotor(s.static_body,b2,0)
j4.max_force = 5000000
s.add(b1,b2,c1,c2,j1,j2,j3,j4)
b1.apply_impulse_at_world_point((0,30),(0,0))
for x in range(25):
s.step(0.02)
print(b1.position, b2.position)
这会在我的屏幕上打印出来(所以 b1 停止了,所有的运动都转移到了 b2):
Vec2d(0.0, 0.6) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.1982) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.7946) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.3891999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.9819999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 3.573) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.1622) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.7496) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.3352) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.9190000000000005) Vec2d(0.0, 10.0)
Vec2d(0.0, 6.501) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.081200000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.659600000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.2362) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 10.584682725252637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.159477451815137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.732472178377638)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.303666904940137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.873061631502637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 13.440656358065137)
这个问题是关于 pymunk 的,但我知道那里有更多的 Chimpunk 用户;所以如果你的答案涉及 C/Chipmunk 代码,没关系。虽然我不会写 C 代码,但如果我阅读别人的代码,我通常可以弄清楚是怎么回事。
设置
我正在模拟 top-down 滑动 object 游戏(想想冰壶或沙狐球)。 我已经对代码的相关部分做了一个最小的例子(在问题的末尾),但它的核心是:
- 创建了两个相同的物理 object(按照卷曲术语,我称它们为 'stones')
- 它们的位置具有相同的 x 值但不同的 y 值(一个在另一个上方的直线上)。
- 使用 apply_impulse(目前,虽然尝试了其他方法),下面的石头 'launched' 直接在另一块石头上
我希望达到的目标
当石头碰撞时,下面的石头应该突然停下来(或者可能会反弹一点——我还没有完全了解这个细节),而它的全部或大部分能量都转移到了上面的石头,它将开始向上移动。
我得到的是什么
当石头碰撞时,下面的石头并没有停止,而是开始将上面的石头推上屏幕。 好像下层比上层质量大,但它们是用相同的功能创建的,所以它们应该是相同的。
我已经上传了一个 .gif 到 imgur 来说明这一点,如果它有帮助的话: http://imgur.com/a/FF6Xq
它比实际 运行 运行脚本时的帧率低,但它仍然说明了正在发生的事情。
我试过的
通读 pygame 文档,尝试识别所有可能相关的 body 和形状属性,我尝试以各种组合调整以下所有内容:
- body.mass
- shape.friction
- shape.elasticity
- 'ground friction'(在top-down场景中模拟地面摩擦力的枢轴约束max_force)
- 发射下部石头的'power'(传递给apply_impulse的参数之一)
- 使用 apply_force 代替 apply_impulse
调整 any/all 使石头的行为发生明显的预期变化,但 none 改变了碰撞时一块石头推动另一块石头的根本问题。
我已阅读有关使用 pymunk.CollisionHandler() 的信息,但尚未尝试使用它。从文档中,我觉得这主要是为了给碰撞添加额外的效果,而不是首先修改碰撞时发生的基本物理现象。但我可能有误解,欢迎任何建议。
我看过几个 pymunk 演示。最值得注意的是,名为 newtons_cradle.py 的演示展示了我想要的行为。这是对其中一个有五个球悬挂在一排的小玩意的模拟;当用户向后拉一端的一个球时,它会击中该行的其余球,能量会转移到另一侧的球上。 newtons_crade.py 与我的代码只有两个主要区别:
- 是'side-view'(所以重力大于0)
- 而不是使用 apply_impulse 或 apply_force,仅使用重力将球推向其他球(受约束约束)。
遗憾的是,在我的 top-down 设置中无法使用重力。 所以问题可能出在我对 apply_impulse/apply_force 的使用上,但我看不到任何修改这些使用方式的方法(我已经尝试过各种功率和质量的组合,以及调整约束的设置).
即使给我指出正确的方向——即关于我可能阅读的其他内容、我可能尝试修改的其他内容的一些建议——我将不胜感激。我不是 pymunk/chipmunk 中第一个尝试此操作的人,但我找不到示例。至少在 pymunk 方面不是;如果C/Chipmunk中有很好的例子我可以学习,那也很有用。
谢谢大家的宝贵时间。
最小示例代码
没有必要研究代码来理解问题,但我已将其张贴在这里以防万一。虽然被精简以仅显示代码的核心,但它是一个完整的脚本并且可以是 运行。它在 Python 3.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import pygame
from pygame.locals import *
import pymunk
import pymunk.pygame_util
def add_and_tether_stone(space,sx,sy):
"""Creates a stone and its corresponding shape, and tethers it with constraints that simulate ground friction and govern spin."""
#body
mass = stone_mass
radius = stone_radius
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment)
body.position = sx,sy
#shape
shape = pymunk.Circle(body, radius)
shape.friction = stone_friction
shape.elisticity = stone_elisticity
space.add(body, shape)
#constraints
fpiv = pymunk.constraint.PivotJoint(space.static_body,body,(0.0,0.0),(0.0,0.0))
fpiv.max_force = ground_friction
fpiv.max_bias = 0.0
fmot = pymunk.constraint.SimpleMotor(space.static_body,body,0)
fmot.max_force = 5000000 #arbitry 'very high' value clamps down on the high rotation imparted by apply_impulse or apply_force
space.add(fpiv)
space.add(fmot)
return body,shape,fpiv,fmot
def launch_stone(body,power):
"""Launches a stone in the manner of a player taking a shot."""
body.apply_impulse_at_world_point((0,power),(0,0)) #force(x,y),offset(x,y)
def main():
global ground_friction,stone_mass,stone_radius,stone_friction,stone_elisticity
running = True
#PyGame setup
pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
sheet = pygame.Surface((500,500))
sheetcolor = (0,0,0)
sheet.fill(sheetcolor)
sheet = sheet.convert()
sheetblit = (0,0)
screen.blit(sheet,sheetblit)
#PyMunk setup
space = pymunk.Space() #space.damping defaults to 1.0, and space.gravity defaults to (0.0, 0.0).
draw_options = pymunk.pygame_util.DrawOptions(sheet) #used only for the pygame_util debug draw mode
#Constants to Tweak
stone_mass = 1.4
stone_radius = 20
power = 340 #in a full implementation, this would vary with player input
ground_friction = 4.5
stone_friction = 2.0
stone_elisticity = 1.0
#Setup for the minimal example: add two stones and launch one at the other.
stone_a = add_and_tether_stone(space,40,260)
stone_b = add_and_tether_stone(space,40,21)
launch_stone(stone_b[0],power)
while running:
for event in pygame.event.get(): #listen for controls (all the controls except 'esc' have been removed for the minimal example)
if event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
#Draw, update physics, and advance
sheet.fill(sheetcolor)
space.debug_draw(draw_options) #from pymunk.pygame-util (handy!)
screen.blit(sheet,sheetblit)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
再次感谢大家的宝贵时间。
也许我在你的代码中遗漏了一些东西(我这里只有命令行 python 所以我不能 运行 你的脚本),但我无法重现你的问题。
这是我试过的一个简短代码,它似乎可以按您的要求工作:
import pymunk
s = pymunk.Space()
b1 = pymunk.Body(1,10)
b1.position = 0,0
b2 = pymunk.Body(1,10)
b2.position = 0,10
c1 = pymunk.Circle(b1, 1)
c1.elasticity = 1.0
c2 = pymunk.Circle(b2, 1)
c2.elasticity = 1.0
j1 = pymunk.constraint.PivotJoint(s.static_body, b1, (0,0),(0,0))
j1.max_force = 4.5
j1.max_bias = 0
j2 = pymunk.constraint.PivotJoint(s.static_body, b2, (0,0),(0,0))
j2.max_force = 4.5
j2.max_bias = 0
j3 = pymunk.constraint.SimpleMotor(s.static_body,b1,0)
j3.max_force = 5000000
j4 = pymunk.constraint.SimpleMotor(s.static_body,b2,0)
j4.max_force = 5000000
s.add(b1,b2,c1,c2,j1,j2,j3,j4)
b1.apply_impulse_at_world_point((0,30),(0,0))
for x in range(25):
s.step(0.02)
print(b1.position, b2.position)
这会在我的屏幕上打印出来(所以 b1 停止了,所有的运动都转移到了 b2):
Vec2d(0.0, 0.6) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.1982) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.7946) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.3891999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.9819999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 3.573) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.1622) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.7496) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.3352) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.9190000000000005) Vec2d(0.0, 10.0)
Vec2d(0.0, 6.501) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.081200000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.659600000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.2362) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 10.584682725252637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.159477451815137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.732472178377638)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.303666904940137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.873061631502637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 13.440656358065137)