基于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)
异常原因分析
- Verlet积分步骤顺序错误:
Solver.update方法执行顺序为applyGravity → applyConstraint → updatePositions,但Verlet积分的正确流程是先应用加速度、更新位置,最后应用约束。当前顺序下,约束修正的位置会被后续的updatePositions覆盖,导致物体每次更新后又超出约束范围,触发重复修正,表现为弹跳。 - 约束未处理速度(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
相关产品推荐
相关产品推荐

