作者:Mike Fleishauer & clayman
本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
Blog:http://blog.youkuaiyun.com/soilwork
clayman_joe@yahoo.com.cn
special thanks to Mike Fleishauer ^_^
在第一章里,我们介绍了关于
XNA
的一些基础知识。但坦白的说,至今为止,我们还没有编写任何代码,而且只创建了一个单调的蓝色屏幕。
这一章,我们将尝试在屏幕上绘制一些东西,简单的
2D
图形。虽然
2D
游戏的时代已经渐渐远去,但即使你编写一个全
3D
的游戏,也不得不处理处理一些
2D
图形,比如简单的选项菜单、
HUD
(
head up displays
)等等。好了让我们开始把。
打开上一章创建的项目,当然,你也可以创建一个新
XNA
项目。把解决方案改名为“
Chapter2”,把工程和Game1.cs
都重命名为“
Sprite
”,当弹出确认更改文件名的对话框时,点击确认。
接下来,我们将在屏幕上绘制一张
2D
图片。但在这之前,需要介绍一点关于
Sprite
的概念。
什么是
Sprite
?
Sprite
,也称为精灵,是一个直接绘制到屏幕上的
2D
图形。在传统的
2D
游戏中,你所看到的一切几乎都是
sprite
。但在
3D
游戏中,比如
Halo
,
sprite
逐渐演变为了用于增加
3D
图形视觉效果的纹理。在讨论
3D
图形时我们会详细讲解它。现在,简单的把
sprite
认为是
2D
图形就可以了。
继续,我们将把一些外部资源添加到工程中,为了方便管理,统一把资源放到一个单独的文件夹中。在
Solution Explorer
邮件点击
Add->New Floder
,命名为
Graphics
。接下来,邮件点击新创建的文件夹
Add->Existion Item….
在弹出窗口中,导航到安装
MC2
源代码的目录下,在
/Source/Data/Art
中,选择
mcl_splashscreen_planet_2.tga.
文件。(当然,可以选择一张任何你喜欢的图片)。
接下来,编写代码:
namespace
Chapter2
{
partial class Sprites : Microsoft.Xna.Framework.Game
{
private Microsoft.Xna.Framework.Graphics.SpriteBatch _sb;
private Microsoft.Xna.Framework.Graphics.Texture2D _sprite;
public Sprites()
{
InitializeComponent();
_sb = new SpriteBatch(this.graphics.GraphicsDevice);
_sprite = Texture2D.FromFile(this.graphics.GraphicsDevice, "../../Graphics/mcl_splashscreen_planet_2.tga");
}
protected override void Update()
{
float elapsed = (float)ElapsedTime.TotalSeconds;
UpdateComponents();
}
protected override void Draw()
{
if
(!graphics.EnsureDevice())
return;
graphics.GraphicsDevice.Clear(Color.Black);
graphics.GraphicsDevice.BeginScene();
_sb.Begin();
_sb.Draw(_sprite, new Vector2(0.0f, 0.0f), Color.Red);
_sb.End();
DrawComponents();
graphics.GraphicsDevice.EndScene();
graphics.GraphicsDevice.Present();
}
}
}
(加粗部分为我们添加的代码)
这些代码是什么意思呢?
首先,我们为
Sprite
类添加了两个全新的成员:
private
Microsoft.Xna.Framework.Graphics.SpriteBatch _sb;
private
Microsoft.Xna.Framework.Graphics.Texture2D _sprite;
_sb
是一个SpriteBatch对象。SpriteBatch对象代表了一批sprite,并且将在同样的状态设置下,绘制他们。大多数情况下,几乎所有的sprite都在同一个批次中。
_sprite
实际上是一张2D的纹理。它代表了一张将要绘制到屏幕上的图片。我们稍后将讨论不同类型的纹理,现在,只需知道2D纹理储存了在X和Y方向上,每个像素的颜色信息。也可以就把Texture2D认为是一张图片。XNA直接支持jpg,tga,dds,bmp,png格式的文件作为纹理。
_sb = new SpriteBatch(this.graphics.GraphicsDevice);
使用GraphicsDevice对象作为参数,实例化SpriteBatch。这里,参数的含义表示以后将用哪一个(一个程序中可以有多个GraphicsDevice)GraphicsDevice对象绘制_sb。
_sprite = Texture2D.FromFile(this.graphics.GraphicsDevice, "../../Graphics/mcl_splashscreen_planet_2.tga");
这行代码把图片加载到内存中,实例化
Texture2D
对象。同样把当前的
graphics device
和图片的路径作为参数。如果在给定路径没有找到所要的图片,那么这个方法将抛出一个异常。
graphics.GraphicsDevice.Clear(Color.Black);
把屏幕清理为黑色。上一节已经介绍过如何使用这个方法。现在我会告诉你为什么需要调用这个方法。如果把渲染比作绘图,那么显存就是我们的画板,通常把用于绘图的显存称为帧缓冲,如何不清理帧缓冲,那么上次在帧缓冲中绘制的图形仍然会保留在其中,并且这些数据处于一种不确定的状态。假设我们下次只在屏幕的左上角绘制图形,那么显示时,除了进行绘制的区域,其他部分可能会显示一些随机数据,相当于我们在一块绘制了大量图形的旧画板上绘图。因此,需要用
Clear
方法对帧缓冲进行初始化,填充为某个我们希望的背景颜色。
_sb.Begin();
_sb.Draw(_sprite, new Vector2(0.0f, 0.0f), Color.Red);
_sb.End();
和之前提到的
BeginScenne
和
EndScene
一样,
_sb.Begin
和
_sb.End
方法告诉图形设备我们将要绘制
sprite
,所有绘制
sprite
的代码都必须在这两个方法之间。
Draw
方法是真正绘制图形的地方。这里的参数告诉显卡从坐标位置为(
0
,
0
)的地方开始绘制
sprite
。注意,绘制
sprite
的,所使用的是屏幕坐标系,这意味着屏幕中的每个像素对应一个(
x
,
y
)
,
屏幕左上角的坐标总是(
0
,
0
),而右下的坐标则取决于屏幕分辨率,如果分辨率为
1024 x 768
那么右下的坐标就是(
1024
,
768
)。绘制
sprite
的位置应该在这两个坐标之间。
运行程序看看吧:
虽然依旧很单调,但总是有了进步。
这里你可能会有一些问题:为什么原来蓝色的天空,现在“燃烧”了起来。
注意看绘制
sprite
的代码,最后一个参数表示了绘制
sprite
时的色调。如果我们把它改为
Color.White
那么将获得和原图一样的效果。你看,使用
XNA
轻易就能实现一些特效。
再绘制几个
sprite
现在我们有
4
个相同大小的
sprite
了。你已经掌握了
2D
绘图的基础,足够完成一个
2D
游戏的背景渲染。再次提醒,所有的
2D
绘图操作都因该在
Begin()
和
End()
方法之间,而
SpriteBatch
方法调用又必须在
BeginScene()
和
EndScene()
方法之间。简单的说,应该按照以下顺序:
--
开始渲染
3D
图形
l
渲染
3D
场景
l
开始渲染
2D
图形
n
渲染
2D
图片
l
结束
2D
渲染
--
结束
3D
渲染
需要记住,
2D
图形的渲染和绘制他们的顺序有关系。在叠加区域,先渲染的图形总是会被后渲染的图形挡住,和在普通画布上绘图的原理一样。这也带领我们进入下一个话题,透明。
Transparent Blits
首先,如果你要问我
Blits
是什么含意,那么我要告诉你,实际上你不必知道它是什么意思
=.=
。
Blit
的含义来自于
BLT
,表示
Block Transfer
,意思是把一个平面的一部分复制到另一个平面。好了,关键的问题就在于我们如何把图片中,不透明的部分复制到已有图片上。
为了渲染一个带透明效果的
sprite
,先来做一些辅助工作。首先,打开
windows
中的绘图板,任意绘制一个图形:
哈哈,我绘制图形的能力确实很惊人,不是吗
^_^
。接下来,再创建一张同样大小的图片。上一张图片是我们希望显示的部分,而现在这张图片则作为它的透明遮罩:
一般情况下,遮罩里白色部分是不透明的,而黑色部分则表示透明区域。把两张图片分别保存为
uglyStar.jpg
和
uglyStarMask.jpg
。为了方便使用,把他们都添加到
Grahics
文件夹中。
现在打开
DirectX SDK
中的
DxTex.exe
程序(它位于
sdk
安装路径的
Utilities/Bin/x86
件夹下)。选择
File->New Texture…
,在弹出的窗口中,把纹理尺寸设置为
32x32
,把
Surface/Volume
格式设置为
Unsigned 32-bit: A8R8G8B8
,如图所示:

选择“
Open onto this surface”,
打开我们之前创建的红色星星
uglyStar.jpg

选择“
Open onto Alpha Channel of this Surface”
,打开
uglyStarMask.jpg

最后把文件保存为
star.dds
,并添加到
Graphics
文件夹。
好了,现在有了一张带透明通道的图片,如何使用他呢。把它绘制到之前的天空上吧。添加如下代码:
………………….
private
Microsoft.Xna.Framework.Graphics.Texture2D _spriteStar;
…………………
.
public
Sprites()
{
………………………
_spriteStar = Texture2D.FromFile(graphics.GraphicsDevice, "../../Graphics/star.dds");
}
protected
override void Draw()
{
……………
..
graphics.GraphicsDevice.BeginScene();
_sb.Begin(SpriteBlendMode.AlphaBlend);
_sb.Draw(_sprite, new Vector2(0.0f, 0.0f), Color.White);
_sb.Draw(_sprite, new Vector2(_sprite.Width, 0.0f), Color.White);
_sb.Draw(_sprite, new Vector2(_sprite.Width, _sprite.Height), Color.White);
_sb.Draw(_sprite, new Vector2(0.0f, _sprite.Height), Color.White);
_sb.Draw(_spriteStar, new Vector2(50.0f, 50.0f), Color.Yellow);
_sb.End();
……………………………
}
运行程序,你因该可以看到下图所示的结果
:
注意到我们只显示了红色的部分没有?这都是透明遮罩的功劳。简要来说,你使用
DirectX
纹理工具,告诉了图片哪些部分需要渲染,哪些部分是透明的,注意这里使用了
dds
格式的文件,而不是
bmp
格式。
你因该对这行代码比较感兴趣:
_sb.Begin(SpriteBlendMode.AlphaBlend);
使用
AlphaBlend
作为
SpriteBlendMode
参数,会告诉
XNA
将要渲染一些带透明效果的图片。还记得我先前说过“
大多数情况下,几乎所有的sprite都在同一个批次中”吗,好吧,我撒谎了-_-b。Alpha混合通常需要进行额外的计算,因此,应该把需要进行Alpha混合的sprite单独作为一个批次。一个批次用来绘制(静态)背景图片,另一个用来绘制带透明效果的前景物品。作为一条规则,你应该总是把需要alpha混合的sprite作为一个批次:
using
System;
using
System.Collections.Generic;
using
Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Audio;
using
Microsoft.Xna.Framework.Components;
using
Microsoft.Xna.Framework.Graphics;
using
Microsoft.Xna.Framework.Input;
using
Microsoft.Xna.Framework.Storage;
namespace
Chapter2
{
partial class Sprites : Microsoft.Xna.Framework.Game
{
private Microsoft.Xna.Framework.Graphics.SpriteBatch _sbBackground;
private Microsoft.Xna.Framework.Graphics.SpriteBatch _sbForeground;
private Microsoft.Xna.Framework.Graphics.Texture2D _sprite;
private Microsoft.Xna.Framework.Graphics.Texture2D _spriteStar;
public Sprites()
{
this.AllowUserResizing = true;
this.IsMouseVisible = true;
InitializeComponent();
_sbBackground = new SpriteBatch(this.graphics.GraphicsDevice);
_sbForeground = new SpriteBatch(this.graphics.GraphicsDevice);
_sprite = Texture2D.FromFile(this.graphics.GraphicsDevice, "../../Graphics/mcl_splashscreen_planet_2.tga");
_spriteStar = Texture2D.FromFile(graphics.GraphicsDevice, "../../Graphics/star.dds");
}
protected override void Update()
{
float elapsed = (float)ElapsedTime.TotalSeconds;
UpdateComponents();
}
protected override void Draw()
{
if (!graphics.EnsureDevice())
return;
graphics.GraphicsDevice.Clear(Color.Black);
graphics.GraphicsDevice.BeginScene();
_sbBackground.Begin();
_sbBackground.Draw(_sprite, new Vector2(0.0f, 0.0f), Color.White);
_sbBackground.Draw(_sprite, new Vector2(_sprite.Width, 0.0f), Color.White);
_sbBackground.Draw(_sprite, new Vector2(_sprite.Width, _sprite.Height), Color.White);
_sbBackground.Draw(_sprite, new Vector2(0.0f, _sprite.Height), Color.White);
_sbBackground.End();
_sbForeground.Begin(SpriteBlendMode.AlphaBlend);
_sbForeground.Draw(_spriteStar, new Vector2(50.0f, 50.0f), Color.White);
_sbForeground.End();
DrawComponents();
graphics.GraphicsDevice.EndScene();
graphics.GraphicsDevice.Present();
}
}
}
好了,第二部分到次结束,下一章,我们将让
sprite
动起来~~。