如何修复Pygame中弹性碰撞函数的速度不更新问题?
碰撞检测生效但设置速度后物体无运动变化
我的collisionHandler函数能正确识别两个物体的碰撞,但调用set_velocity修改速度后,物体完全没有运动变化。我尝试过只调整单个物体速度,效果稍有改善,但调整另一个时还是无效;也试过对速度和位置取整,避免像素对齐干扰colliderect,但都没用。不确定是不是必须用sprites才能解决。
完整代码:
import numpy as np import pygame from sys import exit #Initializing Parameters pygame.init() displayInfo = pygame.display.Info() screenWidth = displayInfo.current_w screenHeight = displayInfo.current_h display_surface = pygame.display.set_mode((screenWidth, screenHeight-50), pygame.RESIZABLE) pygame.display.set_caption("Python Physics Engine") clock = pygame.time.Clock() fps_limit = 60 backgroundColor = (255,255,255) objectList = [] class PhysicsObject(): global objectList def convert(self, measurement): return int(measurement)*self.conversion_factor def __init__(self, x, y, width, height, mass, velocity, acceleration, max_velocity): self.conversion_factor = 50 #meaning 50 px = 1m self.x = round(x) self.y = round(y) self.width = self.convert(width) self.height = self.convert(height) self.mass = mass self.velocity = (self.convert(velocity[0]), self.convert(velocity[1])) self.acceleration = (self.convert(acceleration[0]), self.convert(acceleration[1])) self.max_velocity = (self.convert(max_velocity[0]), self.convert(max_velocity[1])) self.color = (255,0,0) self.objectRect = pygame.Rect(self.x, self.y, self.width, self.height) self.canCollide = True def update(self, dt): #updating velocity self.velocity = (int(self.velocity[0] + self.acceleration[0] * dt), int(self.velocity[1] + self.acceleration[1] * dt)) #ensuring velocity doesn't go past maximum self.velocity = (min(self.velocity[0], self.max_velocity[0]), min(self.velocity[1], self.max_velocity[1])) #updating position self.x += int(self.velocity[0] * dt) self.y += int(self.velocity[1] * dt) #checking that object won't go off screen screenWidth, screenHeight = display_surface.get_size() if self.x < 0: self.x = 0 self.velocity = (-self.velocity[0], self.velocity[1]) elif self.x + self.width > screenWidth: self.x = screenWidth - self.width self.velocity = (-self.velocity[0], self.velocity[1]) if self.y < 0: self.y = 0 self.velocity = (self.velocity[0], -self.velocity[1]) elif self.y + self.height > screenHeight: self.y = screenHeight - self.height self.velocity = (self.velocity[0], -self.velocity[1]) self.objectRect = pygame.Rect(self.x, self.y, self.width, self.height) collisionHandler() def draw(self): self.objectRect = pygame.Rect(self.x, self.y, self.width, self.height) pygame.draw.rect(display_surface, self.color, self.objectRect, 0, 1) def set_color(self, color): self.color = color def set_velocity(self, new_velocity): print("Changing velocity from ", self.velocity, " to ", new_velocity) self.velocity = new_velocity def change_collision_status(self): print("Changing collision status") self.canCollide = not self.canCollide def collisionHandler(): global objectList for object in objectList: for object2 in objectList: if object != object2 and object.objectRect.colliderect(object2.objectRect) and object.canCollide and object2.canCollide: calculateCollision(object, object2) def calculateCollision(object1, object2): object1.change_collision_status() object2.change_collision_status() m1 = object1.mass m2 = object2.mass v1x, v1y = object1.velocity v2x, v2y = object2.velocity v1xf = round(((m1-m2)/(m1+m2))*v1x + (2*m2/(m1+m2))*v2x) v2xf = round((2*m1/(m1+m2)) * v1x + ((m2-m1)/(m1+m2))*v2x) object1.set_velocity((v1xf, v1y)) object2.set_velocity((v2xf, v2y)) object1.change_collision_status() object2.change_collision_status() gravity = (0, 0) #input parameters in SI units, class will handle conversion testObject1 = PhysicsObject(100, 300, 1, 1, 1, (3,0), gravity, (1000, 1000)) testObject2 = PhysicsObject(screenWidth-100, 275, 2, 2, 100, (-3, 0), gravity, (1000,1000)) testObject2.set_color((0,255,0)) objectList = [testObject1, testObject2] running = True #main game loop while running: time_scale = 1e-3 dt = clock.tick(fps_limit) * time_scale for event in pygame.event.get(): if event.type == pygame.QUIT: running = False exit() elif event.type == pygame.VIDEORESIZE: display_surface = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE) testObject1.update(dt) testObject2.update(dt) display_surface.fill(backgroundColor) testObject1.draw() testObject2.draw() pygame.time.delay(int(1000/fps_limit)) pygame.display.flip() pygame.quit()
问题根源与修复方案
1. 碰撞处理被重复调用,速度被覆盖
每个物体的update方法里都调用了collisionHandler(),一帧内碰撞处理会执行两次:第一次处理碰撞修改了速度,但第二个物体的update会基于原速度重新计算加速度,直接覆盖碰撞后的速度。
修复:
把collisionHandler()从PhysicsObject.update中移除,放到主循环所有物体更新之后:
# 主循环内修改 testObject1.update(dt) testObject2.update(dt) collisionHandler() # 所有物体更新后统一处理碰撞 display_surface.fill(backgroundColor) testObject1.draw() testObject2.draw()
2. 碰撞配对重复触发,逻辑混乱
原碰撞循环会遍历所有物体对,导致同一碰撞被两次处理(比如object1和object2,然后object2和object1),速度被反复修改。
修复:
修改碰撞处理逻辑,只处理一次物体对:
def collisionHandler(): global objectList # 遍历到倒数第二个物体,避免重复配对 for i in range(len(objectList)): object1 = objectList[i] for j in range(i+1, len(objectList)): object2 = objectList[j] if object1.objectRect.colliderect(object2.objectRect) and object1.canCollide and object2.canCollide: calculateCollision(object1, object2)
3. 强制取整丢失速度精度
update中对速度和位置用int()强制取整,在dt很小的情况下(约0.016),微小的速度变化会被直接忽略,导致物体无法运动。
修复:
保留速度和位置的浮点数值,仅在绘制和创建pygame.Rect时转整数:
def update(self, dt): # 去掉int(),保留浮点计算 self.velocity = ( self.velocity[0] + self.acceleration[0] * dt, self.velocity[1] + self.acceleration[1] * dt ) self.velocity = ( min(self.velocity[0], self.max_velocity[0]), min(self.velocity[1], self.max_velocity[1]) ) self.x += self.velocity[0] * dt self.y += self.velocity[1] * dt screenWidth, screenHeight = display_surface.get_size() if self.x < 0: self.x = 0.0 self.velocity = (-self.velocity[0], self.velocity[1]) elif self.x + self.width > screenWidth: self.x = screenWidth - self.width self.velocity = (-self.velocity[0], self.velocity[1]) if self.y < 0: self.y = 0.0 self.velocity = (self.velocity[0], -self.velocity[1]) elif self.y + self.height > screenHeight: self.y = screenHeight - self.height self.velocity = (self.velocity[0], -self.velocity[1]) # 创建Rect时转整数 self.objectRect = pygame.Rect(int(self.x), int(self.y), self.width, self.height)
4. 多余的帧率延迟导致运动卡顿
clock.tick(fps_limit)已经会控制帧率,再加上pygame.time.delay会让实际帧率远低于设定值,直接移除该延迟即可。
内容的提问来源于stack exchange,提问作者PoorThermals
相关产品推荐
相关产品推荐

