如何解决带摩擦物体运动与帧率(FPS)相关的问题?
帧率无关的运动控制问题
我写了一个带初始速度的方块运动代码,希望无论帧率(FPS)多少,方块的运动距离和耗时都保持一致,但实际测试结果不一致:
当FPS设为60时,调试输出:
Mid time: 1.8163 s
Time for vel=0: 2.5681 s
End position: (651.94, 262.0)
当FPS设为120时,调试输出:
Mid time: 1.3987 s
Time for vel=0: 5.0331 s
End position: (1224.91, 400.35)
我尝试把速度乘以dt,摩擦力乘以dt²,但不起作用,请问代码哪里错了?
原代码:
import pygame import sys from pygame.locals import * from time import time class Entity: def __init__(self, pos, vel, friction, rgb=(0, 255, 255), size=(50, 80)): self.pos = pos self.vel = vel self.friction = friction self.rgb = rgb self.size = size def update(self, dt): friction = self.friction * dt**2 for i in range(2): self.pos[i] += self.vel[i] * dt # Adding/subtracting friction to velocity so that it approaches 0 if self.vel[i] > 0: self.vel[i] -= friction if self.vel[i] < 0: self.vel[i] = 0 elif self.vel[i] < 0: self.vel[i] += friction if self.vel[i] > 0: self.vel[i] = 0 def render(self, surf): pygame.draw.rect(surf, self.rgb, (self.pos[0], self.pos[1], self.size[0], self.size[1])) pygame.init() clock = pygame.time.Clock() FPS = 120 screen_size = (1600, 900) screen = pygame.display.set_mode(screen_size) pygame.display.set_caption('Window') start_1 = time() printed_first_debug = False printed_second_debug = False # position, velocity, friction player = Entity([20, 100], [8, 4], 0.05) run = True while run: t1 = time() try: dt = 60*(t1-t0) except NameError: dt = 60/FPS t0 = time() for event in pygame.event.get(): if event.type == QUIT: run = False screen.fill((30, 30, 30)) player.update(dt) player.render(screen) if player.pos[0] >= 600 and not printed_first_debug: end_time = time() print(f'Mid time: {round(end_time - start_1, 4)} s') printed_first_debug = True elif player.vel == [0, 0] and not printed_second_debug: end_time = time() print(f'Time for vel=0: {round(end_time - start_1, 4)} s') print(f'End position: ({round(player.pos[0], 2)}, {round(player.pos[1], 2)})') printed_second_debug = True pygame.display.update() clock.tick(FPS) pygame.quit() sys.exit()
错误原因分析
- dt计算错误:原代码中
dt = 60*(t1-t0)是错误的,dt应该是两帧之间的实际时间间隔(单位:秒),不需要乘以60。乘以60会导致时间间隔被放大,不同帧率下的dt缩放比例混乱。 - 摩擦力的缩放错误:摩擦力属于加速度,加速度对速度的影响公式是
速度变化量 = 加速度 × 时间间隔,也就是应该乘以dt而不是dt²。原代码的dt²会让摩擦力随帧率变化的幅度完全失控。 - 速度归零判断不严谨:直接用
player.vel == [0,0]判断速度是否为0,浮点数运算中很难精确等于0,应该判断速度的绝对值小于极小值(比如1e-6)。 - 初始dt设置错误:初始dt设为
60/FPS会导致第一帧的时间间隔错误,应该设为1/FPS(理想情况下的单帧时间)。
修正后的代码
import pygame import sys from pygame.locals import * from time import time class Entity: def __init__(self, pos, vel, friction, rgb=(0, 255, 255), size=(50, 80)): self.pos = pos self.vel = vel self.friction = friction self.rgb = rgb self.size = size def update(self, dt): # 摩擦力是加速度,速度变化为 加速度 * dt friction = self.friction * dt for i in range(2): # 位置更新:速度 × 时间间隔 self.pos[i] += self.vel[i] * dt # 处理摩擦力对速度的影响 if self.vel[i] > 0: self.vel[i] -= friction # 防止速度变为负数,用极小值判断 if self.vel[i] < 1e-6: self.vel[i] = 0 elif self.vel[i] < 0: self.vel[i] += friction if self.vel[i] > -1e-6: self.vel[i] = 0 def render(self, surf): pygame.draw.rect(surf, self.rgb, (self.pos[0], self.pos[1], self.size[0], self.size[1])) pygame.init() clock = pygame.time.Clock() FPS = 120 screen_size = (1600, 900) screen = pygame.display.set_mode(screen_size) pygame.display.set_caption('Window') start_1 = time() printed_first_debug = False printed_second_debug = False # position, velocity, friction player = Entity([20, 100], [8, 4], 0.05) run = True t0 = time() # 初始化t0,避免第一次循环的NameError while run: t1 = time() # dt是实际时间间隔(秒) dt = t1 - t0 t0 = t1 for event in pygame.event.get(): if event.type == QUIT: run = False screen.fill((30, 30, 30)) player.update(dt) player.render(screen) if player.pos[0] >= 600 and not printed_first_debug: end_time = time() print(f'Mid time: {round(end_time - start_1, 4)} s') printed_first_debug = True # 用极小值判断速度是否接近0 elif abs(player.vel[0]) < 1e-6 and abs(player.vel[1]) < 1e-6 and not printed_second_debug: end_time = time() print(f'Time for vel=0: {round(end_time - start_1, 4)} s') print(f'End position: ({round(player.pos[0], 2)}, {round(player.pos[1], 2)})') printed_second_debug = True pygame.display.update() clock.tick(FPS) pygame.quit() sys.exit()
关键修改点说明
- dt计算:改为
dt = t1 - t0,直接获取两帧的时间差,单位是秒,确保运动计算和帧率无关。 - 摩擦力处理:将
friction = self.friction * dt**2改为friction = self.friction * dt,符合加速度对速度的影响公式。 - 速度归零判断:用
abs(vel[i]) < 1e-6替代直接等于0,避免浮点数精度问题。 - 初始化t0:在循环外提前初始化
t0,避免第一次循环的NameError,同时去掉错误的初始dt = 60/FPS。
这样修改后,无论FPS设为60还是120,调试输出的时间和位置都会保持一致。
内容的提问来源于stack exchange,提问作者User4497
相关产品推荐
相关产品推荐

