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

如何修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 22:15:27