在所有的图形引擎中,绘制都是最基础的部分,本文将介绍在XNA框架中与绘制相关的基础知识。
在XNA中,我们使用SpriteBatch来进行绘制。首先,我们需要使用SpriteBatch来绘制什么了?是精灵Sprite,对。
那么Sprite通过什么来表现了?是纹理,比如2D纹理Texture2D。嗯,你可以把纹理想象成Sprite的外表,比如我们制作的一幅精灵图片,就是一个纹理。
我们要如何才能把一幅图片加载到我们的游戏中来作为一个Sprite的纹理了?这要通过素材管道Content Pipeline。所谓素材,包括我们游戏中要用到的图片、模型、声音等,比如一个纹理图片就是素材的一种。素材管道就很容易理解了,它可以把我们需要的素材导入到我们的游戏中,以XNA能够识别的正确的格式。而且,格式的识别与转换是在编译期完成的。
在新建的XNA项目中,会有一个默认的Content文件夹,通常,我们会把所有的素材放在这个地方,并且根据素材的种类我们会在其下创建一些子文件夹,比如Image子文件夹用来存放所有的图片素材,Audio文件夹用来存放声音素材等。
当一个物件别被识别为素材后,就会有一个唯一的资产名称AssetName来标记它,我们可以通过素材的属性页来查看素材的AssetName属性并修改它。
下面我们可以用素材管理器ContentManager将一个素材导入到游戏中:
texture = Content.Load < Texture2D > ( @"I mage\logo " ); // 在上文提到的LoadContent方法中调用
很多时候,我们的图片需要是透明的,那么如何在SpriteBatch绘制的时候将图片绘制为透明了?有两种方法:要么图片具有透明的背景,要么在制作图片时将需要透明的部分设置为纯正的洋红色(255,0,255)。除此之外,还需要注意,SpriteBlendMode模式必须为AlphaBlend(这也是默认模式,稍后会提到),才能达到我们期望的透明效果。
在进行渲染时,我们还需要注意层深度(Layer Depth)。所谓Layer Depth,指的是你需要将目标Sprite绘制在哪一个深度的层次。默认情况下,SpriteBatch会根据你调用的顺序来决定Layer Depth的相对值,比如,你先调用SpriteBatch绘制Sprite A,然后调用SpriteBatch在同一位置绘制Sprite B,那么,Sprite B就会把Sprite A挡住。如果我们依靠调用SpriteBatch绘制的顺序来决定Sprite的深度,那就太不灵活了,为此,调用SpriteBatch绘制方法时,你可以指定一个代表层次深度的参数(范围0~1)来指示SpriteBatch按照我们希望的顺序来绘制对象。
有了上面的这些基础之后,我们可以详细讲解一下XNA中的渲染过程。每次渲染的过程都类似下面的模式:
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw();
spriteBatch.End();
base .Draw(gameTime);
}
(1)GraphicsDevice.Clear()方法用于清除屏幕背景。
(2)SpriteBatch.Begin()方法用于通知显卡准备绘制场景。
(3)轮流调用SpriteBatch.Draw()绘制所有组件。
(4)SpriteBatch.End()方法用于告诉显卡绘制已经完成。
(5)最后,显卡显示绘制的结果。
SpriteBatch.Begin()方法有一个接受参数的重载:
SpriteBlendMode 参数决定了Sprite的颜色与背景颜色的混合模式,默认为AlphaBlend,我们刚才提到过。
SpriteSortMode参数决定了Sprite的排序模型,即与前面的Layer Depth关联。
SaveStateMode参数用于控制是否保存图形设备的状态。
TransformMatrix参数用于控制旋转、缩放等。
接下来我们看最重要的绘制Draw方法:
第一个Texture2D参数,表示要绘制Sprite的纹理。
第二个Vector2参数,表示要绘制的Sprite的左上角的位置在哪里 。
第三个Rectangle参数,表示要绘制源纹理的哪一个区域,如果要绘制纹理的全部部分,则传null。
第四个Color参数,表示绘制时所调制的颜色通道。
第五个参数rotation,表示要旋转的角度。
第六个参数origin,表示围绕哪个位置进行旋转。
第七个参数scale,表示绘制时要缩放的比例。
第八个参数effects,表示是否进行翻转。SpriteEffects.FlipHorizontally -- 水平翻转;SpriteEffects.FlipVertically -- 垂直翻转。
最后一个参数layerDepth,就是我们前面说过的要在哪一个深度层次绘制对象。
最后,要解释一下,为什么需要在每次Draw调用时,都先调用GraphicsDevice.Clear()方法清除屏幕然后再重新绘制所有物件了?这样性能不是很差吗?而且有时候我们可能只有极小的一部分需要重新绘制。
假设我们每次只重新绘制变动的那一部分,那我们就需要一个复杂的管理器来追踪每一个部分的变动,这样我们才有可能知道要重绘哪一部分。你可以想象一下,这个追踪的过程是相当复杂的。而且还存在这样的情况,那就是当一个Sprite移动时,它后面原先被挡住的Sprite会露出来,而这个Sprite可能又会挡住另外一个Sptrite的一部分,这样一来就更加复杂了。所以,每次重绘整个屏幕并不是一个坏的idea。
今天就讲到这里,下一节我们将讲述与FrameRate相关的知识。