Net Framework智能版开发移动游戏

         Microsoft .NET Compact Framework 是完整的Microsoft.NET Framework的一个子集。 Microsoft? .NET Compact Framework适用于资源受到限制的设备,这种设备一般只有很小的显示屏幕和内存,通过这种方法,开发者将使用很多完整性大幅降低的函数取得增强的性能。

  .NET Compact Framework的优点,包括用于Pocket PC及其他Windows CE.NET设备的简单二进制配置,提高了开发者的生产效率并且缩短了开发时间。

  在本文中,我将讨论编写面向小型设备的游戏的关键技术,并且说明怎样使用.NET Compact Framework来很容易的处理它们。我将讨论一些高级性能调节技术,帮助你把游戏推到极致。 总而言之,你将看到使用.NET Compact Framework开发和优化你的游戏是多么容易的一件事。

  本文假定读者已经熟悉.NET Compact Framework并对游戏开发有一定的了解。

  全屏幕游戏窗体

  通常在游戏应用程序中,肯定特别想要使用设备的全屏幕显示。一个占满整个屏幕区域的窗体被称为全屏幕窗体(也称为游戏窗体)。换句话说,一个全屏幕窗体占据整个桌面(或者客户区)和非客户区比如顶部的标题/导航条,边框和底部的菜单条。

  一个应用程序通过设置它的WindowState为Maximized来创建一个全屏幕窗体。

  form.WindowState = FormWindowState.Maximized;

  如果窗体上带有菜单条(或者在Pocket PC的工具栏)那么它不算是全屏幕。

  在Pocket PC的.NET Compact Framework 1.0版本中,为了创建一个全屏幕应用程序,WindowState属性必须在窗体的OnLoad中设置。

  下面的图1和图2说明了Pocket PC上的全屏幕和非全屏幕窗体的区别。

 

  图1非全屏幕窗体

 

  图2全屏幕窗体

  全屏幕窗体的主要的含意就是没有标题栏/导航条或者菜单条。应用程序必须考虑到这些因素,并且在必要时要尽量不使用这些功能。

  如果你只是想要你的窗体占满可用的桌面区域(而不是全屏幕),那么你不必做任何事。默认时,.NET Compact Framework将自动地改变窗体大小占满Pocket PC的屏幕。

  事实上,最好你别明确地设置窗体的ClientSize,因为如果你这么做的话,在各种Windows CE.NET设备之间可能会防碍你的应用程序的相互适应性。 比如,如果你明确地设置你的应用程序显示尺寸来匹配某种设备的窗体,那么它很可能在不同的设备上就不能很好的显示。 建议使用窗体的缺省大小。

  覆盖OnPaint和OnPaintBackground

  一个典型的游戏应用程序将自定义描画窗体的内容。 应用程序通过覆盖一个控件的OnPaint()事件然后自定义处理窗口的描画。

 

  每当一个控件开始描画时,它的背景首先自动地刷新。 举例来说,在OnPaint()描画控件内容的过程中,它的背景先使用this.Backcolor中指定的颜色描画。 但是上面提到的这种应用程序自己描画窗体的情况可能并不太理想;在应用程序完成前景着色以前,背景的自动着色可能导致背景的瞬间闪烁。

  为了防止这种默认的情况发生,每当一个应用程序覆盖OnPaint()方法的时候,强烈建议也覆盖OnPaintBackground ()方法重画背景本身。 象在下面的例程中一样,应用程序可以处理OnPaint()中的所有的描画,而空着OnPaintBackground()。

 

  用于着色的Off Screen位图技术

  你可以通过获取用于屏幕的图形对象,在屏内描画,通过调用一个控件的this.CreateGraphics()直接画到上面。 要牢记,当屏内图形对象不再需要的时候,一定要把它去除。不这样做可能会使显示设备的资源紧张。

  如前面章节所述,你可以通过PaintEventArgs.Graphics访问OnPaint()和OnPaintBackground()方法中的屏幕图形对象。一旦这些描画方法被执行,这些图形对象就会被自动去除。

  游戏程序在屏幕上直接描画往往并不理想。因为当你在屏幕上描画许多对象的时候,你将看到屏幕闪动。为了避免屏幕闪动,游戏开发者通常使用屏外描画技术。

  主要的思想是创建一个屏外位图,获得它的图形对象,执行关于它的所有的描画操作(在内存中)并且复制生成的位图到屏幕上。

 

  在这个例子里,我将创建一个正好是游戏窗体客户区大小的屏外位图。 基于需要,它的尺寸是可以改变的。 然而,维持屏外和屏内图形边界大小关系为1:1将非常有益,尤其是当需要翻译屏内和屏外子图形的坐标时。

 

 

  这个技术避免了屏幕的闪动,并且当内存中所有的屏外描画操作都在进行的时候也非常快。

  子图形

  像位图这样的图象都是以矩形形式出现,但是在现实世界中大部分的子图形是不规则形状的(并不是矩形的)。 所以我们需要找到办法让我们从一个矩形图象中提取出一个不规则子图形图象。

  Color Key透明度

  游戏开发者使用的一种流行的技术就是Color Key技术,当渲染时指定的颜色就被从位图中抽去。 这种技术也称为色度键掩盖,颜色剔除和透明混合。

 

  图3子图形.

 

  图4使用Color Key透明度

 

  图5未使用Color Key透明度

  在子图形位图中(图3),非对象区域充满着绛红色,可以被用做color key。 使用或者不使用这种技术的混和效果分别地如图4和图5所示。

  透明混合的第一步是建立在渲染时要被掩盖的color key(色度键)。我们需要指定一个精确的色度键值,而不是指定一个范围。

 

  如果你不想使用Color类中提供的标准颜色组,你可以按照如下方法来指定红色、绿色和蓝色(RGB)值构造你自己的颜色。

 

  另一种我经常用来指定色度值的技术就是直接使用像素值。 这就避开了处理RGB值的必要。 而且,色度值不是硬编码,可以独立的在位图上被改变。

 

 

imgattr.setcolorkey(bmpsprite.getpixel(0,0), bmpsprite.getpixel(0,0));

  现在,让我们看看如何使用我们建立的色度值透明地描画子图形。

 

  在上面的代码段里,目标矩形被指定为new Rectangle(x,y,bmpSprite.Width,bmpSprite.Height),这里x和y是子图形的所要放置位置的坐标。

  通常,在描画的时候,有可能需要伸展或者缩小子图形。 你可以通过调节目标矩形的宽度和高度达到这个目的。 同样,你还可以调节源矩阵来仅仅描画子图形的一部分,即使这并不一定非常有用。

  当设置用于你的子图形的位图时,建议考虑一下你的目标设备的色彩解析度。 例如,一个24位色位图在一个12色彩解析度的设备上可能不能渲染;这两种在颜色梯度上的区别可能依赖于选择使用的颜色。 而且当选择要被掩盖的ColorKey时,要保证这个颜色值要处于目标显示支持的颜色范围之内。 记住,它支持精确的ColorKey匹配。

  Color Key透明度是.NET Compact Framework支持的唯一的混合技术。

  把图象作为嵌入资源

  你可以把一个图片资源嵌入到你的部件中去,添加图象到工程中,并且设置它的Build Action属性为" Embedded Resource "。 请参看在线帮助主题" Embedding Resource Files in Applications "获取更多的信息。

  然后我们就可以象下面这样使用资源中的内嵌位图了。

 

  Bitmap类支持BMP、JPG、GIF和PNG图像格式。

  调整你的着色方法

  游戏中的着色程序必须严格的最优化设计以便取得最好的性能。 强制的给屏幕着色将会擦除并重画所有的屏外的子图形,然后使用屏外图象刷屏。 显然,这样做效率很低,因为我们没有必要每次都重画整个屏幕。 在这里,我们的帧速率取决于Compact Framework能够刷新整个屏幕的速率。

  一种更好的方法是计算我们的游戏中的子图片的变化的区域,然后只刷新屏幕中这些变化的部分。 子图片可能因为各种原因变化,比如移动、在图象/颜色上变化或者与其他的子图片冲突等。在本章,我们将讨论各种用来有效的计算子图形变化区域的技术。

  变动区域计算

  让我们研究一个移动的子图形,计算变动区域的一个简单的方法就是取得新图片和老图片界线的并集。

 

 

refreshscreen(rectangle.union(sprite.previousbounds, sprite.bounds));

  这里的RefreshScreen()是一个刷新屏内指定矩形区域的方法。

 

  图6.变化区域为新老边界并集

 

  图7.大的变动量产生大的变化区域

  注意 如果新旧坐标之间的变化量很大,或者子图形的尺寸很大,那么这种技术可能会产生一个不必要的大的变化区域,参看图7。

  在这种情况下,用单位矩形组合起来表示变动区域,计算子图形的变动区域非常有效。

  首先让我们看看新旧边界是否重叠。 如果没有重叠的话,我们可以分别把新旧两个的边界当成是两个独立的矩形来计算。 所以,如7所示方案,把新边界和旧边界作为两个不同的变化区域分别处理。

 

 

  如下图8所示,上面的技术也可以用到不必在乎重画重叠区两次的情况下。

 

  图8变化区域被分为新旧两个边界

  现在,让我们看看如何把新旧边界的重叠部分分成单位矩形用来计算变动区域,这样就不会把变动区域重复两次了,意思是所有的单位矩形都是互不相交的。

  首先,把新的边界作为整体单位。注意,这包括新旧边界的重叠部分。

  变化单位区域1:描述当前边界的变化矩形

 

 

refreshscreen(sprite.bounds);

 

  图9.变动区域被分解为几个单位。

  现在,把旧的边界中的不重叠部分分解成为两个独立的单位,见下面的代码:

 

 

  变动区域单位3

 

  检测冲突

  现在,让我们看看一个子图形和另一个子图形冲突的情况。 你可能忽视了两个子图形之间的冲突,而只是使用我们前面讨论的技术分别的去更新子图形的变动部分。

  但是出于游戏应答的目的,你经常需要去检测子图形的冲突。 举例来说在一个射击游戏中,当一发子弹击中靶子,你可能会想要出现爆炸等可视的反应。

  在本文中,我将不详细地讨论不同的可用的检测冲突的技术。但是,我会突出介绍它们中的几种。

  让我们回想一下,大多数的子图形都是不规则形状的,但是描述它们的位图都是矩形的。很难把一个子图形的边界显示为自由的区域,所以我们求助于通过封装矩形来显示它。

  当游戏者观察屏幕上的子图形的时候,他实际上只是注意看子图形区域而对非子图形区域并不十分注意。 因此,子图形之间的任何冲突检测必须只在它们各自的子图形区域内发生,而不应该包括非子图形区域。

  对于较小的子图形,当我们计算冲突并在视觉上消除它的时候通常使用整个子图形边界。 因为如果对象很小并且移动迅速,肉眼是注意不到那些错觉的。 这两个子图形边界的简单矩形相交就足够了。

 

 

rectangle.intersect(sprite1.bounds, sprite2.bounds);

  如果子图形是圆形的,那么我们可以计算圆心之间的距离然后减去它们的半径;如果结果大于零那么我们就会探测到一个冲突。

 

  快速边界插入技术应当首先应用到检测子图形的边界冲突上。如果发生冲突,那么我们可以使用一个更精确的方法,比如冲突位图掩盖技术来识别重叠像素。

  如果我们不在乎像素水平的间隔度,那么我们可以使用前面在变动区域计算部分提到的计算冲突区域的方法。不同于处理一个子图形的新旧边界,我们现在将处理两个冲突子图形的边界。

  具体的冲突检测技术应该由具体的情况决定。选择某种特定的技术取决于各种各样的因素,比如子图形的大小与形状,游戏的特性等等。 在单个游戏中,使用这些技术并不罕见。

  子图形速度

  帧速率经常会被误解为子图形移动速度。我们不能只依靠帧速率,还要调节子图形每帧移动的距离来取得所要的净速度。

  让我们研究一下下面这个例子,这个例子里我们想要子图形在1秒时间内垂直移动100像素单位。现在我们可以固定帧速率为10 fps,然后每帧移动子图形10像素单位达到那个净速度;或者我们可以增加帧速率为20fps,并把子图形每帧垂直移动量降为5像素。

  这两种获得相同净速度的方法,区别是在前面的一种情况下,子图形的移动可能在视觉上有一些跳动,因为它在比后者更少的刷新循环中移动的距离更大。但是在后面这种情况下,我们的游戏是20fps,所以在决定使用哪一种方法之前,我们需要绝对确保硬件、系统和.NET CF的性能。

  游戏进展技术

  一个游戏应该有情节发展,这时随着用户交互,屏幕内容会变化。

  游戏循环

  在游戏中,我们初始化、维持并取消一个循环,游戏循环在必要时能给我们渲染屏幕内容的机会。一般情况下,游戏开始的时候,初始化游戏循环, 一般地情况下,当游戏启动的时候游戏循环初始化,然后通过休眠和根据需要循环返回来维持循环,直到游戏终止。

  这个技术提供动作游戏最大的灵活性和快速反应性。我们现在来实现一个用于我们足球游戏的游戏循环。假定这个游戏有很多级别。

 

 

  注意,每次我们在循环返回之前,在循环中调用Application.DoEvents(),我们这么做的目的是与系统保持主动联系,并且处理事件队列中等待处理的消息。这是很必要的,因为当我们的应用程序处于一个循环中的时候,我们基本上失去了处理任何来自系统的消息的能力;除非我们明确调用 Application.DoEvents(),否则我们的应用程序不会响应系统事件,并且可能产生不希望有的副作用。

  游戏循环中要考虑的另一个问题是渲染速率。大部分的游戏动画需要至少8 - 10帧每秒。相比较之下,典型的动画电影渲染速度是14 - 30帧每秒。

  一个简单的帧速率调节技术就是确定需要的每秒帧以及循环中休眠(1000/fps)毫秒。但是我们也需要计算处理所使用的时间,否则我们的渲染速率将要比预期的要低。处理时间可能会相当长,尤其在较慢的硬件上,因为它要进行高成本的操作,比如处理用户输入,渲染游戏等等。

 

 

  定时回话

  另一种技术是建立一个周期性地回话的系统计时器。就象游戏循环一样,定时器一般在游戏开始和定时信号事件(每隔一定时间发生)被处理的时候实例化,直到游戏结束。

  我们选择了游戏循环这个简单的比喻,是因为我们让系统为我们处理定时器。我们只要处理定时器回话,然后通过每次重画一帧的方法让游戏进展下去。而且,我们不必担心会消耗完事件队列。

  但是我们必须小心选择定时器的定时信号间隔,因为它决定了我们游戏的帧速率。

  在游戏循环技术中,两个定时信号之间的时间间隔是完全被我们的控制的,而且前面我们也可以看到,这很容易考虑处理时间。另一方面,定时回话意味着时间间隔不能改变。 所以我们要么把定时间隔设置的足够大,以便能够完全处理每个回话;要么调节定时信号的处理过程,通过明确地掌握处理定时信号和根据维持节奏需要而跳过定时信号的时间。

  使用定时器回话的一个主要缺点就是我们要依赖操作系统定时器的分辨率。最小定时信号间隔可能取决于定时器最大可能的分辨率。 在Pocket PC上这可能是一个限制因素,因为在Pocket PC上,时间分辨率很低,就是说用在本方法中的fps也很低。而且,操作系统定时事件的优先权相当低,意味着游戏的响应度也很低。

  如果不管这些限制的话,这个技术最适用于慢进度的游戏,在这些游戏里帧速率并不是最重要的。比如说,屏幕保护程序。

 

  注意:当游戏结束的时候,你必须去除定时器。

  注意没有必要调用Application.DoEvents(),因为我们既不使用循环,也不截取任何系统事件,事实上,定时器回话也正是另一种系统事件。而且,注意大部分游戏工作程序被压入OnTimerTick()事件处理程序。

  无效更新

  让游戏进展下去的方法就是在一个已经设定的基础上渲染它。每当游戏程序探测到屏幕需要被刷新,我们就可以请求系统使屏幕的某些相应的区域失效,然后刷新这部分。

  在所有适用于游戏进展由用户交互控制的游戏的技术中,这种技术是最简单的。可用于那些没有恒定定时信号和渲染(见前面游戏循环、定时回话)的游戏,这些游戏只能靠用户操作才能进展,比如猜谜游戏。

  我们可以通过调用this.Invalidate()使我们的游戏窗体整个客户区失效,或者通过调用this.Invalidate( dirtyRect)使客户区的一部分失效。

  仅仅调用this.Invalidate()不能保证及时描画屏幕。我们必须通过调用this.Update(),来保证在进行下一步之前刷新屏幕。异步地调用Invalidate()和Update()方法,在某些情况下有助于取得更高的性能。但是不使用正确的帧同步技术,它可能导致在屏幕上出现伪迹,并且使帧丢失。

  如果我们能够每次刷新整个屏幕,那么我们只要调用this.Refresh(),就能保证Invalidate()和Update()依次调用。

  当我们可以自己描画窗体的时候,我们可以在屏幕的任何部分需要被刷新的时候调用this.Refresh(),然后有选择性的刷新OnPaint()和OnPaintBackground()内的屏幕。

  最优化启动时间

  在游戏中,游戏开发者通常在游戏启动的时候把所有的游戏参数都初始化,以避免在游戏运行的时候产生不必要的延迟。这种方法不好之处在于延迟都被转入游戏启动时间中,尤其是如果用户在开始玩游戏之前,游戏加载时间过长的话可能让人感觉非常不愉快。

  在这种情况下,最好的办法是我们能够显示一个游戏相关信息的宣传页面,然后在后台进行启动工作比如加载资源,初始化游戏参数等等。

  我们可以要么使用一个单独的全屏幕窗体作为宣传页面,要么仅使用主游戏窗体本身配以基本的游戏信息。

 

 

  重要的是不要在启动介绍页面里提供任何函数功能,最好只是把它用做一个介绍/信息页。启动时显示宣传介绍页面并不是必须的。

  游戏按键

  方向键

  在Pocket PC中,方向键(即上下左右键)在游戏中扮演了一个非常重要的角色。我们可以把这些键的KeyDown、KeyPress和KeyUp事件分别设置为我们游戏窗体中的相关的事件方法。

  通常,我们想处理这些方向键的KeyDown事件,提供游戏级功能。

 

 

  Pocket PC输入笔

  Pocket PC上的输入笔的功能就好象台式机的鼠标。我们可以把MouseDown、MouseMove和MouseUp事件分别设置为我们游戏窗体中的相关的事件方法。

 

 

  在Pocket PC上,.NET Compact Framework V1.0不支持鼠标右键和hardware键。

  其它提示

  可能的情况下,与其使用一幅位图,不如自己画图。这将减少占用内存大小,还可以提高性能。比如说,在一个太空射击游戏中,与其使用位图作为背景,不如自己使用黑色的矩形填充背景然后画上星星。

  尝试把类似的位图组合成一张大的位图,以后可以根据需要使用相应坐标选取对应的单位位图。使用大的位图代替好几张小图将所有所在资源的大小。

  试用除了BMP以外的图像格式(例如JPEG),这样可以充分利用其良好的图像压缩率。

  尽可能多的静态初始化你的游戏程序,以免在运行期间进行大量的计算。 例如,在猜谜游戏中,你应该静态保存答案,而不要在游戏运行时再去使用代价高昂的动态算法。

  结论

  当我们编写用于Pocket PC这样的小型设备的游戏时,我们要牢牢的记住,显示屏幕非常小,并且硬件比台式机功能要差很多。

  所以我们要最大限度的最优化用于这些小型设备上的游戏。当设计游戏时,要认真的考虑目标硬件、操作系统和.NET Compact Framework的性能。

  一个游戏还要以它的描画程序取胜。高效率的描画技术决定了游戏的反应灵敏度,特别是象Pocket PC这样的小型设备。所以要使用我们前面提到的描画调节技术(比如变动区域计算技术)来节省每帧描画所用时间。

  帧速率是另一个要牢牢记住的重要因素,要根据目标设备的性能灵活选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值