就像你在第一章学到的,内容管道通常用来导入游戏资源,诸如纹理、shaders、阴影和声音文件。不像任何其他非Visual Studio(或 XNA Studio)支持的文件,你只要把添加它们到工程项目,内容文件会被处理,并且然后会被编译为二进制内容文件,这些文件能被你的游戏加载(见图3-1)。
在过去游戏编程不得不编写自己的导入器来加载游戏内容数据或者使用一种可利用的格式,诸如在DirectX中.x 文件格式的模型文件。但是常常,这种可利用的格式是不能胜任的,太慢,或者当试着给游戏添加新特性时就是不可更改。这是为什么几乎每一款商业游戏都有自己的文件格式的原因,并且在其后台自定义逻辑编程。只有游戏开发者知道内部格式的规划是有益处的,并且他们能所心所欲地经常扩展或改变它。但是,在游戏中要用这种途径得到内容,通常有许多工作要做。加载纹理通常不是那么复杂,因为有许多库存在,并且即使当你编写自己的文件格式,它基本上也只包含被作为24bit或32bit色彩值储存的像素。如果你试图使用压缩或者硬件压缩纹理,诸如DXT格式,就变得有点儿难了,但是DirectX 有一整套的有用方法来协助你处理。
另一方面,加载3D模型数据要复杂得多,特别是在XNA,在这里你不仅必须要有几何数据,而且渲染一个3D对象需要数个shader,然后当然,材质数据将告诉着色器shader它的颜色、纹理以及一些要用到的参数。在DirectX中,大多数指南和范例只是使用.x文件格式,但是.x格式可能对于许多工程项目还不够胜任。尤其是,你如果使用normal napping(法线圆锥曲面叶)和需要包含切线的几何数据,.x文件将不是非常有用了。你将不得不在你的应用程序中产生切线,并且这样做还可能引入运行问题。例如,我的一个老游戏,Rocket Commander,就有这个问题,并且它需要一个复杂的模型加载过程和切线再生过程。
其他游戏数据像加载声音文件(.wav),shader(.fx),或者自定义数据(例如.xml)可能是简单的,因为你的游戏或者你正在使用的框架提供了充分的帮助类来快速加载这一切,但是然后,在另一个平台运行同样内容的游戏,你可能遇到问题。例如,你可能在Windows 平台使用ACPCM声音文件,并且使用编译过的Pixel Shader 1.1 文件,或者只是加载一对 .jpg 文件作为纹理,但是在Xbox 360上不支持ACPCM ;声音要么是PCM,要么是Xbox专有的自定义XMA格式。Shader代码必须是Xbox 360接受的格式,加载纹理的工作也可能有差别。如果更多的平台在未来被支持,这个问题甚至将变得更复杂。
为了简单化游戏内容的加载,XNA现在允许你直接把原始的内容文件放到XNA Studio工程项目,并且它们将被处理和编译为当前所选平台的正确输出格式。例如,你的声音文件能基于你的XACT项目设定来进行处理——你将拥有不同的输出格式和压缩格式。但是所有的原始声音文件在wave bank 上都是同样的,仅仅需要在某一位置被更新。这个主意很好,但是它需要所有的原始内容文件格式都被支持,这是不切合实际的因为可利用的文件格式如此之多,而你不知道哪一些将被使用。例如,你的一个图像艺术师可能使用Photoshop并且储存.psd文件,而其他团队可能使用 Gimp或Paint-Shop或仅仅是Windows绘图板。大约有数以千计的其他图形工具和程序。另外,你实际上不知道要提取哪一个数据;很多格式有“多层”结构,并且可能艺术家想要每一层都可利用,或者干脆合并一切图层。
取代干脆放下一切,使用一个可利用的处理程序,和被支持的格式,或者如果你认为你需要,试着写你自己的内容处理程序(见第七章):
纹理格式:: .dds, .png, .jpg, .bmp, .tga -基本上每一个你都能用.NET Framework或DirectX加载。输入格式通常为了最佳质量,应该是不被压缩的。被高度压缩的.jpg文件是坏的,尤其是在游戏中你使用DXT再次压缩它们。换句话说,对你的输入文件可以使用合适的输出压缩(如使用DXT压缩的.dds文件),并且再次为内容属性进行同样的设定,离开它们那个途径(在我的所有项目中,我用这个方式处理了大多数内容)。
声音和音乐格式:: .xap (XACT Audio Project) -在XACT中,你只能导入.wav文件,但是你能设置许多效果和参数,以及选择windows平台上的ACPCM压缩或者Xbox 360 平台上的XMA。阅读更多的相关内容在第九章。
3D模型格式: .fbx and .x model files - .x文件以来自DirectX SDK的许多范例和指南而知名。DirectX 提供了一些易于加载.x 文件的类。 大多数.x 文件在XNA下也应该工作得很好;主要的区别是DirectX 的.x文件通常不使用shader,而XNA总是使用shader。为了从3D Studio Max导出模型,使用Panda DirectX Exporter。你可以在http://www.andytather.co.uk/Panda/directxmax.aspx找到Panda Exporter 插件。
.fbx是一种较新的文件,最初由Alias开发,Alias是Maya的制作商,Maya也是一个3D建模工具。Alias被Autodesk -- 3D Studio Max 和许多 CAT 程序的制造商收购了。.fbx意思是“Universal 3D Asset Exchange”,并且是 Autodesk免费的跨平台内容交换格式。在新版本的3D Studio Max 9 它默认被包含,并且Maya也支持它。还有许多其他的3D 内容建造程序支持导入和导出.fbx 格式。在XNA,它对于活动的模型、骨骼和蒙皮特别有用。为此它支持更多选项,但它对shader特别糟糕,因为没有材质或任何shader 设置能被导出。
.fbx 格式的另一个问题是缺少格式的规格说明书,并且为了访问SDK你必须加入 Autodesk Developer Network 支付一年一度的会员资格金,真是吸血。如果你看看其它交换格式像Collada,你能看到它们要开放得多并且可扩展,因为它们不只是被一个公司开发,许多新附件和特色时常会被添加。在过去Collada 不支持shader设定,但是当前版本对3D数据支持很好;你能输出tangent切线,shader设定,和其他一切你在游戏中需要的东西。不幸的是,Collada不被XNA支持,并且我不能说服Microsoft包含它。XNARacer 最初所有的模型、赛道、地形数据都使用 Collada文件,但是后来全更改了以支持内容管道content pipeline。
其它格式 -你能导入自定义文件格式,例如xml文件、二进制文件,甚至写你自定义的处理程序。如果你有一个较大的项目,并且它值得你努力;或者你需要一个特殊的模型格式,并且它尚不被XNA支持,自定义格式就可能很有用。例如,Quake3/Doom3 使用 md3/md5文件,你如果只是用来导入一些模型测试和随便玩玩,一个md5 导入器会很棒。
万一你的游戏有较多的内容文件或者一些自定义数据,你要么决定写一个自定义处理程序,并且在工程项目中使用被导入的以及被编译的数据,要么就按照旧方式自己加载内容文件。例如,在本书最后一章你将编写一个竞速游戏,它会使用一个从位图文件导入的,带有地形高度值的地形图。处理位图文件,以及为游戏输出地形高度数据将是可能的,但是这有太多的工作——仅仅加载高度数据则要简单得多,并且只需要一次。
内容管道的另一个缺点是,被编译过的内容不能再一次被修改。一旦你启动游戏,或者把你的游戏部署于客户端电脑或Xbox 360,所有的内容文件只包含被编译过的数据。比如说你已经写了一个粒子编辑器,它使用了支持所有粒子的shader。当编辑器在运行的时候,如果你想要动态的改变纹理、shader,以及其他粒子设置,你将不得不重新加载纹理、shader等等。但是因为你首先需要在XNA Studio 中有内容被编译,你必须停止你的应用程序,添加所有的文件到你的XNA Studio 工程项目,重编译并且等待,直到所有内容被重新构建(build),然后再次启动。尤其是测试、调整特效或粒子的情形,可能非常恼人并且会严重拖慢你的工作进程。只是动态的加载纹理、shader和粒子设置,以及像那样的程序不使用内容管道可能会更好。
最后,这里有一个诀窍,我会用在大多数有许多内容文件的工程项目中:编译所有的内容文件,并且确定你不会非常频繁地修改它们(仅仅好几天改一次)。现在,你可以使用一个虚拟工程项目来编译你所有的游戏内容,并且拷贝所有编译过的内容文件到你真正的工程项目中。尤其当使用单元测试和章节最后谈论的敏捷方法学的时候,你将每天成百次的启动应用程序,并且每一次运行都要尽可能地快。
内容管道也有优点,那就是编译过的数据(.xnb文件)无法被除了XNA引擎之外的其它任何程序读取,并且其加载过程也通常快得多,因为所有数据都已经是游戏所需要的确切的格式。例如,纹理总是被储存为DXT文件,并且使用了假设你在内容属性中指定的mip-maps。这样,游戏就会在一次快速调用中加载texture纹理数据,然后把它发送给图像卡渲染,这又是一个快速处理过程。这对于3D模型数据更加重要。如果您看一看游戏Rocket Commander并分析一下,你会发现加载3D模型和产生所有的附加数据以及切线,会花掉大部分的初始化时间(超过了90%);然而XNA游戏有10倍的模型数据大小,加载快得多。能尽可能快地加载所有数据对Xbox 360控制台来说,也是一件好事;控制台游戏通常拥有很短的加载时间。
OK,您已经学习了很多使用内容管道的优势和不足;此时要聚焦在游戏编程和日常问题上了。如果您看一看Rocket Commander游戏和Racing Game游戏的内容文件夹(如图3-2),会发现Rocket Commander有一大堆文件夹,然而XNARacer只使用了两个简单的文件夹。
看图的样子,你可能会以为Rocket Commander的内容文件要多很多,但实际上XNARacer使用的3D模型文件数量几乎是Rocket Commander的10倍,并且还包含更多的纹理、音乐和声音文件。
您或许要问为什么Rocket Commander使用这么多的文件夹。因为在这个游戏中没有使用内容管道,为了更有组织地维护这些内容,并且容易查找,游戏的每一部分都使用了文件夹。例如,“Textures”文件夹包含了菜单和游戏界面使用的所有2D 纹理,它的“Models”子文件夹包含了3D模型纹理,“Effects”子文件夹则包含了特效纹理,等等诸如此类。
在XNA中,您不能使用这样的目录结构,因为大多数内容文件,尤其是3D模型文件,可能需要递归地加载许多其它的内容文件(如图3-3)。
正如您所见,Apple模型是从Apple.x文件加载的,它又递归地加载Apple.dds、AppleNormal.dds和NormalMapping.fx。内容处理器希望所有这些文件都放在同一个文件夹中,这迫使您使用一个文件夹来存放所有的3D模型文件、纹理以及它们使用的shader。另外,大多数shader还会被其它3D数据使用,复制shader可能非常令人糊涂,把他们放到另一个文件夹中也是。有时候您还需要为自定义3D数据加载纹理(比如,在游戏XNARacer中,guard rail holder模型和generated guard rail对象使用相同的纹理)。
无论如何,记住每一块内容必须有唯一的名称是很重要的。您不能拥有一个名为“Apple”的模型和一个名为“Apple”的纹理。正如您在图3-3中看到的,在“Input File”那一行只添加了一个Apple.x文件;所有的其它文件都是由模型处理器自动添加的。另外,XNA足够聪明的会重命名所有的递归文件,因为它们通常会使用和模型文件名称相同的纹理。递归文件的名称以“~0”结尾。您也无法设置这些递归文件的内容属性,因为你不能把它们添加到工程项目中,所以要确保输入文件已经使用了正确的格式(在上述的例子中是DXT1和DXT5)。
现在您已经足够了解如何导入某些内容并在游戏中访问它们。在前几章,您已经访问了一些内容文件,并快速地了解了内容管道。现在,您将更进一步地注视实际的处理过程,以及如何使用内容文件。在第七章,您会通过扩展X模型文件处理器(X Model File Processor)为您的图像引擎编写自己的内容处理器,并添加一些有用的特性。
回顾第一章您学习了如何添加纹理;只要选择一个纹理文件(.dds、.jpg、.bmp或.png)然后把它放到XNA Studio项目中。现在你可以点击纹理,并且配置其 Content Processor 属性(如图3-4):
给texture设置正确的Content Processor模式很重要。对于2D数据,像sprite、文本以及游戏使用的所有UI图像,通常最好使用32bpp Sprite格式(未压缩的,这意味着1024×1024分辨率及32bpp的纹理,需要4MB空间)。
在一个3D游戏中,3D纹理数据的使用要比2D UI 纹理多得多,而且每一款游戏的纹理和级别会逐渐增大。因为这个原因,保持纹理的小尺寸非常重要。要使用硬件纹理压缩(hardware texture compression),而不要降低纹理的分辨率以及使游戏看上去非常糟糕。您可以为色彩纹理选择1:6压缩率的DXT1格式,以及为包含Alpha信息(或compressed normal maps)的纹理选择1:4压缩率的DXT5格式。这意味着在游戏消耗相同显存容量的情况下,DXT1压缩的纹理数量是未压缩纹理的6倍,而且还不会有太多的质量损失。另一个技巧是在shader里合并纹理或者甚至生成纹理;例如,细节纹理可以改进地形的细节而几乎不消耗额外的显存。
对于模型文件,当前你只能使用X Model Importer或者FBX Model Importer(如图3-5)。将来可能会有更多的格式可供使用。如果你编写自定义的模型处理器,就像你将要在第七章做的那样,你能以你选择纹理处理器同样的方式选择模型处理器。对于normal mapping 你要选择来自于第七章的自定义XNARacer Tangent Model Processor。在下面几章中只要许可默认值。
如果您按照前两章的方式来加载纹理,那么您或许已经知道了如何在XNA中加载内容。纹理加载如下:
backgroundTexture = content.Load<Texture2D>("CityGroundSmall");
3D模型也以相同的方式加载----只要改变Load方法的泛型参数类型:
appleModel = content.Load<Model>("apple");
显示模型稍微有点复杂;没有简单的绘制方法,你必须遍历所有的模型mesh并更新所有shader特效,然后再渲染每一部分。更多细节参看第五章和第六章。本书在第七章后面,您将看到一个新类专门加载和渲染模型,它使3D模型显示在3D世界中甚至简单到只需使用一行代码就实现:
appleModel.Render(Vector3.Zero);
现在您已经了解所有的关于内容管道的基础知识。在即将到来的几章中,当您在图形引擎中使用自定义的Tangent Model Processor添加3D模型的时候,你将学习有关内容管道的更多内容,并且在第九章中你会深入学习XACT。