作者:Mike Fleishauer & clayman
本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
Blog:http://blog.csdn.net/soilwork
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();
}
}
}