Python自制2D游戏引擎帧率为2的幂次,如何有效实现帧率限制
Python游戏引擎帧率限制异常问题
问题背景
虽然Python并非开发游戏引擎的最优语言,但开发者出于兴趣尝试用Python编写2D游戏引擎,在实现delta time与帧率限制功能时遇到问题:代码运行帧率天然为2的幂次,通常接近32或64。
问题复现代码
以下是精简后的复现代码,除调试打印外与游戏引擎中对应逻辑完全一致:
import time previousTime = time.time() time.sleep(0.00001) dT = 0 # 用for循环替代正常业务中的while循环 for x in range(3): # 打印帧序号 print(x) # 获取当前帧时间 now = time.time() # 计算delta time dT = now - previousTime # 调试打印各参数 print('now ' + str(now)) print('p time ' + str(previousTime)) print('dT ' + str(dT)) # 更新上一帧时间 previousTime = now # 计算当前帧耗时与60fps目标帧耗时(0.016s)的差值 frameTimingDif = 0.016 - dT # 调试打印更多参数 print('time dif ' + str(frameTimingDif)) print('FPS ' + str(1/dT)) # 尝试sleep剩余时间以稳定到60fps,差值为负时捕获异常 try: time.sleep(frameTimingDif) except: print('woops')
初始运行结果
0 now 1634974666.2838764 p time 1634974666.2682076 dT 0.015668869018554688 time dif 0.00033113098144531283 FPS 63.820815581253804 1 now 1634974666.2995417 p time 1634974666.2838764 dT 0.015665292739868164 time dif 0.00033470726013183627 FPS 63.83538543489841 2 now 1634974666.3151739 p time 1634974666.2995417 dT 0.015632152557373047 time dif 0.00036784744262695346 FPS 63.97071652990879 [Finished in 156ms]
补充现象
如果在循环开头添加time.sleep(0.000001),帧率会降到32左右,运行结果如下:
0 now 1634976028.433253 p time 1634976028.4020138 dT 0.03123927116394043 time dif -0.01523927116394043 FPS 32.01099010127684 woops 1 now 1634976028.4489527 p time 1634976028.433253 dT 0.01569962501525879 time dif 0.00030037498474121127 FPS 63.6957888502483 2 now 1634976028.4801967 p time 1634976028.4489527 dT 0.03124403953552246 time dif -0.01524403953552246 FPS 32.006104680000306 woops [Finished in 156ms]
解答
帧率为2的幂次的原因
这个现象的根源是操作系统的时钟精度限制,和Python本身的执行效率无关:
- 你观察到的0.0156s、0.0312s刚好是1/64秒和1/32秒,对应Windows系统默认的15.625ms时钟中断间隔。
time.sleep()的实际等待时长只能是这个间隔的整数倍,所以最终算出来的帧率自然就是64、32这类2的幂次。 - 当你添加极短的sleep调用时,代码执行刚好跨了一个时钟中断周期,等待时长就跳到了2个时钟间隔(31.25ms),对应帧率直接降到32。
更稳定的帧率限制实现方案
推荐用短睡眠+忙等待的折中方案,兼顾精度和CPU占用:
先计算好目标帧率对应的单帧耗时,比如60fps对应约0.0166667s。每帧逻辑处理完后,先判断剩余等待时间,如果剩余时间大于1ms,就用sleep睡掉大部分时间,剩下的不到1ms的时间用循环轮询时间戳硬等,既不会让CPU占满,也能保证帧率控制的精度。
示例实现代码
import time TARGET_FPS = 60 FRAME_DURATION = 1 / TARGET_FPS previous_time = time.time() while True: # 此处写游戏逻辑、渲染等业务代码 # ... # 帧率限制逻辑 current_time = time.time() frame_cost = current_time - previous_time wait_time = FRAME_DURATION - frame_cost if wait_time > 0: # 先睡掉大部分等待时间,留1ms用忙等补精度 if wait_time > 0.001: time.sleep(wait_time - 0.001) # 剩余时间硬等 while time.time() - current_time < FRAME_DURATION: pass previous_time = time.time()
如果是Windows平台,也可以调用win32api临时把系统时钟精度调整为1ms,进一步提升sleep的精度,注意程序退出前要把时钟精度改回默认值,避免影响系统其他程序运行。
内容的提问来源于stack exchange,提问作者Marshall Ware
相关产品推荐
相关产品推荐

