Python Pygame+Pymunk传送带仿真冲量仅作用于新物体问题
Pymunk传送带仿真冲量仅作用于最新物体修复方案
核心问题原因
- 物体列表初始化位置错误:
objects = []写在鼠标点击事件分支内,每次点击添加新物体时都会先清空列表,再存入新物体,导致列表始终只保存最后一次创建的物体,冲量遍历逻辑自然只能作用到最新物体。 - 碰撞回调范围错误:使用全局默认碰撞处理器,任意两个物体(包括物体之间、物体和非传送带对象)碰撞都会触发回调,且没有判断物体是否真的和传送带接触,会给未落到传送带上的悬空物体错误施加冲量。
- 冲量逻辑不符合物理规则:固定施加水平方向冲量,没有匹配倾斜段传送带的走向,也没有根据物体和传送带的相对速度调整冲量大小,无法模拟速度同步后的无摩擦状态。
修复步骤
- 将
objects列表的初始化移到主循环启动前、鼠标事件逻辑外,每次点击创建新物体时直接向列表追加元素,不要每次点击都重置空列表。 - 为传送带和传送物体定义专属碰撞类型,不要使用全局默认碰撞处理器,仅针对传送带与物体的碰撞对设置回调逻辑。
- 在碰撞回调中直接通过碰撞仲裁器
arbiter获取当前发生接触的传送带段和物体,根据传送带段的预设运动方向、传送带与物体的相对速度计算冲量大小,在接触点施加冲量,模拟真实摩擦力效果。
修复后可运行代码
import pygame import pymunk import pymunk.pygame_util import math import sys pygame.init() WIDTH, HEIGHT = 1920,800 mu = 2 # 定义碰撞类型 COLLISION_TYPE_BELT = 1 COLLISION_TYPE_OBJECT = 2 window = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Kinematics approximation ") def calculate_distance(p1, p2): return math.sqrt((p2[1] - p1[1])**2 + (p2[0] - p1[0])**2) def calculate_angle(p1, p2): return math.atan2(p2[1] - p1[1], p2[0] - p1[0]) def draw(space, window,draw_options): window.fill("white") space.debug_draw(draw_options) pygame.display.update() def create_belt(space): belts = [ # 格式:起点、终点、厚度、运动方向单位向量、线速度 [(0,500), (600,500), 5, (1,0), 120], [(600,500), (800,400), 6, (math.cos(-math.pi/4), math.sin(-math.pi/4)), 120], [(800,400), (WIDTH,400), 6, (1,0), 120] ] for pos_1, pos_2, width, belt_dir, belt_speed in belts: shape = pymunk.Segment(space.static_body,pos_1, pos_2, width) shape.body.position = 0,0 shape.friction = mu shape.collision_type = COLLISION_TYPE_BELT shape.belt_dir = belt_dir shape.belt_speed = belt_speed space.add(shape) def create_object(space, mass, pos): body = pymunk.Body(body_type = pymunk.Body.DYNAMIC) body.position = pos shape = pymunk.Poly.create_box(body, size = (20,10)) shape.mass = mass shape.color = (255,0,0, 100) shape.body.friction = mu shape.collision_type = COLLISION_TYPE_OBJECT space.add(body,shape) return shape def run(window, width, height): run = True clock = pygame.time.Clock() fps = 240 dt = 1/fps space = pymunk.Space() space.gravity = (0,981) create_belt(space) # 提前初始化物体列表 objects = [] def coll_pre(arbiter, space, data): belt_shape, obj_shape = arbiter.shapes belt_dir = belt_shape.belt_dir belt_speed = belt_shape.belt_speed # 计算物体沿传送带方向的当前速度 obj_vel_along = obj_shape.body.velocity.dot(belt_dir) # 计算速度差,换算为需要施加的冲量 vel_diff = belt_speed - obj_vel_along impulse_mag = vel_diff * obj_shape.mass * 0.1 # 限制冲量上限,模拟最大静摩擦 impulse_mag = max(-100, min(100, impulse_mag)) impulse = (belt_dir[0] * impulse_mag, belt_dir[1] * impulse_mag) # 在所有接触点施加冲量 for contact in arbiter.contact_point_set.points: obj_shape.body.apply_impulse_at_world_point(impulse, contact.point_b) return True # 仅绑定传送带和物体的碰撞回调 handler = space.add_collision_handler(COLLISION_TYPE_BELT, COLLISION_TYPE_OBJECT) handler.pre_solve = coll_pre draw_options = pymunk.pygame_util.DrawOptions(window) while run: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.MOUSEBUTTONDOWN: pressed_pos = pygame.mouse.get_pos() # 直接追加新物体,不重置列表 objects.append(create_object(space,10, pressed_pos)) draw(space,window, draw_options) space.step(dt) clock.tick(fps) pygame.quit() sys.exit() if __name__ == "__main__": run(window, WIDTH, HEIGHT)
修复后所有接触传送带的物体都会受到沿传送带方向的摩擦冲量,倾斜段运动方向正确,物体速度与传送带同步后冲量会自动降低到接近0,仿真效果符合真实传送带运动规律。
内容的提问来源于stack exchange,提问作者Ruben Vreugdenhil
相关产品推荐
相关产品推荐

