我们已经得到了所有的对象移入区域和移出区域,可以检查它们是否为空;如果为空,就不需要进行绘制。
假定有几个对象需要绘制,将Graphics.ClipRegion属性设置为将要绘制的区域。任何企图在该区域之外进行的绘制将都是无效的:绘制操作将被裁剪到特定的区域中。该功能很重要,因为我们可能需要对更新区域中的对象进行重绘,而这些对象又同该区域外的对象有所重叠。ClipRegion属性允许我们实现该操作,而不需要对所有重叠了的对象进行重绘,直到绘制到最前端的一个对象为止,如图4-5所示。在该图中,有一堆方框和一个球重叠在一起。当屏幕更新时球被移走了,ClipRegion属性使我们不用再渲染全部方框。
图4-5(a)展示的是原始场景,其中包含了两个方框,在它们的后面有一个球。(b)图展示了球移走之后的场景。移动区域被裁剪掉了,因为这部分还没有被绘制。这使得下方框少了一部分区域。(c)图展示了对下方框进行绘制后的结果(未使用ClipRegion属性)。其中方框被重绘了,但现在却出现在上方框的前面,这并不是我们所期望的结果。为了进行修正,我们对上方框也进行了重绘,这样所有的对象都位于它的后方了。这可能会导致一长串连锁的额外的对象绘制操作。最后,在(d)图中,我们对(c)图重复进行了绘制,但这次使用了ClipRegion属性(虚线矩形所示的区域)。这样,下方框中只有与使用了ClipRegion属性的区域发生重叠的部分进行了重绘。这防止了下方框跳到上方框的前面,因此不需要再做进一步的绘制。
确定了移动区域,并且设置了ClipRegion属性集,我们渲染优化过程的第二步(清空更新区域)就很简单了。如果使用一个背景图像,我们只需要将它复制到移动区域即可;如果不是,那么只需要调用Graphics.Clear方法(只有与ClipRegion属性重叠的区域会受到影响)即可。
现在需要绘制游戏对象,我们先找到那些与移动区域发生重叠的对象,并只对它们进行绘制,这样就可以减少工作量。完全落在该区域之外的对象自从在前面绘制完成后就不需要发生任何改变,因此可以不考虑它们。这同时也节约了大量的GDI处理过程。
在本阶段中,后台缓冲区要被完全更新,并且最后要准备好显示给用户。然而我们也可以对这个过程进行优化。如您所知,我们需要绘制的是移动区域,因此在Render方法的最后,我们可以只将该区域传递给窗体的Invalidate函数。Invalidate函数将触发Present函数,在其中我们可以从后台缓冲区中只将该区域复制到游戏窗体。同样,这些改进可以显著地减少GDI的工作量,使帧率得到提高。
我们还可以将矩形区域累计到一个名为_presentRect的类变量中。Present函数用它来识别将后台缓冲区中的哪个区域复制到窗体上。
为了更形象地显示哪些区域被重绘了,我们在CGameEngineGDIBase.Render函数中添加一些调试代码,在每一帧中的移动区域上渲染一个矩形边框。默认情况下该功能被注释掉了,但如果您想看到屏幕上发生更新的确切区域,就可以将该功能打开。要启用该功能,请找到位于Render函数结尾处调用了DrawRectangle函数的代码,将代码旁边的注释符去掉,如程序清单4-18所示。