3.2管理内容管道

      正如您在第一章中学到的,内容管道主要用来导入游戏资源,比如texture、模型、shader和声音文件。和Visual Studio(或XNA Studio)使用的其他文件不同的是,内容文件不仅仅是被添加到项目中就没事了,它们会被处理并编译成二进制内容文件,然后在游戏中加载它们(如图3-1所示)。
图3-1

3-1

     过去,游戏编程人员必须得自己写内容导入器加载游戏内容数据或使用一种受支持的格式文件,比如DirectX中的.x格式的模型文件。但通常可以使用的格式要么不多,要么太慢,要么当需要给游戏添加新特性时又太死板,这就是为什么几乎每一个商业游戏都使用自己的文件格式及其自定义的处理逻辑。这有一个好处,就是只有游戏的开发人员才知道内部格式的设计,他们可以随时对它进行扩展或修改它。但用这种方式来获取3D内容却很麻烦,要做很多工作。加载texture通常并没有那么复杂,因为有很多现成的库可以使用,即使是写您自己的文件格式,也基本上只是包含一些使用2432位颜色值的像素而已。但如果您尝试使用压缩或者使用硬件压缩(hardware-compressed)的texture(比如DXT格式)就有点困难了,不过DirectX有一套非常好的指令系统,里面包含很多有用的方法来帮助您处理这种情况。

     另一方面,加载3D模型数据更加复杂,尤其在XNA中。因为在XNA中,您不仅要处理几何数据(geometry data),还要使用shader来渲染3D对象,然后还得使用material数据通知shader使用什么样的颜色、texture以及参数。在DirectX中大多数教程和示例都使用.x文件格式,但这种格式对很多项目都不适用。Especially if you use normal napping and require the geometry data to contain tangents, the .x file format will not be very helpfulYou would have to generate tangents in your application and work around problems this might introduce。例如,我曾经做过的一个游戏Rocket Commander就存在这样的问题,它需要复杂的模型加载过程和切线重建过程。

     对于其它的游戏数据,比如声音文件(.wav)、shaders.fx)或者是自定义的数据(像.xml文件),它们的加载过程就比较容易理解,因为您的游戏或者您正在使用的开发框架已经提供了足够的类来快速地加载任何数据。但是,如果您把具有相同内容的游戏放到其他平台上运行,可能会遇到很多问题。例如,在Windows平台上您使用ACPCM声音文件,使用编译过的Pixel Shader 1.1文件或者.jpg文件作为texture,但在Xbox 360上却不支持ACPCM,在Xbox上声音类型要么是PCM,要么是自定义的XMA格式。同时处理shader的代码也要符合Xbox 360能接受的格式,而且加载texture的工作方式也可能不一样。如果将来支持更多的开发平台,那么这个问题会变得更加复杂。

     在XNA中,为了简化游戏内容的加载,您可以直接把原始的内容文件添加到XNA Studio项目中,然后它们会自动被处理,编译成适合当前选定平台的输出格式。例如,您的声音文件可以根据您创建的XACT项目设置来进行处理——虽然得到的输出和压缩格式不同,但wave bank中所有原始声音文件都是相同的,这样就可以只在一个地方进行更新。这个想法很不错,不过它要求每一个原始内容文件格式都要被支持,但这个又很不现实,因为可以使用的文件格式类型太多,根本不知道哪些会被用到。例如,您的图形设计人员可能使用Photoshop并保存成.psd文件,而其他团队可能使用Gimp或者Paint-Shop,或者就使用Windows自带的画图工具,而且还有成千上百个其它画图工具和程序可以使用。另外,您也不知道到底要提取哪些数据,很多格式都“多层”结构,设计人员可能想让每一层都可用,或者干脆把所有东西都合并起来。

受支持的文件格式

      不要任何东西都用,您要选用一种可用的处理器以及受支持的文件格式,如果需要的话您还可以写自己的内容处理器(详见第七章):
  • Texture格式:.dds.png.jpg.bmp.tga ——基本上您可以使用.Net Framework或者DirectX加载任何东西。为了获得最佳质量,输入格式通常不要进行压缩。高压缩的.jpg文件非常不好,尤其是使用DXT对它们进行再压缩。不过,您可以对输入文件应用恰当的输出压缩(比如应用DXT压缩的dds文件),然后在内容文件属性中进行相同的设置(在我的项目中都是采用这种方式来处理大多数内容文件)
  • 声音和音乐格式:.xapXACT音频项目)——XACT中您只能导入.wav格式文件,但可以设置很多特效和参数,并选择适用于Windows平台的ACPCM压缩格式或者适用于Xbox 360平台的XMA格式。详见第九章。
  • 3D模型格式:.fbx.x模型文件——DirectX SDK中的很多示例和教程都使用.x格式。DirectX提供了很多现成的类来方便地加载.x文件。大多数.x文件应该也可以在XNA中正常使用,主要区别是DirectX.x文件通常不使用shader,而XNA却总是使用。要从3D Studio Max中导出模型文件,可以使用Panda DirectX导出器,可以在这里找到Panda DirectX导出器插件:http://www.andytather.co.uk/Panda/directxmax.aspx
    .fbx是一种比较新的格式,最初是由Alias3D模型工具Maya的开发商)开发的,后来Alias又被Autodesk3D Studio Max和很多CAT程序的开发商)收购了。.fbx意思是“Universal 3D Asset Exchange”,它是Autodesk公司免费的跨平台内容交换格式,新版3D Studio Max 9Maya都支持它。另外还有很多其它的3D内容制作程序也支持.fbx格式的导入与导出。在XNA中这种格式特别适用于动画模型、骨骼和皮肤。它还支持更多的选项,但对shader却不好使,因为无法导出materialshader设置。
    .fbx格式的另一个问题是缺少格式规范,而且要使用SDK还必须得加入Autodesk Developer Network,每年还要交纳会员费。如果您看看其它交换格式,比如Collada,会发现它们更开放,也更容易扩展。同时由于它们不是由一家公司开发的,经常会增加新的内容和特性。过去Collada不支持shader设置,但现在的版本对3D数据的支持非常好,可以导出tangentshader设置以及游戏需要的任何东西。不幸的是,XNA不支持Collada格式,我也无法说服Microsoft来支持它。最初XNARacer使用的模型、track以及landscape都是使用Collada,但后来都被换成其他的格式以支持内容管道。
  • 其他格式——您可以导入自定义文件格式,比如XML文件、二进制文件,甚至写自己的自定义处理器。如果您有一个大的项目而且值得付出很多努力,或者您需要使用一种特殊的模型格式而XNA又不支持,那使用自己的自定义文件格式会很有帮助。例如,游戏Quake3/Doom3使用了md3/md5文件,如果您只是导入一些模型来测试或者玩玩,使用md5导入器会是一个不错的选择。
     如果您有更多的内容文件或者自定义的数据,您可以写一个自定义的处理器,然后在项目中使用导入的编译过的数据,或者按照老方法自己加载内容文件。例如,本书最后几章您要写的一个竞速游戏,它就使用了一个从bitmap文件导入的带有高度值的landscape。为游戏处理bitmap文件和输出landscape高度值或许是可以的,但有很多工作要做——如果仅仅加载高度值会容易得多,而且只需要一次。

优势和不足

     内容管道的另一个缺点是,被编译过的内容不能再被修改。一旦您启动游戏或者把游戏部署到客户端电脑上或Xbox 360上,那么所有的内容文件包含的就只是编译过的数据。比方说您写了一个支持shader的粒子系统编辑器(particle editor),如果您想在编辑器运行的时候动态地改变textureshader和其它粒子系统设置,您必须得重新加载这些textureshader等等。但由于要先在XNA Studio中编译内容文件,所以您必须得停止应用程序,把所有这些文件添加到项目中并重新编译,等到所有内容都重新构建之后再启动程序。Especially in the case of just testing and tweaking effects or particles,这个非常讨厌而且会严重拖慢您的工作进度。如果在这些程序中不使用内容管道,只是动态地加载textureshader和粒子系统设置将会更好。

    
这里有一个技巧来处理大量的内容文件,我在大多数项目中都会使用:编译所有文件并确保不要频繁地更改(可以隔几天改一回)。这样您就可以使用一个虚拟项目编译所有的游戏内容,然后再把编译过的内容文件复制到实际的项目中。特别是在使用单元测试和最后一章讨论的敏捷方法学的时候,一天您会启动程序好几百次,所以每次运行都要尽可能地快。

     内容管道也有优点,那就是除了XNA引擎其它任何程序都无法读取编译过的数据(.xnb文件),并且数据的加载过程也更快,因为此时的所有数据都已经是游戏所需要的确切的格式。例如,如果在内容属性中把texture保存成DXT格式文件并使用mip-maps内容处理器,那么游戏就可以快速地调用texture数据,然后把它传送给显卡来渲染,这又是一个快速处理过程。这对于3D模型数据更加重要。如果您分析一下游戏Rocket Commander,会发现加载3D模型和产生所有的附加数据以及切线数据会花掉大部分的初始化时间(超过了90%),然而模型数据比它多10倍的XNA游戏加载却比它还快。能尽可能快地加载所有数据对Xbox 360来说也是一件好事,因为控制台游戏的加载时间通常都会很短。

处理内容目录

     好了,您已经学习了很多使用内容管道的优势和不足,那么现在要把重点放在编程和很多常见问题上。如果您看看游戏Rocket CommanderRacing Game的内容文件夹目录(如图3-2所示),会发现Rocket Commander又一大堆内容文件夹,而XNARacer只有两个。

3-2

     看了图3-2,您可能会以为Rocket Commander的内容文件会比XNARacer多很多,实际上XNARacer使用的3D模型文件数量几乎是Rocket Commander10倍,而且还包含更多的texture、音乐和声音文件。

     那么您或许要问为什么Rocket Commander使用这么多的文件夹。原因是这个游戏中没有使用内容管道,游戏的每一部分都单独使用一个文件夹,这样做可以更有组织地管理这些内容,而且容易查找。例如,“Textures”文件夹包含了菜单和游戏界面使用的所有2D texture,它的子文件夹“Models”包含了3D模型texture,“Effects”子文件夹则包含了特效texture

     在XNA中您不能使用这样的目录结构,因为大多数内容文件,尤其是3D模型文件,需要递归地加载其它相关的内容文件(如图3-3所示)。
图3-3

3-3

     正如您所看到的,Apple模型是从Apple.x文件加载的,而Apple.x文件又会递归地加载Apple.ddsAppleNormal.ddsNormalMapping.fx。内容处理器希望所有这些文件都放在同一个文件夹中,这迫使您不得不使用一个文件夹来存放所有的3D模型文件以及它们使用的textureshader文件。另外,大多数shader文件还会被其它3D数据使用,所以在另外一个文件夹也存放一份这些shader的相同的拷贝会比较混乱。有时候您还需要加载自定义3D数据需要使用的texture文件,比如,在游戏XNARacerguard rail holder模型和generated guard rail对象使用相同的texture

     不管怎样,一定要记住每一块内容必须要有唯一的名称,您不能同时使用一个名为“Apple”的模型和一个名为“Apple”的texture。正如您在图3-3中看到的,在“Input File”那一行只添加了一个Apple.x文件,其它的文件都是由模型处理器自动添加的。另外,XNA会很灵活地重命名所有的递归文件,因为它们通常会使用和模型文件名称相同的texture文件,递归文件的名称以“~0”结尾。您也无法设置这些递归文件的内容属性,因为它们不会被添加到项目中,所以要确保输入文件已经使用了正确的格式(在上述的例子中是DXT1DXT5)。

导入并访问内容数据

     现在您已经知道了导入内容并在游戏中访问它们。在前几章您已经访问了一些内容文件,并快速地了解了内容管道。现在您将更进一步地学习实际的处理过程,以及如何使用内容文件。在第七章,您会通过扩展X模型文件处理器(X Model File Processor)为您的图形引擎写自己的内容处理器,并添加一些有用的特性。

     在第一章您已经学了如何添加texture,只要选择一个texture文件(.dds.jpg.bmp.png)然后把它拖放到XNA Studio项目中。现在右键点击它来配置其Content Processor属性(如图3-4所示):
图3-4

3-4

     texture设置正确的Content Processor模式非常重要。对于2D数据,像sprite、文本以及游戏使用的所有UI图像,通常最好使用32bpp Sprite格式(未压缩的,即使用1024×1024分辨率及32bpptexture,需要4MB存储空间)。

     在一个3D游戏中,使用3D texture数据要比2D UI texture数据多得多,而且随着游戏的不同texture的数量和级别也在不断地膨胀。所以texture的尺寸得非常地小,但并不是要降低texture 的分辨率,因为这样会使游戏粗糙难看,而要使用硬件texture压缩(hardware texture compression)。您可以为颜色texture选择压缩率为1:6DXT1格式,为包含Alpha值(或compressed normal maps)的texture选择压缩率为1:4DXT5格式。这就是说在消耗相同图像存储容量的情况下,采用DXT1压缩的texture的数量是未压缩的texture的数量的6倍,而且还不会有太多的质量损失。另一个技巧是在shaders里合并或生成textures;例如,细节textures可以改进landscape的细节而不消耗额外的显存。

     对于模型文件,目前可以使用X Model Importer或者FBX Model Importer(如图3-5所示),将来可能会有更多的格式可以使用。如果写自定义的模型处理器(见第七章),you can select them the same way you select texture processorsFor normal mapping you want to select your custom XNARacer Tangent Model Processor from Chapter 7。在下面几章中使用默认值即可。
图3-5

3-5

     如果您按照前两章的方式来加载texture,那么您或许已经知道了如何在XNA中来加载内容,代码如下:
backgroundTexture = content.Load<Texture2D>("CityGroundSmall");

加载3D模型也使用相同的方式,只要改变Load方法参数的类型即可:
appleModel = content.Load<Model>("apple");

     显示模型稍微有点复杂,没有简单的绘制方法可以使用,必须检查所有的模型网格并更新所有shader特效,然后再渲染每一部分。更详细的内容参照第五章和第六章。在第七章中您将使用一个专门的类来加载和渲染模型,只需使用一行代码就可以把3D模型显示在3D世界中:
appleModel.Render(Vector3.Zero);

     现在您已经了解了内容管道的基础知识,接下来几章将学习有关内容管道的更多内容,比如在您的图形引擎中使用自定义的Tangent Model Processor添加3D模型,以及第九章更加详细地学习XACT