问题
要让模型处理器可以将对象存储在模型中并传递到XNA项目,XNA提供了模型的Tag属性。从教程4-13,4-14和4-15中可以看到Tag属性对于存储一个相同类型的数组是很有用的,例如Vector3的数组或Triangle的数组。但很多情况中,你想传递多个对象,例如同时传递Vector3集合和模型的包围盒。
解决方案
定义一个自定义类,这个类存储所有你想传递的对象。在你的模型处理器中,创建这个类对象,将要传递到XNA项目中的对象存储在这个类对象中。最后在模型的Tag属性中存储这个类对象。
因为你定义了一个自定义类,所以你必须定义一个TypeWriter (见教程4-15)才能让XNA知道如何串行化这个对象,还要定义一个TypeReader,通过这个TypeReader从二进制文件读取这个对象。
工作原理
因为你要扩展内容处理器并传递自定义类对象,所以你要执行教程4-15中的“使用一个自定义类对象扩展内容处理器的步骤清单”中的初始化步骤。在第1步中,我调用了新内容管道项目TagPipeline。在第4步中我调用了处理器ExtendedModelProcessor。
namespace TagPipeline { [ContentProcessor] public class ExtendedModelProcessor : ModelProcessor { public override ModelContent Process(NodeContent input, ContentProcessorContext context) { return base.Process(input, context); } } }
完成初始化后,你就做好了定义一个可以存储你想传递到XNA项目的东西的新类的准备了。本例中,你将传递一个包含模型所有Vector3的数组和全局包围盒。
在内容管道命名空间顶部定义这个新类:
public class TagObject { private Vector3[] positions; private BoundingBox boundingBox; public TagObject(Vector3[] positions, BoundingBox boundingBox) { this.positions = positions; this.boundingBox = boundingBox; } public Vector3[] Positions { get { return positions; } } public BoundingBox GlobalBoundingBox { get { return boundingBox; } } }
这个简单的类可以存储一个Vector3数组和包围盒。它的构造函数将这些变量传递到内部变量中,你还定义了两个getter方法,让你可以获取变量的内容。
注意:因为这个变量不包含行为方法,你也可以使用结构数据类型替代类。
然后编写模型处理器代码。它可以获取你想传递的数据:Vector3数组和包围盒。
public override ModelContent Process(NodeContent input, ContentProcessorContext context) { ModelContent usualModel = base.Process(input, context); List<Vector3> vertices = new List<Vector3>(); vertices = AddVerticesToList(input, vertices); BoundingBox bBox = BoundingBox.CreateFromPoints(vertices); TagObject myTagObject = new TagObject(vertices.ToArray(), bBox); usualModel.Tag = myTagObject; return usualModel; }
模型处理器开始的操作已经在前面的教程中做过很多次了。然后,你使用AddVerticesToList方法遍历整个模型结构将所有Vector3添加到一个集合中。
有了这个集合之后,可以使用BoundingBox. CreateFromPoints方法从这个集合生成包围盒。这个方法以Vector3集合为参数,而这个集合具有Ienumerable接口,就好像一个数组或 List。
通过将集合转换到数组,你就拥有了创建TagObject类对象所需的所有东西!最后,将这个类对象存储在模型的Tag属性中。
现在己经完成了这个教程的前半部分,在第二部分,你要编写TypeWriter和TypeReader。
编写TypeWriter和TypeReader
现在如果你运行代码,XNA会报错,这是因为它还不知道如何将TagObject类对象存储到二进制文件,所以需要在内容管道命名空间下添加自定义TypeWriter:
[ContentTypeWriter] public class TagObjectTypeWriter : ContentTypeWriter<TagObject> { protected override void Write(ContentWriter output, TagObject value) { output.WriteObject<Vector3[]>(value.Positions); output.WriteObject<BoundingBox>(value.GlobalBoundingBox); } public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(TagObjectTypeReader).AssemblyQualifiedName; } }
如教程4-15中的解释,前两行代码指定这个类是一个可以串行化TagObject对象的ContentTypeWriter。同理,你也要重写两个方法:Write方法指定一个TagObject如何被写入到二进制文件中,GetRuntimeReader方法可以被XNA调用,让程序知道到哪找到对应的TypeReader,可在教程4-15见到更多信息。
默认内容处理器知道如何串行化Vector3数组和包围盒,所以你只需要简单地让XNA为你串行化就可以了。在GetRuntimeReader方法中,你声明在相同的命名空间中编写一个叫做TagObjectReader的对应TypeReader。
注意:如果默认您内容管道不知道如何串行化包围盒你可以自己定义。你可以调整处理TagObjects的TypeWriter,使它将包围盒中的两个Vector3保存到二进制文件中,这样ContentReader可以重新构造这个包围盒。但是,更好的方法是编写一个额外的TypeWriter和TypeReader用来串行化/反串行化一个包围盒对象,如果使用这个方法,后面的ContentWriters会知道如何串行化包围盒对象!
下面编写TypeReader,因为已经在GetRuntimeReader方法中定义了,所以TypeReader类必须被叫做TagObjectTypeReader:
public class TagObjectTypeReader : ContentTypeReader<TagObject> { protected override TagObject Read(ContentReader input, TagObject existingInstance) { Vector3[] positions = input.ReadObject<Vector3[]>(); BoundingBox bBox = input.ReadObject<BoundingBox>(); TagObject restoredTagObject = new TagObject(positions, bBox); return restoredTagObject; } }
在程序启动时,每个TagObject类对象都会被串行化为一个二进制文件,而TagObjectTypeReader方法可以重建这些对象。首先你从文件中读取Vector3数组并将它存储在一个变量中;然后对包围盒进行同样的处理。有了这两个对象后,就可以重建TagObject对象并把它传递到XNA程序中。
很简单,但有一点很重要,你必须以写入文件的同样顺序读取这些对象!如果你首先读入的是包围盒,你会基于第一个Vector3数组重建包围盒!幸运的是,XNA team对此进行了保护,如果你颠倒了读取顺序,程序会报错。
注意:如果你的解决方案无法编译,请再看一下教程4-15中的步骤清单。
在XNA项目中访问数据现在运行程序,所有使用ExtendedModelProcessor 的模型都会在Tag属性中包含一个TagObject对象、因为Tag属性可以包含任何东西,所以首先需要将它转换为TagObject对象。现在就可以访问它的属性了:
myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; TagObject modelTag = (TagObject)myModel.Tag; BoundingBox modelBBox = modelTag.GlobalBoundingBox; Vector3[] modelVertices = modelTag.Positions; System.Diagnostics.Debugger.Break();
代码
这个教程中内容管道包含下列对象:
- ExtendedModelProcessor模型处理器,包含AddVerticesToList辅助类
- 自定义TagObject类定义 class definition
- 自定义TypeWriter,可以串行化TagObject类对象
- 自定义TypeReader,可以从二进制文件读取TagObject类对象
完整代码如下:
namespace TagPipeline { public class TagObject { private Vector3[] positions; private BoundingBox boundingBox; public TagObject(Vector3[] positions, BoundingBox boundingBox) { this.positions = positions; this.boundingBox = boundingBox; } public Vector3[] Positions { get { return positions; } } public BoundingBox GlobalBoundingBox { get { return boundingBox; } } } [ContentProcessor] public class ExtendedModelProcessor : ModelProcessor { public override ModelContent Process(NodeContent input, ContentProcessorContext context) { ModelContent usualModel = base.Process(input, context); List<Vector3> vertices = new List<Vector3>(); vertices = AddVerticesToList(input, vertices); BoundingBox bBox = BoundingBox.CreateFromPoints(vertices); TagObject myTagObject = new TagObject(vertices.ToArray(), bBox); usualModel.Tag = myTagObject; return usualModel; } private List<Vector3> AddVerticesToList(NodeContent node, List<Vector3> vertList) { MeshContent mesh = node as MeshContent; if (mesh != null) { Matrix absTransform = mesh.AbsoluteTransform; foreach (GeometryContent geo in mesh.Geometry) { foreach (int index in geo.Indices) { Vector3 vertex = geo.Vertices.Positions[index]; Vector3 transVertex = Vector3.Transform(vertex, absTransform); vertList.Add(transVertex); } } } foreach (NodeContent child in node.Children) vertList = AddVerticesToList(child, vertList); return vertList; } } [ContentTypeWriter] public class TagObjectTypeWriter : ContentTypeWriter<TagObject> { protected override void Write(ContentWriter output, TagObject value) { output.WriteObject<Vector3[]>(value.Positions); output.WriteObject<BoundingBox>(value.GlobalBoundingBox); } public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(TagObjectTypeReader).AssemblyQualifiedName; } } public class TagObjectTypeReader : ContentTypeReader<TagObject> { protected override TagObject Read(ContentReader input, TagObject existingInstance) { Vector3[] positions = input.ReadObject<Vector3[]>(); BoundingBox bBox = input.ReadObject<BoundingBox>(); TagObject restoredTagObject = new TagObject(positions, bBox); return restoredTagObject; } } }