You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Pymunk中的盒子从角落以设定强度持续一段时间推升

实现Pymunk盒子的持续角落推升效果

问题需求

需要让物理场景中的盒子从指定角落,以设定强度(如50%最大力度)缓慢向上推升,且推升动作持续设定时长(2-3秒)。此前尝试用单次冲量的方式无法实现需求,因为冲量是瞬间作用,无法达成持续缓慢推升的效果。

解决方案思路

要实现持续推升,不能依赖单次冲量,而是需要在设定时长内,每帧持续施加力或分多次施加小冲量,同时精准控制力的作用位置为盒子的指定角落,结合力度百分比和时长参数实现需求:

  1. 记录推升的起始时间与持续时长,实时判断当前是否处于推升阶段
  2. 计算盒子角落的局部坐标,确保力作用在目标位置
  3. 按设定的力度百分比和方向,每帧稳定施加力(或小冲量)

修改后的完整代码

import pygame
import pymunk
import pymunk.pygame_util
import math

pygame.init()

WIDTH, HEIGHT = 1280, 720
window = pygame.display.set_mode((WIDTH, HEIGHT))

def draw(space, window, draw_options):
    window.fill('white')
    space.debug_draw(draw_options)
    pygame.display.update()

def create_invisible_border(space, width, height, collision_type, friction=0.4, elasticity=0.9, thickness=100):
    body = pymunk.Body(body_type=pymunk.Body.STATIC)
    body.position = (width/2, height/2)
    space.add(body)
    
    top_border = pymunk.Segment(body, (-width/2, height/2 + thickness), (width/2, height/2 + thickness), thickness)
    bottom_border = pymunk.Segment(body, (-width/2, -height/2 - thickness), (width/2, -height/2 - thickness), thickness)
    left_border = pymunk.Segment(body, (-width/2 - thickness, height/2), (-width/2 - thickness, -height/2), thickness)
    right_border = pymunk.Segment(body, (width/2 + thickness, height/2), (width/2 + thickness, -height/2), thickness)

    top_border.collision_type = collision_type
    bottom_border.collision_type = collision_type
    left_border.collision_type = collision_type
    right_border.collision_type = collision_type

    top_border.friction = friction
    bottom_border.friction = friction
    left_border.friction = friction
    right_border.friction = friction

    top_border.elasticity = elasticity
    bottom_border.elasticity = elasticity
    left_border.elasticity = elasticity
    right_border.elasticity = elasticity
    
    space.add(top_border, bottom_border, left_border, right_border)

def create_static_circle(space, x_pos, y_pos, collision_type, radius):
    body = pymunk.Body(body_type=pymunk.Body.STATIC)
    body.position = x_pos, y_pos
    space.add(body)
    shape = pymunk.Circle(body, radius)
    shape.collision_type = collision_type
    space.add(shape)

    return body

def create_object(space, mass, size, x, y, collision_type, friction = 0.4, elasticity = 0.9):
    moment = pymunk.moment_for_box(mass, size)
    body = pymunk.Body(mass, moment)
    body.position = x, y
    shape = pymunk.Poly.create_box(body, size)
    shape.elasticity = elasticity
    shape.friction = friction
    shape.collision_type = collision_type
    space.add(body, shape)

    return body

def apply_lift_force(body, box_size, angle_deg, max_strength, percentage):
    # 计算盒子左下角的局部坐标(可修改为其他角落:右上角为(box_size[0]/2, box_size[1]/2))
    corner_local_pos = (-box_size[0]/2, -box_size[1]/2)
    # 计算实际施加的力度与分量
    strength = max_strength * (percentage / 100)
    angle_rad = math.radians(angle_deg)
    force_x = strength * math.cos(angle_rad)
    # Pymunk中y轴向上为负方向,因此取负确保力向上
    force_y = -strength * math.sin(angle_rad)
    # 在指定角落施加力
    body.apply_force_at_local_point((force_x, force_y), corner_local_pos)

def run(window, width, height):
    run = True
    clock = pygame.time.Clock()
    fps = 60
    dt = 1/fps
    space = pymunk.Space()
    space.gravity = (0, 981)

    create_invisible_border(space, width, height, 2)
    create_static_circle(space, 1050, 160, 3, 25)
    box_size = (100, 100)
    box_body = create_object(space, 1, box_size, 300, 300, 1, elasticity = 0.2)

    # 推升参数配置
    lift_active = False
    lift_start_time = 0
    lift_duration = 3000  # 持续3000毫秒(3秒)
    lift_max_strength = 2000  # 最大力度
    lift_percentage = 50  # 50%力度
    lift_angle = 135  # 推升角度(135度对应左上方向)

    draw_options = pymunk.pygame_util.DrawOptions(window)
    while run:
        current_time = pygame.time.get_ticks()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                break
            # 按下任意键触发推升(可自定义触发条件)
            if event.type == pygame.KEYDOWN:
                lift_active = True
                lift_start_time = current_time

        # 检查是否处于推升时间段内
        if lift_active and (current_time - lift_start_time) < lift_duration:
            apply_lift_force(box_body, box_size, lift_angle, lift_max_strength, lift_percentage)
        elif (current_time - lift_start_time) >= lift_duration:
            lift_active = False

        draw(space, window, draw_options)
        space.step(dt)
        clock.tick(fps)
    pygame.quit()

if __name__ == '__main__':
    run(window, WIDTH, HEIGHT)

关键代码说明

  • 推升参数自定义:可通过调整lift_duration(持续时长,毫秒)、lift_max_strength(最大力度)、lift_percentage(力度百分比)、lift_angle(推升角度)来适配不同需求
  • 角落位置控制:apply_lift_force函数中,corner_local_pos变量定义了力的作用角落,可根据需求修改为盒子的任意角落
  • 时长控制逻辑:通过pygame.time.get_ticks()获取当前时间,对比起始时间判断是否超出推升时长,超出后自动停止施力
  • 方向适配:因Pymunk坐标系中y轴向上为负,所以力的y分量取负,确保推升方向向上

内容的提问来源于stack exchange,提问作者john

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 20:40:30