调整帧率

  尽管三环动画看起来相当不错,但可能有时您会觉得动画的速度太快或者太慢而想要去改变它。

  前面我提到过帧率的概念,这里还是简单地提示一下:帧率表示一秒钟游戏重绘整个场景的次数。在XNA中,PC和Xbox 360游戏的默认帧率为60fps,而在Windows Phone 7游戏的默认帧率为30fps。除非您在一台非常慢的机器上运行这个程序,否则您很有可能看到动画以60fps绘制。

  还有一种不同类型的帧率,和单独的动画相关,这种帧率(通常称为动画速度)反映了给定动画帧序列绘制一次的速度,或者说一秒钟绘制的动画帧数。目前您的动画的速度是60fps,因为您每次重绘场景(60fps一次)都会从精灵位图中提取并绘制一个新的精灵帧。

  这里有几种方法可以改变这个三环动画的速度。XNA的Game类有一个叫做TargetElapsedTime的属性,用来告诉XNA在每次Game.Update调用之后等待了多久。本质上,这个属性表示每一帧之间的时间间隔。默认情况下这个值被设为1/60秒,也就是帧率为60fps。

  要改变程序的帧率,添加以下代码到Game1类构造方法的末尾:

TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 50);

  这将告诉XNA每50微妙调用一次Game.Update,相当于帧率20fps。编译并运行游戏,您会发现动画以低得多的速度运行。在TimeSpan构造方法中尝试不同的值(例如1毫秒)来看看动画循环的速度。

  理想情况下,您会希望将帧率保持在60fps左右,就是说可以不用管默认帧数。为什么要以60fps作为标准呢?因为这是显示器或电视机不会让人眼察觉到闪烁的最低刷新率。

  如果您将帧率调得太高,XNA不保证您能获得期望的性能。显卡GPU的速度、电脑处理器的速度、耗的资源数量和代码的效率都等,对于游戏是否拥有最佳性能,都有着决定性的影响。

  幸运的是,XNA提供了一种方法来检测您的游戏是否存在性能问题。Update和Draw方法都有的GameTime对象参数,有一个叫做IsRunningSlow的布尔类型的属性。您可以在任何时候在这两个方法中检查IsRunningSlow的值;如果值为true,XNA将不能跟上您指定的帧率。在这种情况下,XNA会进行跳帧以努力达到您期望的速率。这或许不是您愿意在任何游戏中看到的结果。所以如果出现这样的情况,您可能应该提醒用户他的机器配置很难运行您的游戏。

调整动画速度

  尽管调整游戏本身的帧率可以影响动画的速度,但是这样做并不是理想的方法。为什么呢?当您改变了游戏的帧率,将会影响到所有精灵的动画速度,比如移动物体的速度之类。如果您希望一个动画的速度为60fps而另一个为30fps,您就不应该通过改变整个游戏的帧率来实现。

  移除之前修改TargetElapsedTime的代码,让我们尝试一种新的途径。

  当调整一个精灵动画的速度的时候,一般您应该只针对该精灵做这样的调整。这可以通过只在指定时间过后才移动到精灵位图的下一帧的方法达到目的。要实现这种效果,需要添加两个用来追踪动画帧之间时间间隔的成员变量:

int timeSinceLastFrame =  0;
int millisecondsPerFrame = 50;

  timeSinceLastFrame变量用来追踪自上一帧之后经过了多少时间。millisecondsPverycdFrame变量用来指定在移动当前的帧索引之前您想要等待多长时间。

  实际的精灵帧的循环是在您的Update函数中产生的。因此,下一步是检查精灵帧之间的消耗时间,并且仅在预设的消耗时间达到时,才执行移动当前帧的索引。修改您加在Update函数中的代码,加入如下所示的if嵌套语句(粗体字是修改的部分):

// 原文粗体的部分是开头两行
timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (timeSinceLastFrame > millisecondsPerFrame)
{
	timeSinceLastFrame-= millisecondsPerFrame;
	++currentFrame.X;
	if (currentFrame.X >= sheetSize.X)
	{
		currentFrame.X = 0;
		++currentFrame.Y;
		if (currentFrame.Y >= sheetSize.Y)
		 currentFrame.Y = 0;
	}
}

  就像您看到的那样,这里使用gameTime.ElapsedGameTime属性来检测自上一帧之后经过了多少时间。这个属性表示上一次调用Update方法后经过的时间。您用timeSinceLastFrame变量加上这个时间增量,当变量的值大于您想要在帧间等待的时间后,进入到if语句中,用timeSinceLastFrame减去millisecondsPerFrame来调整timeSinceLastFrame的值,然后移动到下一个动画帧。

  现在编译并运行游戏,您将会看到三环动画速度变慢。这里值得注意的是三环动画的运行帧率(20fps)不同于游戏的帧率(60fps)。用这个方法您可以使任意数量的动画在不同的帧率运行而不用牺牲游戏整体的帧率。

您刚刚做了些什么

  现在是时候稍作停留了,因为您现在已经知道如何随意地在XNA中实现2D动画了!让我们花几分钟回顾一下您在本章学习到的东西:

  • 您研究了XNA游戏场景的背后发生的事情,包括XNA程序流程和XNA游戏循环。
  • 您在屏幕上绘制了第一个精灵。
  • 您学习了一些内容管道的知识并了解了它的用途。
  • 您在屏幕上移动了一个精灵。
  • 您接触到了精灵的透明度,垂直翻转和其它一些操作。
  • 您使用基于层深度的不同的Z次序来绘制精灵。
  • 您使用精灵位图绘制了精灵动画。
  • 您调整了游戏的帧率。
  • 您调整了单独的精灵动画速度。

总结

  • 当您创建了一个XNA项目的时候,它内置了一个游戏循环和程序流程。游戏循环由Update/Draw调用组成。程序流程包含初始化(Initialize),加载内容资源(LoadContent)和执行特殊的卸载操作(UnloadContent)几个步骤。
  • 要在屏幕上绘制一个精灵,您需要一个Texture2D对象来在内存中保存精灵。内容管道在编译期将精灵编译成内部格式以便于绘制。然后您使用一个SpriteBatch对象将精灵绘制到屏幕上。
  • 所有的绘制操作都应该在SpriteBatch.Begin和SpriteBatch.End调用对之间完成。这些调用通知图形设备精灵信息正在送到显卡中。Begin方法有些重载的版本让您能够改变处理透明度的方式和精灵的排序方式。
  • 精灵动画通常通过一个精灵位图来实现(一个包含各种用卡通手翻书方式绘制的精灵帧的位图)。循环遍历并绘制这些图像使得精灵以动画方式显示。
  • XNA游戏默认的帧率是60fps。改变这个值将会影响没有使用单独的计时器来决定动画速度的精灵动画的帧率以及整个游戏的帧率。
  • 为了改变单独的精灵动画速度,您可以设置一个计数器来追踪自上一帧后经过的时间,并仅在经过了X毫秒的时间后才更新帧。

知识测试:问答

  1. XNA的游戏循环有哪些步骤?
  2. 如果想要加载一个Texture2D对象,您需要在哪个方法中做这件事?
  3. 要把XNA游戏的帧率改为20fps,您应该使用什么样的代码?
  4. 在加载一个Texture2D对象时,您应该向Content.Load传递什么参数?
  5. 真还是假:如果您添加到项目中的文件不能被内容管道解析,它将会在编译期告诉您。
  6. 您绘制一个精灵,并且想让背景透明,需要哪些步骤?
  7. 您有两个精灵(A和B),当它们重合时您希望A总是在B之上绘制,您应该怎么做?
  8. 在循环遍历一个精灵位图的时候,您需要追踪哪些变量?

知识测试:练习

  在本章中,您开发了一个两个XNA LOGO在屏幕上移动并在边缘之间反弹的例子。现在把本章末尾的动画动画例子改写成和它一样的移动和反弹方式——不过这一次要让动画精灵同时沿着X轴和Y轴移动,并且在屏幕的四个边缘之间反弹。