2.14 编写自定义内容导入器
问题
你想通过内容管道将一个自定义格式的文件导入到XNA项目中。因为你的文件的扩展名和/或结构与XNA支持的文件格式不同,默认XNA内容管道无法知道如何导入并处理你的文件。
解决方案
你需要编写一个自定义内容导入器,它可以从磁盘中读取文件,将有用的数据格式化为一个对象,做好被内容处理器处理的准备。因此,这个教程假设你知道如何创建自己的内容处理器,可以参见教程3-9了解内容管道的知识。
这个教程只关心自定义导入器。ContentIonmporter需要从磁盘读取数据并将它格式化为一个对象。本教程中,你需要读取存储在一个由逗号分隔值的文件(common-separated values,简称CSV文件)中的两个Vector3并将它们转换为一个矩阵。选择这个例子的理由是因为一个View矩阵需要经过一些计算才能被构建,XNA有一个默认的TypeWriter和TypeReader可以串行化和反串行化Matrix对象,这样本教程就可以只关心从磁盘加载文件,编写内容导入器,从自定义内容处理器获取数据。
看一下图2-27。自定义导入器读取一个CSV文件的数据并创建一个CSVImporterToProcessor对象存储从文件中获取的有用数据。自定义CSVMatrixProcessor处理CSVImporterToProcessor对象中的数据并保存到一个Matrix对象。然后,XNA默认的处理Matrix的TypeWriter和TypeReader会进行串行化和反串行化操作。可参见教程4-15和4-16学习如何编写自己的TypeWriter和TypeReader。
因为导入器和处理器都在编译时调用,你可能想知道将导入器和处理器分开处理的好处。例如,为什么不能更加简单地只定义一个方法读取CSV文件立即将它存储在Matrix对象中,而不是编写两个方法和一个中间CSVMatrixProcessor对象?
将处理过程分开的优点是你可以为多个导入器重用处理器(反之亦然)。例如,XNA框架可以将一个NodeContent对象转换为一个ModelContent对象。如果你从一个不是.x或.fbx格式的文件中导入一个模型,你要做的就是编写一个导入器为你的文件创建一个NodeContent对象,之后就可以使用默认的ModelProcessor了。
图2-27 定义一个内容导入器和它在内容管道中的位置
工作原理
首先使用一个文本编辑器创建一个CSV,下面是我的CSV文件:
-5;5;8; 0;2;0;
这些是将在教程中构建的视矩阵的位置的观察目标。
中间CSVImporterToProcessor类
接着,看一下教程3-9中的步骤清单。我调用自己的内容管道项目CSVToViewMatrixPlpeline。在教程3-9的第4步中不进行任何操作;你需要编写一些额外的方法。在本教程的最后,你可以看到内容管道的所有代码。
首先,你需要一个对象用来存储从磁盘中读取的数据。这个对象作为导入器的输出和处理器的输入(这会使用它的内容构建一个视矩阵)。添加一个自定义CSVImporterToProcessor类,它存储两个Vectors:
public class CSVImporterToProcessor { private Vector3 position; private Vector3 target; public Vector3 Position { get { return position; } } public Vector3 Target { get { return target; } } public CSVImporterToProcessor(Vector3 position, Vector3 target) { this.position = position; this.target = target; } }
这个类可以存储两个Vectors3。两个getter方法让处理器可以访问这个数据。当调用构造函数时,导入器需要提供所有数据。
ContentImporter
ContentImporter用来从文件中读取数据,所以确保在文件的顶部已经添加了包含它们的命名空间(解释请见教程3-9的第3步):
using System.IO;
做好了编写自定义导入器的准备,添加以下代码:
[ContentImporter(".csv", DefaultProcessor = "CSVMatrixProcessor")] public class CSVImporter : ContentImporter<CSVImporterToProcessor> { public override CSVImporterToProcessor Import(string filename, ContentImporterContext context) { } }
第一行的属性表示这个类可以处理.csv文件,它的输出默认被CSVMatrixProcessor处理(下面就要创建)。在下面的代码中,你指定这个导入器会生成一个CSVImporterToProcessor对象。
在编译时,导入器会导入一个.csv文件并将它转换为一个CSVImporterToProcessor对象。首先打开文件读取第一行,这是由下面代码中的前两行中进行的。要分割;之间的值,你需要使用一个简单的Split方法,这会返回一个三个字符串的数组,每个字符串包含相机位置的数字,接下去的三行代码将这三个字符串转换为浮点数,用来创建最后的位置Vector3。
StreamReader file = new StreamReader(filename); string line = file.ReadLine(); string[] lineData = line.Split(';'); float x = float.Parse(lineData[0]); float y = float.Parse(lineData[1]); float z = float.Parse(lineData[2]); Vector3 position = new Vector3(x,y,z);
从文件中读取第二行,用同样的方法并将它转换为一个观察目标Vector3:
line = file.ReadLine(); lineData = line.Split(';'); x = float.Parse(lineData[0]); y = float.Parse(lineData[1]); z = float.Parse(lineData[2]); Vector3 target = new Vector3(x,y,z);
现在,有了数据就可以创建一个CSVImporterToProcessor对象了:
CSVImporterToProcessor finalData = new CSVImporterToProcessor(position,target); return finalData;
这个对象被发送到用户选择的处理器。显然,处理器应该可以将CSVImporterToProcessor对象作为输入。
在内容处理器中接受数据
因为CSVImporterToProcessor类是自定义类,你需要创建一个自定义处理器。这个处理器会将CSVImporterToProcessor对象转换为一个Matrix对象。看一下本章第一个教程了解如何根据相机的位置和观察目标构建一个视矩阵:
[ContentProcessor] public class CSVMatrixProcessor : ContentProcessor<CSVImporterToProcessor, Matrix> { public override Matrix Process(CSVImporterToProcessor input, ContentProcessorContext context) { Vector3 up = new Vector3(0, 1, 0); Vector3 forward = input.Target - input.Position; Vector3 right = Vector3.Cross(forward, up); up = Vector3.Cross(right, forward); Matrix viewMatrix = Matrix.CreateLookAt(input.Position, input.Target, up); return viewMatrix; } }
你声明这个处理器可以将一个CSVImporterToProcessor对象转换为一个Matrix对象。CSVImporterToProcessor对象中的position和target用来创建视矩阵。XNA知道如何从二进制文件串行化/反串行化一个Matrix对象,并将Matrix对象加载到XNA项目中,所以你无需编写自定义的TypeWriter和TypeReader。
使用方法
当你确认已经完成了教程3-9中的9个步骤后,你就可以将.csv文件导入到项目中去了。当在解决方案资源管理器中选择一个.csv文件时,你应该可以在属性窗口中注意到这个文件由CSVImporter导入,如图2-28所示,因为你将CSVImporter声明为默认导入器,CSVMatrixProcessor作为处理器。
图2-28 选择内容导入器和处理器
导入.csv文件后,你要做的就是使用下面的代码从.csv文件中加载一个视矩阵:
protected override void LoadContent() { viewMatrix = Content.Load<Matrix>("camerasettings"); }
代码
因为内容管道很短,我把它放在了一起。第一个代码块是命名空间。命名空间中的第一个类是CSVImporterToProcessor类,它可以存储所有数据。接下来是ContentImporter类,它可以从一个.csv文件中读取并存储CSVImporterToProcessor对象中的数据集。最后的代码块是内容处理器,可以基于CSVImporterToProcessor对象中的内容构建一个视矩阵。因为XNA自带可以处理Matrix对象的TypeWriter和TypeReader,所以上面的代码就是一个完整功能的内容管道了。如果你的处理器创建了一个自定义类对象,可参见教程4-15和4-16学习如何创建自己的TypeWriter和TypeReader。
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Content.Pipeline.Processors; using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; namespace CVSToViewMatrixPipeline { public class CSVImporterToProcessor { private Vector3 position; private Vector3 target; public Vector3 Position { get { return position; } } public Vector3 Target { get { return target; } } public CSVImporterToProcessor(Vector3 position, Vector3 target) { this.position = position; this.target = target; } } [ContentImporter(".csv", DefaultProcessor = "CSVMatrixProcessor")] public class CSVImporter : ContentImporter<CSVImporterToProcessor> { public override CSVImporterToProcessor Import(string filename, ContentImporterContext context) { StreamReader file = new StreamReader(filename); string line = file.ReadLine(); string[] lineData = line.Split(';'); float x = float.Parse(lineData[0]); float y = float.Parse(lineData[1]); float z = float.Parse(lineData[2]); Vector3 position = new Vector3(x,y,z); line = file.ReadLine(); lineData = line.Split(';'); x = float.Parse(lineData[0]); y = float.Parse(lineData[1]); z = float.Parse(lineData[2]); Vector3 target = new Vector3(x,y,z); CSVImporterToProcessor finalData = new CSVImporterToProcessor(position, target); return finalData; } } [ContentProcessor] public class CSVMatrixProcessor : ContentProcessor<CSVImporterToProcessor, Matrix> { public override Matrix Process(CSVImporterToProcessor input, ContentProcessorContext context) { Vector3 up = new Vector3(0, 1, 0); Vector3 forward = input.Target - input.Position; Vector3 right = Vector3.Cross(forward, up); up = Vector3.Cross(right, forward); Matrix viewMatrix = Matrix.CreateLookAt(input.Position, input.Target, up); return viewMatrix; } } }
发布时间:2009/11/16 上午9:00:41 阅读次数:5627