Tuesday, November 24, 2009

Non-Trivial Custom ContentProcessor - Part 2

If you're reading this for the first time you might want to check out the Introduction and Part 1 first.

The next step taken by the XNA Content Pipeline is to process the data produced by the content importer. This is done by the aptly named ContentProcessor classes. These classes are responsible for pretty much anything that happens between the content being loaded (via ContentImporter or ContentReader which I'll discuss an a later article) and the content being used.

ContentProcessors are used to massage data into a format usable by an XNA project and/or to load additional content. For example a model processor will load textures and shaders that the model is rendered with. So processors usually run when importing content into a game project or when loading imported content as part of game execution.

As I mentioned in Part 1, my custom content importer simply converted the raw XML input into a nice tree that can be worked with by the processor (using the .NET System.Xml family of classes.) Now this XML has to be turned into something meaningful to the game. This obviously leaves a lot of work to the content processor.

As I mentioned before this is the structure of the XML:
  • Group (document element)
  • One or more entity elements. Each element defines how it's related to the others. In-game this is used to build an n-tree of entity objects with one entity as the root.
  • One or more property elements for every entity. The meaning/use of these properties is dependent on the type of entity.

What we'll do in the processor is iterate through all "entity" elements in the document, read in the data we care about, and add each to a content data class called EntityGroupTypeContent. The code to do this is relatively straigtforward and uninteresting. I'm sure it can be improved but at the time I wrote it my main intention was for it to work.

outputGroup = new EntityGroupTypeContent();
XmlElement top = input.DocumentElement;
foreach (XmlElement entity in top.GetElementsByTagName("entity"))
{
// Read in entity attributes (convert from string as needed)
string name = entity.GetAttribute("name");
string parentName = entity.GetAttribute("parent");
string entityType = entity.GetAttribute("type");
string renderObjectName = entity.GetAttribute("render-object");
string[] rawPosition = entity.GetAttribute("position").Split(',');
Vector3 position = new Vector3(Convert.ToSingle(rawPosition[0]),
Convert.ToSingle(rawPosition[1]),
Convert.ToSingle(rawPosition[2]));

string[] rawOrientation = entity.GetAttribute("orientation").Split(',');
Quaternion orientation = new Quaternion(Convert.ToSingle(rawOrientation[0]),
Convert.ToSingle(rawOrientation[1]),
Convert.ToSingle(rawOrientation[2]),
Convert.ToSingle(rawOrientation[3]));

// Read entity properties
Dictionary properties = new Dictionary();
foreach (XmlElement property in entity.GetElementsByTagName("property"))
{
string propertyName = property.GetAttribute("name");
EntityGroupPropertyContent propertyData = new EntityGroupPropertyContent(property);
properties.Add(propertyName, propertyData);
}

// Add the entity
outputGroup.AddChildEntity(name, parentName, entityType, renderObjectName, position, orientation, properties);
}

Notice the lack of exception handlers on Convert.ToSingle. If something goes wrong here the exception percolates up to the content builder. When this happens the exception is recorded as a build error in Visual Studio. Also notice the EntityGroupPropertyContent instantiation. It's pretty important but talking about it brings up a lot of discussion irrelevant to this entry. I'll talk more about EntityGroupPropertyContent in Parts 3 and 4.

Next we have to validate the data that was loaded. The game expects the following conditions to be true:

  • The group has a root entity. Its parent entity is undefined
  • There is only one root entity.
  • Entities within the group are only related to eachother, not other groups
  • Entity properties are valid (notice valid here isn't defined yet, stay tuned :)

Since we didn't verify these conditions in the importer they have to be checked now. This way any errors in the content will be caught by the compiler and show up as build errors. Trying to catch these errors later would result in them crashing the game.

I'll get into validation with Part 3.

No comments:

Post a Comment