首页 » 与孩子一起学编程 » 与孩子一起学编程全文在线阅读

《与孩子一起学编程》17.3 统计时间

关灯直达底部

到目前为止,我们一直在使用 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.tickclock.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 个像素,才能弥补较慢的帧速率。我们将在本书后面的一些程序中使用这个技巧。

下面的沙滩球程序使用了前面几节讨论的内容:Clockget_fps

代码清单 17-4 沙滩球程序中使用 Clock 和 get_fps

你可能已经注意到,代码清单 17-4 末尾的 while 循环中使用了 while 1,而不是像代码清单 17-3 中那样使用 while True。它们的作用完全相同。检查 TrueFalse(像在 while 语句中一样),值 0None 以及空串或空列表都看作是 False,所有其他值都作为 True。所以 1 = True,也正是因为这个原因,while 1 等同于 while True。这两种写法在 Python 中都很常用。

如果你运行程序的方式不同,可能还会发现别的问题。如果使用 SPE,并使用“Run in terminal without arguments”来运行程序,结束 Pygame 程序时终端窗口可能关闭,所以你看不到打印帧速率的 print 语句的输出。有两种方法可以解决这个问题。

 
  • 使用 Run without arguments(CTRL-Shift-R)运行程序,你会在 SPE shell 窗口中看到 print 语句的输出(在 SPE 的文本编辑器窗口下面)。

  • print 语句后面增加一个延迟,比如:pygame.time.delay(5000)。这会在终端窗口关闭之前给你留出 5 秒钟的时间读输出。

终端窗口可能会一直处于打开状态(这要看你使用什么系统)。在我的系统上,必须在结束 Pyagame 程序之后手动关闭终端窗口。

Pygame 和动画精灵的基本知识就介绍完了。在下一章中,我们将使用 Pygame 建立一个真正的游戏,我们还会介绍另外一些你能完成的工作,比如增加文本(显示游戏得分)、声音和鼠标及键盘输入。

你学到了什么

在这一章,你学到了以下内容。

 
  • Pygame 中的动画精灵,以及如何使用动画精灵处理多个移动的图像。

  • 动画精灵组。

  • 碰撞检测。

  • pygame.clock 和帧速率。

测试题

 
  1. 什么是碰撞检测?

  2. 什么是像素完美碰撞检测?它与矩形碰撞检测有什么区别?

  3. 可以利用哪两种方法跟踪多个在一起的动画精灵对象?

  4. 在代码中控制动画的速度有哪两种方法?

  5. 为什么使用 pygame.clock 比使用 pygame.time.delay 更准确?

  6. 怎么得出你的程序运行的帧速率?

动手试一试

键入这一章中的所有代码示例就能让你试个够。如果还不够,可以回过头去再做一遍。相信你能从中得到很多收获!