持更大范围的硬件设备而设计。选择使用Reach配置些游戏,会牺牲一些较强大的图形API,但能保证游戏在大量设备上运行。HeDef设计支持当今最强大的图形设备,它面向的是Xbox360硬件和至少支持DirectX 10的Windows计算机。
在以前版本的XNA中,BasicEffect类只提供非常基本的效果,它的目的是即时没有深入掌握复杂的着色器(shader)代码,也能顺利地构建新游戏。但随着不支持定制着色器的Windows Phone 7的问世,情况发生了变化,在Reach和HiDef两种配置中,添加了新的可配置效果,诸如:基本效果(Basic Effect)、双重纹理效果、alpha测试效果、皮肤效果(模型批量渲染)和环境贴图效果。
XNA 4.0中的另外几项新特性是针对Windows Phone 7的,如XNA 4.0中的标量(scalar)允许开发人员在写游戏时不必担心本机分辨率或屏幕方向,XNA会自动处理屏幕分辨率的缩放。再如利用
Microsoft.Xna.Framework.Input.Touch命名空间中的类就可以使用Windows Phone设备的多点触摸输入功能。这个命名空间添加了TouchPanel类和TouchLocation类,允许从输入设备访问触点。
此外,XNA 4.0还支持两种音频处理方式,一种是用传统的“Microsoft跨平台音频创建工具”(Microsoft Cross-Platform Audio Creation Tool, XACT),另一种是利用简化API来处理游戏中的音频。
二 用XNA开发2D游戏关键技术
1游戏类Game1类
XNA游戏开发中一个非常重要的类型是Game1类型,所有的XNA游戏项目(2D游戏和3D游戏)都会拥有Game1对象,并通过它来处理游戏逻辑和运行游戏。
Game1类中提供了2个类级变量、1个Game1构造器和5个其他方法。第一个类级变量为graphics,它的类型是GraphicsDeviceManager。这个对象使得开发人员能够访问PC、Xbox360和Windows Phone 7上的图形设备。
GraphicsDeviceManager对象有一个GraphicsDevice的属性,代表机器上的实际图形设备。由于这个图形设备对象充当着XNA游戏和机器上显卡之间的桥梁,所以在XNA中,任何绘制在屏幕上的图像都是通过该对象完成的。
另一个变量是SpriteBatch类的一个实例。这是一个“精灵”对象。在计算机图形术语中,精灵(sprite)被定义成“能集成到更大场景中的2D或3D图像”。2D游戏是通过在场景中绘制多个精灵(玩家、敌人、场景或道具等)来构建的。
Game1类中的Initialize方法用于初始化变量以及与Game1对象相关的其他对象。图形设备对象将在这里实例化,并可在此方法中利用该对象初始化依赖于其设置的其他对象。
之后是LoadContent方法,在该方法中会加载游戏需要的全部图形内容和其他游戏中的资源文件,包括图片、模型(.X和.FBX格式)、声音等。
在初始化游戏资源后,Game1对象会进入游戏循环状态。几乎所有的游戏都采用了某种形式的游戏循环。Update方法和Draw方法负责游戏循环。当游戏在运行时发生的所有操作包括逻辑的变更和状态的改变都是在Update中完成,这些操作包括:移动对象、检查碰撞、更新分数、检查游戏结束逻辑等。而Draw
方法则负责将游戏中的所有“精灵”对象绘制在屏幕上。
最后,当游戏结束时,会调用Game1类中的UnloadContent方法来释放所有游戏加载进内存的资源。
2 游戏轮询
游戏开发中一个很重要的概念就是“轮询”。这个概念是游戏程序区别于普通基于事件响应的应用程序的关键所在。所谓“轮询”,就是游戏程序在运行时,将会进入一个循环过程,由开发人员来决定在每个循环过程中应该如何改变游戏状态(game status)。
为了能够改变游戏状态,开发人员需要在Update方法中负责更新所有与游戏相关的事件,包括对象在屏幕上的位置、分数、动画序列等,还可以检查用户的输入、检测碰撞以及调整AI算法。
游戏状态通常分为:启动画面(splash screen),进行游戏(playing the game)和游戏结束画面(end-game screen)。状态还有可能发生细微的变化,如用户可能获得某种形式的法宝(power-up),使他们进入无敌态或被动态等等。
如上所述,通常在Update中修改游戏状态,在Draw方法中利用这些状态绘制与这个特定状态关联的不同图像、场景或其他信息。
一个典型的XNA的游戏生命周期包括Initialize、LoadContent、Update、Draw和UnloadContent。其中Initialize和LoadContent负责初始化游戏对象并且载入游戏资源。Update和Draw方法负责游戏轮询,Update会告诉游戏是否继续进行游戏,调用Draw方法,或者直接结束游戏,调用UnloadContent来释放所有游戏资源,这项工作一般是由XNA自动完成的。
3修改游戏
前面说过,Game1类通过Update方法改变游戏状态,但最终一切游戏中的对象都必须通过Draw方法绘制到屏幕上。Draw方法的目的是以层叠的形式调用GameComponent和其他方法中的Draw方法。
Draw方法是个虚方法(virtual method),它能够被子类所重写,这就意味着子类(游戏对象)能够自定义显示方式,从而让游戏的显示多样化。
下面来讨论下帧和帧频。XNA的游戏循环默认是每秒运行60次,这就说明每秒游戏都要进行60次Update和Draw方法的调用。为什么要进行这么多次调用?这是因为人眼能分辨的最小帧频时24fps,也就是说,如果一段连续的画面能够在一秒钟显示24次以上,人眼就分辨不出它是连续的动画还是静止的图片。我们通常用fps来表示帧频,24fps就表示每秒24帧,即每帧的时间大约是41.667ms,而60fps就表示每帧的时间大约是16.667ms。XNA的默认帧频时60fps,即每秒在屏幕上绘制60次,这样就能形成动态的效果。而Draw方法正是通过GameTime类型的参数来确定游戏的帧频。
那么该如何添加和绘制精灵(sprite)呢?首先我们需要在Game1类中添加一个新类型Texture2D,用它来代表2D图像。在XNA中,所有图形、场景、效果都是通过内容管道(content pipe)加载,它能够统一文件的格式,使程序能够轻松的识别内容,当然,内容管道还能加载声音文件、3D模型、字体等。
创建好Texture2D对象并且通过LoadContent方法将图形加载到内容管道后,就可以在Draw方法中绘制精灵了。Draw方法绘制2D图形是通过SpriteBatch对象完成的,它被定义为一个精灵画刷,并且成对出现,由它的Begin方法开始,End方法结束,所有绘制方法都应在它们中间完成。而SpriteBatch的Draw方法是一个重载方法,它可以接受许多不同的参数,基本的参数有texture、position和color,它们指定了精灵——也就是图形对象,应该绘制在屏幕中的位置,以及该为它们填充什么颜色。当然,你完全可以选择其他重载版本来更加细化地绘制图形,比如通过rotation参数来旋转图形,用scale参数来改变图形比例,用rectangle参数来绘制指定位置的矩形,通过layerDepth参数改变层深度等等。
当然,在绘制图形前,别忘了修改Update方法改变游戏状态,否则图形不会发生改变。
4 2D动画
XNA中实现2D动画的一个普遍方法是通过“精灵表(sprite sheet)”来实现。精灵表是一张m行n列的图像矩阵,其中的每个元素都是一个动画中的一帧,通过Update方法来不断更新矩阵中的图像帧并通过Draw方法显示在屏幕上从而实现动画效果。
为了能够提取精灵表中的各个图片,需要知道如下参数: 1. 精灵表中每张图片(或者帧)的高度和宽度; 2. 精灵表的行、列总数;
3. 对精灵表中的当前行、列进行标示的一个索引。 然后,在Draw方法中的绘制方法如下: spriteBatch.Draw(texture, Vector.Zero,
new Rectangle(currentFrame.X * frameSize.X,
currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y),
Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
注意该方法中Rectangle对象,它的构造函数的前两个参数用于获取精灵表中指定位置的图像的坐标,后两个数用于确定绘制图像的大小。
但要让图像最终动起来,还需要在Update方法中改变索引,改变索引的方法如下:
++currentFrame.X;
if (currentFrame.X >= sheetSize.X) {
currentFrame.X = 0; ++currentFrame.Y;
if (currentFrame.Y >= sheetSize.Y) currentFrame.Y = 0; }
首先改变索引的列坐标,当列坐标越界时,则将列坐标重置为0,同时增加行坐标,以绘制精灵表的下一行。最后,如果行坐标越界,则将行坐标归0,从
帧索引(0,0)开始从头绘制整个动画序列。
5游戏组件
游戏开发中的另一项非常重要的概念是游戏组件,通过游戏组件的形式将游戏的各个组成部分有条理的组合在一起。试想一下,如果将所有的游戏逻辑实现都放在Game1类中,这将是件非常可怕的事情,而且这也会使C#语言完美的面向对象特性变得毫无用武之处。
幸好XNA提供了一种相当好的方式将代码的不同逻辑部分集成到应用程序中。CameComponent类允许以模块方式将任意代码插入应用程序,并将那个组件自动集成到游戏循环的Update调用中,即,一旦执行游戏的Update调用,所有关联的GameComponent类也会自动调用它们的Update方法。
同时,GameComponent类中会自动集成Initialize、LoadContent、Draw等方法,这些和Game1类是一样的。但是如果希望创建的游戏组件同时与游戏循环的Draw方法相关联,是组件同时具有绘制能力,就要使GameComponent从DrawableGameComponent派生。
有了GameComponent类后,可以以它为模板创建一个SpriteManager类,用来管理所有在游戏中用到的对象。然后分别创建不同的具有继承层次的Sprite游戏精灵类来完成不同的游戏对象的不同的Update和Draw方法,最后再通过SpriteManager类统一管理这些游戏对象,添加它们之间的游戏逻辑。
6碰撞检测
一个游戏的用户体验是好是坏,主要依赖于碰撞检测。如果没有碰撞检测,游戏中的对象,主角也好,场景也罢,都无法进行信息的交互,那么这个游戏也就没有可玩性了。但在设计算法是也要适可而止,因为算法愈精确,游戏玩起来可能就愈加“艰难险阻”。所以,在设计碰撞检测算法时,要在准确性和性能之间取得一个平衡。
实现碰撞检测最简单、最快捷的方式之一就是使用“包围框”(bounding-box)算法。也就是在屏幕上围绕每个物体“画”一个框(隐形的框),再检测这些框本身是否相交,从而触发碰撞算法。
2D游戏中包围框算法的实现要用到XNA中的一个类Rectangle,顾名思义,即矩形类,该类提供了一个Intersects方法用于判断两个矩形是否相交。可以先用不同的矩形类将所有应该检测碰撞的对象包装起来,再在游戏循环中检测它们是否相交,也就是碰撞,然后完成一些碰撞后的逻辑。
当然,为了精确碰撞检测算法,就得为图像添加多个更为精确的小的包围框。但也不能强求碰撞检测要百分百的准确,只要玩家察觉不到差别就可以了,因为越精确的碰撞检测会造成性能的下降。试想一下,两个图像一开始只用一个包围框分别包裹起来,那么检测碰撞只要计算两次就可以识别。但如果将每个图像用5个小的包围框包裹起来,那么每次检测就要耗费25次运算,并且当包围框越多,对象越多的时候,这种运算的开销会更大。所以再考虑到性能的前提下,包围框并不是做得越精细越好,只要能体现出碰撞效果适可而止即可。
三 用XNA开发3D游戏的关键技术
1 坐标系与摄像机
处理3D图形是,第一个根本的区别是增加了额外的一维。XNA的2D编程类似于在一块画布上作画,屏幕左上角的坐标是(0,0),X轴向右为正,Y轴向下为正。而3D绘图更像是用一台手持摄像机拍摄视频。3D空间的定位要依赖于3维坐标系统。不同于2D绘图,3D绘图需要两个基本组件用于绘制一个场景:将物体放到世界中,再将摄像机放到世界中,并指定摄像机对准哪个方向。只有摄像机看到的物体才会在屏幕上可见。
与Managed DirectX不同,XNA采用的是右手坐标系。
定义好坐标系后,就必须确定摄像机对象。定义摄像机时,它的位置、指向等属性存储在Matrix(矩阵)对象中。XNA的摄像机由两个Matrix对象组成:试图矩阵和投影矩阵。
视图(View)矩阵用于实现世界坐标向摄像机坐标的转换,它容纳的信息决定了摄像机在世界中的位置、它的指向以及它自己的摆放方向。投影(projection)矩阵用于实现摄像机坐标系向屏幕坐标系的转换,它容纳的信息决定了如何在屏幕上观察3D世界,具体如何观察要取决于摄像机的属性,包括视角、视距等。
为了创建试图矩阵,要使用Matrix的静态方法CreateLookAt。该方法返回一个Matrix对象。另外,该方法接受3个Vector3类型的参数,Vector3是用来表示3D坐标的类型。第一个参数是cameraPosition,用来创建摄像机的位置;第二个参数cameraTarget,用来确定摄像机指向目标位置的坐标,即指向;第三个参数是cameraUpVector,用来代表哪个方向是“上”的Vector3对象。尤其要注意第三个参数,它决定的摄像机的摆放姿势,是竖直、水平或是倾斜,它和cameraTarget共同决定了摄像机的指向和如何摆放。
要创建投影矩阵,要使用Matrix的另一个静态方法Matrix.CreatePerspectiveFieldOfView。该方法也返回一个Matrix对象,并接受4个float类型的变量,分别是:fieldOfView,它决定了摄像机的视角,以弧度为单位,一般为45°,或π/4;aspectRatio,它是摄像机的宽高比,一般设置为屏幕的宽高比;nearPlaneDistance,它是摄像机观察一个物体的最近视距(再近就看不到了);farPlaneDistance,它是摄像机观察一个物体的最远视距(再远就看不到了)。投影矩阵够早的是摄像机的视锥或视野,它定义了3D空间中的一块区域,在这块区域内的物体都会被摄像机捕捉到并显示在屏幕上。
要在XNA的3D游戏中构建摄像机需要创建一个Camera(摄像机)类,在这个类中定义试图矩阵和投影矩阵以确定一个摄像机。
2绘制基元
3D绘图中最基本东西就是三角形,它是3D绘图最主要使用的一种“基元”(primitive)。只要三角形画得足够多,任何形状都能够呈现。将足够多的小三角形紧凑地拼接在一起就能够形成一个光滑的表面。