[Clayman的专栏]中文XNA教程(二)—— Into the 2D World 

2006-12-21 12:33 发布

3406 0 0


我们会设置一个[Clayman的专栏],专门发布Clayman写的XNA文章。

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

楼主新帖

TA的作品 TA的主页
B Color Smilies

你可能喜欢

[Clayman的专栏]中文XNA教程(二)—— Into the 2D World 
联系
我们
快速回复 返回顶部 返回列表