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

基于Pygame实现Verlet物理约束时物体反复弹跳问题求助

Verlet物理求解器约束弹跳异常排查与解决

我基于Pygame从零实现简单Verlet物理求解器,完成约束逻辑后,测试发现物体碰到约束区域底部会出现反复弹跳的异常。已尝试调整重力参数、物体尺寸、修改约束检测逻辑,甚至在约束修正后同步obj.posold = obj.poscurr.copy(),但仅能减小弹跳幅度,无法彻底解决问题。

我的Python实现代码

import os
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = 'hide'
import pygame
from pygame.locals import *
from pygame import Vector2 as Vec2
import sys
pygame.init()

wnSize = (700, 700)
fps = 10

gravity = Vec2(0, 100/fps)
objs = [[i/2 for i in wnSize]]
objradius = 50

areaCenter = Vec2(wnSize[0]/2, wnSize[1]/2)
areaRadius = 350

class VarletObject:
    def __init__(self, pos):
        self.poscurr = Vec2(pos)
        self.posold = Vec2(pos)
        self.acc = Vec2(0, 0)
    def updatePos(self, dt):
        vel = self.poscurr - self.posold
        self.posold = self.poscurr.copy()
        self.poscurr = self.poscurr + vel + self.acc * dt * dt
        self.acc = Vec2(0, 0)
    def accelerate(self, acceleration):
        self.acc += Vec2(acceleration)

class Solver:
    def __init__(self, gravity, objPoses):
        self.gravity = Vec2(gravity)
        self.objs = [VarletObject(i) for i in objPoses]

    def update(self, dt):
        self.applyGravity()
        self.applyConstraint()
        self.updatePositions(dt)

    def updatePositions(self, dt):
        for obj in self.objs:
            obj.updatePos(dt)

    def applyGravity(self):
        for obj in self.objs:
            obj.accelerate(self.gravity)

    def applyConstraint(self):
        for obj in self.objs:
            to_obj = obj.poscurr - areaCenter
            dist = to_obj.length()
            if dist > areaRadius - objradius:
                print(1)
                n = to_obj/dist
                obj.poscurr = areaCenter + n * (dist - objradius)

    def draw(self, wn):
        pygame.draw.circle(wn, (0, 0, 0), areaCenter, areaRadius)
        for obj in self.objs:
            pygame.draw.circle(wn, (255, 255, 255), obj.poscurr, objradius)
            pygame.draw.line(wn, (255, 0, 0), areaCenter, obj.poscurr)


wn = pygame.display.set_mode(wnSize)

solver = Solver(gravity, objs)

clock = pygame.time.Clock()
while True:
    wn.fill((255, 255, 255))
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    solver.update(1)
    solver.draw(wn)
    pygame.display.flip()
    clock.tick(fps)

关键代码对比

教程中的C#/C++约束代码

void applyConstraint() {
    const vec2 position{800.0f, 450.0f};
    const float radius = 400.0f;
    core::foreach<VerletObject>([&](VerletObject& obj) {
        const Vec2 to_obj = obj.position_current - position;
        const float dist = MathVec2::length(to_obj);
        // 50 is the default radius
        if (dist > radius - 50.0f) {
            const Vec2 n = to_obj / dist;
            obj.position_current = position + n * (radius - 50.0f);
        }
    });
}

我的Python约束代码

for obj in self.objs:
    to_obj = obj.poscurr - areaCenter
    dist = to_obj.length()
    if dist > areaRadius - objradius:
       print(1)
       n = to_obj/dist
       obj.poscurr = areaCenter + n * (dist - objradius)

异常原因分析

  1. Verlet积分步骤顺序错误:Solver.update方法执行顺序为applyGravity → applyConstraint → updatePositions,但Verlet积分的正确流程是先应用加速度、更新位置,最后应用约束。当前顺序下,约束修正的位置会被后续的updatePositions覆盖,导致物体每次更新后又超出约束范围,触发重复修正,表现为弹跳。
  2. 约束未处理速度(posold):即使调整顺序后,仅修正poscurr而不调整posold,物体的速度不会因碰撞而反弹,仍会持续向约束面移动,导致反复触发约束修正。

解决方案

1. 调整Update流程顺序

将Solver.update的执行顺序改为applyGravity → updatePositions → applyConstraint,确保约束修正的是最终位置:

def update(self, dt):
    self.applyGravity()
    self.updatePositions(dt)
    self.applyConstraint()

2. 约束修正时同步调整posold

在修正poscurr后,根据约束面的法线反射速度(通过调整posold实现),让物体碰撞后产生反弹效果,避免持续触发约束:

def applyConstraint(self):
    for obj in self.objs:
        to_obj = obj.poscurr - areaCenter
        dist = to_obj.length()
        allowed_dist = areaRadius - objradius
        if dist > allowed_dist:
            n = to_obj / dist
            # 保存修正前的当前位置,用于计算速度
            old_poscurr = obj.poscurr.copy()
            # 将物体拉回约束边界内
            obj.poscurr = areaCenter + n * allowed_dist
            # 计算原速度并反射,通过posold实现碰撞后的速度变化
            vel = old_poscurr - obj.posold
            vel_reflected = vel - 2 * vel.dot(n) * n
            obj.posold = obj.poscurr - vel_reflected

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:15:04