为什么游戏会出现“卡顿”:`clock.tick()` v.s. `clock.get_fps()`
running = True
while running:# 事件处理# 帧处理(如,更新、绘制和渲染画面).....# 控制目标帧率为60 FPSclock.tick(60)
程序片断中,clock.tick(60)
设定的60 FPS,即设定帧停顿时间为(1/60秒),那么,问题来了:当“帧处理”时间大于(1/60秒)呢?
一、clock.tick(target_fps)
当上述问题出现时,当然要等“帧处理”完,此时,帧刷新的间隔就大于(1/60秒),即此时帧率小于60 FPS。
clock.tick(target_fps)
设定的是目标帧率( clock.tick(60)
设定的目标帧率为60 FPS),它是**“最大目标帧率”,即程序尝试达到的理想上限,但实际帧率可能低于目标帧率**,而不会高于目标帧率。这是由 clock.tick()
的工作机制和游戏实际运行负载决定的。
“限制上限”,而非“保证达到”
clock.tick(60)
的本质是:让主循环每秒最多执行60次(即每帧至少间隔约16.67毫秒,1/60秒)。它的工作流程是:
- 记录上一帧的结束时间;
- 计算当前帧的处理时间(从帧开始到调用
tick()
前的耗时); - 如果处理时间小于16.67毫秒:自动等待剩余时间(例如处理用了10毫秒,就等待6.67毫秒),确保每帧总耗时≥16.67毫秒,从而限制帧率不超过60 FPS;
- 如果处理时间大于16.67毫秒:不等待(因为已经超时),直接进入下一帧,此时实际帧率会低于60 FPS(例如处理用了33毫秒,实际帧率约30 FPS)。
实际帧率与目标帧率的关系
-
实际帧率 ≤ 目标帧率:
- 当游戏逻辑简单、绘制压力小时(如静态画面、少量精灵),每帧处理时间<16.67毫秒,
tick()
会通过等待补全时间,实际帧率等于目标帧率(60 FPS); - 当游戏逻辑复杂、绘制压力大时(如大量精灵、复杂特效),每帧处理时间>16.67毫秒,
tick()
无法“压缩时间”,实际帧率低于目标帧率(例如30 FPS)。
- 当游戏逻辑简单、绘制压力小时(如静态画面、少量精灵),每帧处理时间<16.67毫秒,
-
实际帧率不会大于目标帧率:
因为tick()
会强制等待剩余时间(即使处理时间很短),确保每秒最多60帧,避免帧率过高导致的资源浪费(如显卡过度渲染)。
举例说明
假设目标帧率60 FPS(每帧理想耗时16.67毫秒):
- 简单场景:每帧处理(逻辑+绘制)仅用10毫秒 →
tick()
等待6.67毫秒 → 总耗时16.67毫秒 → 实际帧率=60 FPS(等于目标); - 复杂场景:每帧处理用了20毫秒(超过16.67毫秒) →
tick()
不等待 → 总耗时20毫秒 → 实际帧率=50 FPS(低于目标); - 极端复杂场景:每帧处理用了50毫秒 → 实际帧率=20 FPS(远低于目标)。
为什么游戏会出现“卡顿”
clock.tick(target_fps)
是**“上限锁”:保证帧率不会超过目标值,但无法保证达到目标值。实际帧率最终由每帧的处理成本**(逻辑复杂度、精灵数量、绘制压力等)决定:
- 处理成本低 → 实际帧率=目标帧率;
- 处理成本高 → 实际帧率<目标帧率(成本越高,帧率越低)。
这也是为什么游戏会出现“卡顿”——当实际帧率远低于目标帧率时,画面更新变慢,不能满足视觉对动画速度的要求,你就感觉到“卡顿”。
此时需要优化代码(如减少精灵、简化绘制)来降低每帧成本,让实际帧率接近目标值。
二、clock.get_fps()
既然,感觉到“卡顿”:“实际帧率<目标帧率”,那么,如何知道“卡顿”时的“实际帧率”是多少呢?
clock.get_fps()
是 Pygame 中 pygame.time.Clock
类的一个方法,用于获取游戏当前的平均帧率(FPS,Frames Per Second),即每秒实际运行的帧数。它是调试游戏性能、监控流畅度的重要工具。
基本用法与原理
- 前提:需先创建
Clock
对象(clock = pg.time.Clock()
),并在主循环中通过clock.tick(target_fps)
控制目标帧率(如clock.tick(60)
表示目标60 FPS)。 - 作用:
get_fps()
会计算并返回最近一段时间内的平均帧率(非瞬时值,而是平滑后的结果),单位为“帧/秒”。 - 原理:基于
clock.tick()
的调用记录,通过计算相邻两帧的时间间隔,自动统计最近几帧的平均耗时,进而反推出帧率(帧率 = 1/平均每帧耗时)。
示例:显示当前帧率
import pygame as pg
pg.init()screen = pg.display.set_mode((800, 600))
pg.display.set_caption("帧率监控示例")
clock = pg.time.Clock() # 创建Clock对象# 加载字体(用于显示帧率)
try:font = pg.font.SysFont("SimHei", 24)
except:font = pg.font.SysFont(None, 24)running = True
while running:# 事件处理for event in pg.event.get():if event.type == pg.QUIT:running = False# 控制目标帧率为60 FPS(实际帧率可能低于此值)clock.tick(60) # 必须调用,否则get_fps()无法计算帧率# 获取当前平均帧率(浮点数)current_fps = clock.get_fps()# 绘制screen.fill((0, 0, 0)) # 黑色背景# 显示帧率文本(保留1位小数)fps_text = font.render(f"FPS: {current_fps:.1f}", True, (0, 255, 0))screen.blit(fps_text, (20, 20)) # 左上角显示pg.display.flip()pg.quit()
特点
- 非瞬时值:返回的是“最近几帧”的平均值(而非当前这一秒的精确值),避免帧率波动导致的数值剧烈变化(例如,某一帧卡顿会被后续帧平滑)。
- 依赖
tick()
:必须在主循环中调用clock.tick(target_fps)
,否则get_fps()
会返回0
(因为没有帧间隔数据)。 - 性能参考:返回值反映游戏当前的实际流畅度——若远低于目标帧率(如目标60 FPS,实际仅30 FPS),说明存在性能瓶颈(如精灵过多、绘制逻辑复杂)。
用途
- 性能调试:通过观察帧率变化,定位性能问题(如打开某特效后帧率骤降,说明该特效消耗过多资源)。
- 动态优化:根据实际帧率调整游戏逻辑(如帧率过低时,临时降低精灵数量或简化绘制效果)。
- 玩家体验监控:在开发版中显示帧率,确保游戏在目标设备上能稳定运行(通常需保持30 FPS以上,60 FPS为理想状态)。
- 帧率数值会受硬件性能、游戏逻辑复杂度影响(例如,同一段代码在高性能电脑上可能60 FPS,在低配设备上可能30 FPS)。
总之,clock.get_fps()
是游戏性能调优的“晴雨表”,通过它可以直观了解游戏的运行流畅度,是开发中不可或缺的工具。