Creating a terrain using ContentPipeline and XmlImporter (or a custom importer / processor)

Here's my situation: I want to create a text file describing terrain attributes, like heightmap file (raw or an image format), colormap, detailmap and splatting maps. Only the heightmap file and format will be required in this file, so the colormap, detailmap and splatting maps are optional. I then want to add this file to my XNA game project and have it pack a .xnb file containing this data. Ideally, when I do this in my game's LoadContent method:

Terrain t = contentManager.Load<Terrain>(@"Content\Terrain\Test");

I should get the terrain object back ready to use, with whatever options I set in it. I couldn't get custom importers and processors to work, so I'll come back to that later, but I did get XmlImporter working with my custom ContentPipeline writer and reader objects working. I had a few problems though:

1) Every property not marked as [SerializerIgnore] must be specified (request: it might be useful to consider a [SerializerOptional] attribute).

2) In the Xml file, the terrain media files are specified as paths, but I want them to be ExternalReference in the .xnb file, how do I do this

3) Let's say I have a folder 'Content\Terrain\' with a heightmap.bmp, colormap.bmp and test.xml. The Xml file looks like this:

< xml version="1.0">
<XnaContent>
<Asset Type="MyGame.Terrain">
<Heightmap>heightmap.bmp</Heightmap>
<Colormap>colormap.bmp</Colormap>
</Asset>
</XnaContent>
</xml>

In my custom writer, if I write the heightmap and colormap as ExternalReference<Texture2D> I get an exception saying absolute paths must be specified. Does that mean I have to specify the entire path to each file in the Xml file And if so, when I get the files back to I have to specify that absolute path again to get the textures It seems like that approach could cause media problems when I deploy the game to another machine or an Xbox360.

By the way: Nice job on the ContentPipeline, it looks like this will be a useful tool once people understand it better and write more tutorials for it.


Answer this question

Creating a terrain using ContentPipeline and XmlImporter (or a custom importer / processor)

  • AlexBB

    Any time you load something that is IDisposable using the content manager, the manager will take care of disposing this for you when you either call Unload, or dispose the manager.


  • Pooja Katiyar

    Thank you Shawn and Leaf, that is very helpful information

  • Wayne Sepega

    Call dispose on an object when you are done using and want to clear up its resources. If you let the object fall out of the object graph (i.e. stop referencing it, or set all references of it to null) and it implements IDisposable, then the garbage collector will call dispose when it gets activated to clean up memory. I am pretty sure ContentManager will take care of this for you, but don't quote me on that.

  • rajshekar

    I'm still a little fuzzy on when to call Dispose. When using ReadExternalReference, is it ever necessary to call Dispose on the returned resource, or will the ContentPipeline/XNA/GC take care of all that for you
  • luca82

    You can use a [ContentSerializer(Optional = true)] attribute for members that are allowed to be left out.

    To use ExternalReference, you will need a custom processor to build those references. Typically you'd want something like:

    In a custom processor:

    // make an external reference to the texture
    string absoluteFilename = Path.GetFullPath(relativeFilename);

    ExternalReference<TextureContent> reference = new ExternalReference<TextureContent>(absoluteFilename);

    // build the texture (this will do all the appropriate incremental rebuild checks)
    ExternalReference<TextureContent> builtTexture = context.BuildAsset<TextureContent, TextureContent>(reference, "ModelTextureProcessor");

    // now store the builtTexture reference somewhere: this points to the output XNB file produced by building the texture

    In your ContentTypeWriter:

    output.WriteExternalReference(builtTexture);

    In your ContentTypeReader:

    Texture texture = input.ReadExternalReference<Texture>();

    The thing to note is that the external reference you write out needs to point to the result of building the texture, rather than to the original source texture file. That being so, you can then read it directly into a runtime Texture object.


  • Juvefan

    The absolute path requirement is to avoid any confusion when dealing with relative paths such as where are they relative to, what is the current working directory, that kind of thing. Although you have to specify absolute paths they are stored as relative paths so you will not have problems with deployment.

    You don't need to store absolute paths in your terrain description file, store relative paths and make them absolute (relative to your terrain description file) when you want to create the external references. I've done this before by storing the path of the description file in the object that the custom importer creates - I don't know exactly how you would do that if you are only using the XmlImporter, perhaps you might want to look into getting custom importers working.

    Cheers,
    Leaf.


  • Creating a terrain using ContentPipeline and XmlImporter (or a custom importer / processor)