到目前为止,我们一直在使用 time.delay 来控制动画运行的快慢。不过这不是最好的办法,这是因为,使用 time.delay 时,你并不真正知道每个循环需要多长时间。循环中的代码要花一些时间来运行(这是一个未知时间),然后延迟也要花费一些时间(这是一个已知时间)。所以这个时间中有一部分是已知的,但有一部分是未知的。  
 
  
如果我们想知道循环多长时间运行一次,就需要知道每个循环的总时间,这应当是代码运行时间 + 延迟时间。要计算动画的时间,使用毫秒或千分之一秒会很方便。它的缩写是 ms,所以 25 毫秒就是 25 ms。  
在我们的例子中,假设代码时间是 15 ms。这说明,while 循环中的代码运行需要 15 ms,这不包括 time.delay。我们已经知道延迟时间,因为这里使用 time.delay(20) 把延迟设置为 20 ms。所以循环的总时间是 20 ms+15 ms=35 ms。由于 1 秒就是 1000 ms,如果每个循环需要 35 ms,可以得到 1000 ms / 35 ms=28.57。这说明每秒大约有 29 个循环。在计算机图形学中,每个动画步叫做一帧,游戏程序员讨论图形更新的快慢时都会提到帧速率(每秒帧数,fps)。在我们的例子中,帧速率大约是 29 fps。  
问题在于,我们并不能真正控制这个公式中的“代码时间”部分。如果增加或删除代码,这个时间就会改变。即使是相同的代码,如果动画精灵个数不同(例如,随着游戏对象的出现和消失,动画精灵个数会变化),绘制这些精灵所花费的时间也会变化。可能不是 15 ms,代码时间可能变成 10 ms 或 20 ms。如果有一种更便于预测的方法来控制帧速率就好了。好在,Pygame 的 time 模块为我们提供了这样的工具:一个名为 Clock 的类。  
用 pygame.time.Clock 控制帧速率  
并不是向每个循环增加一个延迟, pygame.time.Clock 会控制每个循环多长时间运行一次。这就像一个定时器在控制时间进程,指出“现在开始下一个循环!现在开始下一个循环!……”  
 
  
使用 Pygame 时钟之前,必须先创建 Clock 对象的一个实例。这与创建其他类的实例完全相同:  
clock = pygame.time.Clock
然后在主循环体中,只需要告诉时钟多久“滴答”一次——也就是说,循环应该多长时间运行一次:  
clock.tick(60)
传入 clock.tick 的数不是一个毫秒数。这是每秒内循环要运行的次数。所以这个循环应当每秒运行 60 次。在这里我只是说“应当运行”,因为循环只能按计算机能够保证的速度运行。每秒 60 个循环(或帧)时,每个循环需要 1000 / 60 = 16.66 ms(大约 17 ms)。如果循环中的代码运行时间超过 17 ms,在 clock 指出开始下一次循环时当前循环将无法完成。  
实际上,这说明对于图形运行的帧速率有一个限制。这个限制取决于图形的复杂程度、窗口大小以及运行这个程序的计算机的速度。对于一个特定的程序,计算机的运行速度可能是 90 fps,而较早的一个较慢的计算机也许只能以 10 fps 的速度缓慢运行。  
对于非常复杂的图形,大多数现代计算机都完全可以按 20 ~ 30 fps 的速率运行 Pygame 程序。所以如果希望你的游戏在大多数计算机上都能以相同的速度运行,可以选择一个 20 ~ 30 fps(或者更低)的帧速率。这已经很快了,足以生成看上去流畅的运动。从现在开始,这本书中的例子都将使用 clock.tick(30)。  
检查帧速率  
如果想知道你的程序能以多快的速度运行,可以用一个名为 clock.get_fps 的函数检查帧速率。当然,如果将帧速率设置为 30,它就总会以 30 fps 的帧速率运行(假设你的计算机能够运行那么快)。要看一个特定程序在特定机器上运行的最快速度,可以先将 clock.tick 设置得非常快(例如 200 fps),然后运行这个程序,用 clock.get_fps 检查实际的帧速率。(接下来就会给出一个这样的例子。)  
调整帧速率  
如果想要确保你的动画在每个机器上都以相同的速度运行,可以利用 clock.tick 和 clock.get_fps 实现一个小技巧。因为你知道要以多快的速度运行,而且也知道实际运行的速度,因此可以根据机器的速度调整(scale)动画的速度。  
例如,假设已经设置了 clock.tick(30),这说明你想按 30 fps 的帧速率运行。如果使用 clock.get_fps 并发现只得到速率为 20 fps,可以知道:屏幕上对象移动的速度比你希望的要慢。因为每秒的帧数更少,所以每一帧必须把对象移动得更远,这样看上去才跟得上预想的速度。你的移动对象可能有一个名为  speed 的变量(或属性),这会告诉它们每一帧移动多远。只需要增加 speed对运行速度较慢的机器做出补偿。  
要增加多少呢?可以按期望帧频率与实际帧速率的比值来增加。如果对象的当前速度是 10,期望的帧速率是 30 fps,程序实际运行速率为 20 fps,可以得到:  
object_speed = current_speed * (desired fps / actual fps)object_speed = 10 * (30 / 20)object_speed = 15
所以并不是每帧要将对象移动 10 个像素,而是需要移动 15 个像素,才能弥补较慢的帧速率。我们将在本书后面的一些程序中使用这个技巧。  
下面的沙滩球程序使用了前面几节讨论的内容:Clock 和 get_fps。  
代码清单 17-4 沙滩球程序中使用 Clock 和 get_fps
 
  
你可能已经注意到,代码清单 17-4 末尾的 while 循环中使用了 while 1 ,而不是像代码清单 17-3 中那样使用 while True。它们的作用完全相同。检查 True 或 False(像在 while 语句中一样),值 0、None 以及空串或空列表都看作是 False,所有其他值都作为 True。所以 1 = True,也正是因为这个原因,while 1 等同于 while True。这两种写法在 Python 中都很常用。
,而不是像代码清单 17-3 中那样使用 while True。它们的作用完全相同。检查 True 或 False(像在 while 语句中一样),值 0、None 以及空串或空列表都看作是 False,所有其他值都作为 True。所以 1 = True,也正是因为这个原因,while 1 等同于 while True。这两种写法在 Python 中都很常用。  
如果你运行程序的方式不同,可能还会发现别的问题。如果使用 SPE,并使用“Run in terminal without arguments”来运行程序,结束 Pygame 程序时终端窗口可能关闭,所以你看不到打印帧速率的 print 语句的输出。有两种方法可以解决这个问题。  
 终端窗口可能会一直处于打开状态(这要看你使用什么系统)。在我的系统上,必须在结束 Pyagame 程序之后手动关闭终端窗口。  
Pygame 和动画精灵的基本知识就介绍完了。在下一章中,我们将使用 Pygame 建立一个真正的游戏,我们还会介绍另外一些你能完成的工作,比如增加文本(显示游戏得分)、声音和鼠标及键盘输入。  
你学到了什么  
在这一章,你学到了以下内容。  
 测试题  
 - 什么是碰撞检测?   
- 什么是像素完美碰撞检测?它与矩形碰撞检测有什么区别?   
- 可以利用哪两种方法跟踪多个在一起的动画精灵对象?   
- 在代码中控制动画的速度有哪两种方法?   
- 为什么使用 pygame.clock 比使用 pygame.time.delay 更准确?   
- 怎么得出你的程序运行的帧速率?   
动手试一试  
键入这一章中的所有代码示例就能让你试个够。如果还不够,可以回过头去再做一遍。相信你能从中得到很多收获!