XNA Kick Start (五)

第三章 进入3D世界

本文版权归我所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,欢迎大家和我交流。
作者:clayman
Blog:
http://blog.csdn.net/soilwork
clayman_joe@yahoo.com.cn


 

    上一章,我们学习了如何使用XNA显示简单图形。你可能已经开始抱怨我讲了两章,却还停留在2D图形上,而这种简单任务用GDI+也能轻易完成。本章我们就将进入3D世界,编写简单三角形的三维版本。此外,为了简化编写XNA程序的初始化操作,不再使用“原始”的winForm模板,而直接使用现代化的Game类。

 

 

一.       理解Game

       前一章,我从一个基本的winForm程序开始,展示了如何把一个普通窗口程序,扩展为XNA程序。当然,为了简单易懂,实际上还省略了很多重要的步骤。总之,这些代码是编写任何XNA程序都需要重复的,因此,为了提高开发效率,同时让初学者更容易学习和使用XNAXNA提供了Game类。

       Game位于Microsoft.Xna.Framework名称空间中,实现了一个完整的游戏框架。简单的游戏框架应该包含些什么呢?一般来说,至少应该提供以下功能:管理GrahicsDevice,资源管理,提供游戏循环(main loop),绘图。

       现在就动手创建一个标准XNA应用程序看看吧。打开GSEGame Studio Express),在新项目模板中,可以看到XNA程序模板分为WindowsXbox360两种,目前,我们只使用Windows平台下的模板,即Windows Game。注意,在Windows上是无法调试Xbox项目模板的。

       创建项目之后,可以看到模板为我们生成了大量代码。模板程序继承于Game,除添加了两个成员,重载了一些基类方法外,并没有多少新东西。直接运行程序,就能看到一个蓝色窗口了。依次来看模板中的成员和方法。

       GraphicsDeviceManager封装了我们熟悉的GraphicsDevice,并把它作为自身属性之一。你可能会问在设计类库时,为什么不把GraphicsDeviceManager也作为Game类的成员之一,而是在模板中添加它。实际上在beta1时,GraphicsDeviceManager确实是Game的成员之一。乍一看,这样做也许更加方便,然而,却增加了代码耦合,你不得不通过Game才能创建和访问GraphicsDevice

       GraphicsDeviceManager会自动使用默认参数创建GraphcsDevice,并提供了大量属性和方法对它进行管理。

       ContentManager我们已经使用过,这里不再重复。接下来的Initialize()方法也没有什么特殊的地方。

       Game会在适当的时候调用LoadGraophicsContentUnloadGraphicsContent方法加载和卸载资源,我们所要做的就是把所要处理的代码放到这两个方法里。细心的你可能注意到了,这两个方法还接收一个bool值用来控制资源管理,暂时忽略它,在后面讨论资源管理时会有详细讲解。

       Update()方法就是之前提到的游戏循环。程序在开始运行之后将不停调用这个方法,来响应各种消息。大部分的游戏逻辑都应该位于这里,响应用户输入,更新游戏状态等等。

       至于Draw()方法,你应该已经比较熟悉了。

 

使用Game编写程序

 

       让我们把前一章的程序移植到这个新框架中吧。

        首先,添加所需成员:

 

     private VertexPositionColor[] verts;

     private VertexDeclaration decl;

     private Effect effect;

 

       InitTriangle()方法添加到程序中,并在Initialize()中调用它,初始化顶点数组。注意要把原来程序中所有的graphicsDevice变量改为graphics.GraphicsDevice

       把先前编写的simpleTriangle.fx文件添加到工程中,修改LoadGraphicsContent()方法:

       protected override void LoadGraphicsContent(bool loadAllContent)

     {

        if (loadAllContent)

        {

            effect = content.Load<Effect>("simpleTriangle");

            effect.CurrentTechnique = effect.Techniques["render"];

         }

     }

 

       最后修改Draw()方法

 

       protected override void Draw(GameTime gameTime)

    {

            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            graphics.GraphicsDevice.VertexDeclaration = decl;

            effect.Begin();

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)

            {

                pass.Begin();

                graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, verts, 0, 1);

                pass.End();

            }

            effect.End();

            base.Draw(gameTime);

     }

      

       这里不需要调用present方法,基类的Draw会完成这个任务。

       现在运行程序,又可以看到先前可爱的三角形了。你看,使用Game类,我们节约了大量代码。

 

深入Game

 

       除上面提到的内容外Game以及GraphicsDeviceManager还有很多重要的方法和属性。

       前面我们提到过XNA程序开始运行之后就将进入主循环,不停调用某些方法,以便让整个游戏世界“活动”起来。那么是否可以控制整个世界活动的快慢呢?当然是可以的。整个循环分为定时和非定时两种模式。对于定时模式来说,程序每过一段固定的时间(默认值是1/60s)就调用Tick方法,Tick更新程序时间,然后调用Update()Draw()方法。而非定时模式下,上一次Tick方法调用返回之后,就立刻再次调用它。在这种模式下,由于每次UpdateDraw方法所完成的任务不同,因此可能会造成程序的活动快慢不一致。用IsActive属性来选择循环模式,TargetElapseTim则用来设置定时模式下,循环的更新时间间隔。

       运行程序的时候你可能已经发现了,默认情况下XNA程序不会显示鼠标。这样做的好处是当你想用一张长矛的图片来代替光标时,只需要根据鼠标位置直接绘制它,而不必再考虑如何隐藏windows原来丑陋的光标。当然,如果你嫌这样太麻烦,可以修改IsMouseVisible属性以显示Windows光标。

       更改游戏窗口大小也是必不可少的功能。可以用Window属性下的AllowUserResizing属性来控制这个功能。此外,窗口名称也由Window属性来设置。把以下代码添加到Initialize方法中:

 

       this.IsMouseVisible = true;

     this.Window.AllowUserResizing = true;

      

       再次运行程序,除了正确显示光标并且能随意缩放以外,还可以看到在缩放窗口之后,Game会智能的自动调整后备缓冲的大小,很方便,不是吗?

       GraphicsDeviceManager同样也有很多值得一提的地方。通常GraphicsDeviceManager会用默认参数创建GraphicsDevice,但要是默认参数不能满足要求时,如何控制GrahicsDevice的创建呢,游戏中总是允许玩家选择不同的显示模式。GrapohicsDeviceManager在创建GraphicsDevice之前会先触发一个名为PreparingDeviceSettings的事件,它接受 EventHandle类型的委托,允许我们通过它来设置创建GraphicsDevice的属性。例如:

 

       graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>graphics_PreparingDeviceSettings);

 

     void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)

     {

         e.GraphicsDeviceInformation.CreationOptions = CreateOptions.SoftwareVertexProcessing;

         e.GraphicsDeviceInformation.DeviceType = DeviceType.Reference;

         e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.None;

     }

 

       通过GraphicsManager还可以设置是否全屏显示,后备缓冲的格式以及大小,是否垂直同步等等。需要注意,如果在创建了GraphicsDevice之后修改这些属性,那么需要使用ApplyChange()方法重置设备,响应修改,否则修改是无效的。

 

进入三维世界

 

坐标系统

       欢迎来到三维世界。对于人类来说,3D世界也许比2D世界要容易理解,我们本身就生活在3D世界中。而在计算机图形世界中却不是这样。

       与其说来到3D世界,不如说构造3D世界,首先需要确定的就是坐标系统。

       图形学中常见的坐标系统分为两种,左手系和右手系。他们都是三维笛卡尔坐标系,从下图可以看到,两者的区别仅仅在于Z轴的朝向不同。在DirectX或者MDX中,默认使用的坐标系是左手系,而在XNA以及DirectX 10中,则统一使用右手系,也就是说从屏幕指向你自己的方向是Z轴正向。在游戏中,通常把构造整个世界所用的坐标系统称为世界坐标。

       显示器也有自己的坐标系统。显然这是一个二维坐标系统。它的原点(00)位于显示器正中,面对显示器右手方向为X轴正向,垂直向上为Y轴正向,屏幕左下角的坐标为(-1-1),右上角坐标为(11)。所有最终显示在屏幕上的物体坐标都必须归一化到[-11]之间。回顾前面我们所编写的三角形顶点坐标,可以看到顶点位置都是在这个范围之内的。

       好了现在问题来了,如何把3D坐标系中的物体变换到屏幕坐标中呢?

 

坐标变换

 

       我们使用术语坐标变换来表示把一个顶点在某一坐标系中的位置换算为在另一坐标系中位置的数学计算过程。对于线性系统来说,坐标变换主要指矩阵乘法。哦,这里需要你有一点点线性代数的知识了。

       人在观察世界时,其实就是把3D世界中的物体投影到2D的视网膜上来呈像。如下图所示,在前裁剪平面(front clipping plane)和后裁剪平面(back clipping plane)之间的棱台是从某个角度观察3D世界时的可视区域,习惯上把他称为视见体(view frustum)。其间的所有物体最终都都投影到裁剪平面作为2D图形显示。为了定义某个观察角度时的视见体和最终投影,需要完成三次坐标变换。

       第一步称为世界坐标变换(world transform)。注意,我前面就提到过我们是在构造3D世界,世界坐标变换所完成的任务就是把某个物体放置到3D虚拟世界中的某个位置。你也许有些疑惑,为什么需要使用坐标变换,而不直接指定某个物体在世界坐标中的位置呢。对于我们用程序生成的几何体来说,确实可以在创建顶点时就指定它在世界坐标中的位置,从而省略这一步变换。但是考虑如果需要移动某个物体怎么办呢?总不可能每次重新编写顶点位置吧。此外,对于从外部导入的模型来说,所有顶点位置都是按照模型的局部坐标系统(以模型中心为原点)来保存的。

       世界坐标完成了构造3D世界的任务,把每个物体放到相应的位置。接下来,作为观察者,我们需要选择观察这个虚拟世界的角度。显然,从X轴正向观察场景和从Y轴正像观察,看到的物体是不同的。我们把这一步变换成为观察变换(view transform)。它将把物体在世界坐标中的位置变换到以观察者为原点的坐标系中。

       单单知道从什么位置看是不够的,我们还必须知道观察者的视野范围有多大,能看多远。这就需要使用投影变换来完成了,它把3D世界中的物体投影到2D屏幕上。

~~~~~~~~~~~~~~~~~~~~~~~第三章未完待续~~~~~~~~~~~~~~~~~~~~~~~
可以到这里下载本节源码.

ps:终于上班了,忙啊,努力工作~~~~~~



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值