
图2-6
如何把图片绘制出来,并且能缩放,旋转,透明等处理?XNA里的SpriteBatch类完全就可以满足我们的要求。
比如,在图2-6里我们把上面加载好的loadingTexture对象绘制在屏幕左上角,也就是坐标 Vector2(0,0);
spriteBatch.Begin();
spriteBatch.Draw(loadingTexture,new Vector2(0,0),Color.White);
spriteBatch.End();
当然,我们做游戏的时候,一个场景里肯定不可能只使用一张图片,那么就需要我们同时绘制多个纹理到屏幕上。不过不用担心,一个SpriteBatch对象可以多次调用Draw方法来绘制不同的Texture2D对象。
spriteBatch.Begin();
spriteBatch.Draw(loadingTexture,new Vector2(0,0),Color.White);
spriteBatch.Draw(playerTexture,new Vector2(260,100),Color.White);
spriteBatch.End();
这段代码绘制的结果如图2-7,这样我们就把一个弓箭手绘制在城堡上面:

图2-7
在绘制多个图片的时候可能出现纹理堆叠的情况,在XNA里最后绘制的默认在最上层,当然也可以指定绘制的层次。这些高级的Draw方法的应用,就需要深入我们了解SpriteBatch对象。
SpriteBatch对象的Draw方法有不同的重载,其中最复杂的如下:
public void Draw (Texture2D texture,Vector2 position,Nullable<Rectangle> sourceRectangle,Color color,float rotation,Vector2 origin,Vector2 scale,SpriteEffects effects,float layerDepth)
这个Draw方法的第三个参数sourceRectangle为制定绘制纹理的那个矩形部分。这个参数非常有用,比如游戏编程里我们为了减少图片的数量和大小,我们总是把很多帧动画绘制在一张图片上如图2-8,如果我们要绘制单帧图片的话,我们就需要制定绘制这个图片的单帧大小的矩形范围。当然如果想要把图片完整的现实出来,只需要把这个参数设置为null就行了。

图2-8
还有其他高级用法,比如:旋转某个纹理。
旋转
那么我们就需要使用rotation 参数旋转一个图像。你需要使用弧度值指定这个旋转角度,所以如果你想让图像顺时针旋转20度,你可以使用MathHelper.ToRadians(20)。
需要注意的是旋转一个图片,我们总是要指定旋转中心点的,origin这个参数就是旋转中心点。当然origin不光是在旋转时有用。origin可以指定你想让图像上的哪个点位于屏幕上你在position参数中指定的位置上。例如,如果你将position,origin两个参数都指定为(0,0),那么图像的左上角将位于屏幕的左上角,如图2-9:

图2-9
如果一张80
× 60大小的图像,你将屏幕位置指定为(0,0),origin指定为(40,30),那么图像的中心点将位于屏幕的左上角,如图2-10所示
。
图2-10
如果两个参数都是(40,30),那么图像的中心点将位于屏幕的(40,30)位置。这时的结果与图2-9一样。
在图片旋转的时候如果把(0,0)为图像的origin,这个图像会围绕这个点旋转,如图所示。如果你指定(32,32)为图像的origin,这个图像会围绕它的中心点旋转,如图2-11:

图2-11
缩放某个纹理也是我们常见的。
缩放
如果你想放大/缩小图像,可以设置参数scale。因为这个参数是一个Vector2,你可以对图像的水平方向和竖直方向施加不同的缩放值。例如,设置为(0.5f, 2.0f)将会使宽度变为原始宽度的一半,高度变为原始高度的2倍。如图2-12:
图2-12
其他还有
镜像
参数effects让你可以水平或竖直翻转图像。通过flags,你可以使用SpriteEffects.FlipHorizontally|SpriteEffects.FlipVertically同时进行这两个操作,这和将图像旋转180度的效果是相同的。
层深度
最后一个参数layerDepth让你可以指定图像位于哪个层,当你想将多个图像在有重叠时层的概念是很有用的。
需要注意的是
1. layerDepth是一个介于0.0-1.0之间的float值,一般默认为0.0,如果是0.0那么就指定系统最后绘制,如果是1.0那么指定系统最先绘制。通俗的来讲,layerDepth值越小就越在其他纹理上面。
2.
需要使用spriteBatch.Begin的一个重载函数:
SpriteBatch.Begin(SpriteSortMode.BackToFront,BlendState.AlphaBlend);
2.2
使用ScreenManager管理游戏场景
在一个稍微复杂的游戏里,我们都需要用到场景来处理。什么是场景,简单的来讲,比如一个过关游戏,第一关就是一个场景,第二关就是另外一个场景。每个场景里有不过的背景图片,不同的精灵角色,有不同的关卡设计。场景里的资源如何展示,从一个场景到另一个场景如何切换,这些我们需要写一个专门的类ScreenManager来处理。
微软WP7开发者官方网站http://create.msdn.com上就有XNA的小游戏示例代码,里面就有这样的ScreenManager类,下面我们来简要分析下这个类,为后面我们设计游戏菜单打下基础。我省掉了中间的一些代码,大家可以看函数来理解这个类的作用。
namespace WPGame2D.Screens
{
/// <summary>
///画面管理类,负责管理多个画面
/// </summary>
public class ScreenManager
: DrawableGameComponent
{
public Camera2D Camera;
public ContentManager ContentManager;
/// <summary>
///所有用到的文字资源
/// </summary>
public SpriteFonts SpriteFonts;
……..
private
List<GameScreen>
screens = new List<GameScreen>();
private
List<GameScreen>
screensToUpdate = new List<GameScreen>();
private
SpriteBatch spriteBatch;
……..
/// <summary>
///构造函数
/// </summary>
public
ScreenManager(Game game)
: base(game)
{
TouchPanel.EnabledGestures
= GestureType.None;
ContentManager = game.Content;
ContentManager.RootDirectory = "Content";
}
/// <summary>
///游戏通用默认SpriteBatch对象。每个场景都同享它,缓存了每个场景所用到的文件资源
/// <summary>
public SpriteBatch SpriteBatch
{
get
{ return spriteBatch; }
}
/// <summary>
///初始化ContentManager对象
/// </summary>
public override void
Initialize()
{
SpriteFonts = new SpriteFonts(ContentManager);
base.Initialize();
isInitialized = true;
}
public void ResetTargets()
{
transitions.Clear();
}
/// <summary>
///加载游戏资源
/// </summary>
protected
override void
LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
blankTexture =
ContentManager.Load<Texture2D>("Common/blank");
Camera = new
Camera2D(GraphicsDevice);
// 让每个场景加载所用的资源
foreach
(GameScreen screen in
screens)
{
screen.LoadContent();
}
}
/// <summary>
/// 卸载所用的游戏资源
/// </summary>
protected
override void
UnloadContent()
{
foreach
(GameScreen screen in
screens)
{
screen.UnloadContent();
}
}
/// <summary>
/// 让每个场景执行update操作,执行游戏逻辑-
/// </summary>
public override void Update(GameTime gameTime)
{
input.Update();
Camera.Update();
screensToUpdate.Clear();
foreach
(GameScreen screen in
screens)
screensToUpdate.Add(screen);
………..
}
/// <summary>
///开始渲染每个场景
/// </summary>
public override void Draw(GameTime gameTime)
{
int
transitionCount = 0;
foreach
(GameScreen screen in
screens)
{
……..
screen.Draw(gameTime);
…….
}
……..
}
/// <summary>
/// 添加一个新的场景到管理器中
/// </summary>
public void AddScreen(GameScreen
screen, PlayerIndex? controllingPlayer)
{
…….
if
(isInitialized)
{
screen.LoadContent();
}
screens.Add(screen);
……….
}
/// <summary>
/// 移除指定的场景。不过通常调用GameScreen.ExitScreen来代替这个方法。这样比直接调用RemoveScreen好,中间有一个过渡过程。
/// </summary>
public void RemoveScreen(GameScreen
screen)
{
if
(isInitialized)
{
screen.UnloadContent();
}
……..
}
}
}
分析这个类,我们可以看到该类继承于DrawableGameComponent, 从字面意思我们就可以理解到这是一个在XNA里可绘制出来的组件的基类。我们把场景继承于这个类,是因为每一个Screen里的东西基本都是可绘制的。我们在这个类上按F12,可以看到类的结构如图2-13:

图2-13
比如我们从一个场景到另一个场景,我们就可以用到RemoveScreen和AddScreen方法。
需要注意的是,ScreenManager类里含有Draw方法是实现基类DrawableGameComponent里定义的方法。在这个方法里我们负责把场景里的元素绘制出来。
ScreenManager类里还涉及到GameScreen类,我放到XNAGameSample2里提供给大家下载阅读,在下一章里,我们会介绍菜单的制作,配合2.3章节的ScreenManager类和GameScreen类实现点击菜单项就完成了不同游戏场景的切换。