Wednesday, April 18, 2018

xna - ContentSerializerRuntimeType required in content pipeline?


I have a problem at the moment loading in a texture for a larger model in the content pipeline. Now it seems that I need to make a proxy object, which is fine, however looking at the SpriteSheet example provided by Microsoft they seem to use the ContentSerializerRuntimeType attribute to bind the proxy class to the real class for use at runtime.


The problem is they sprinkle serialization attributes all over the runtime class, so I was wondering if you HAVE to do it this way or if it is just a recommendation, and is there any other way of getting your proxy class to the real instance?


Ideally I dont want to have to put attributes all over my classes and would be more than happy to write the serialization code myself if there was a way to, but it seems to be an unknown as far as the documentation goes for this area...



So anyone know a way round this, or is this just the only way to do it?



Answer



If you want to avoid adding attributes to your classes (required for automatic reading/writing) then you'll want to implement a ContentTypeReader and ContentTypeWriter




Here's my runtime class that I want to load:


public class ComponentPropertyGetMathExpression : MathExpression
{
public Type ComponentType;
public string PropertyName;


public ComponentPropertyGetMathExpression()
{

}

// methods removed
}

I have 2 fields I want to get the values for.




One interesting thing here is, one of them has the type Type, which doesn't have a public parameterless constructor. You'd get weird errors if you tried to use the default reader/writer because of this, so this is a real example of when you would need to implement your own.





The first thing, is to create a Content version of this class in a seperate project, that will references the project with the runtime type and will be referenced by the content project:


[ContentSerializerRuntimeType("FuzzyDuckEntertainment.Entities.Templates.Expressions.ComponentPropertyGetMathExpression, FuzzyDuckEntertainment.Entities")]
public class ComponentPropertyGetMathExpressionContent : MathExpressionContent
{
public string ComponentType;
public string PropertyName;


public ComponentPropertyGetMathExpressionContent()
{
}

// Runtime methods not needed - this class is simply a container of fields/properties
}

The ContentSerializerRuntimeType attribute tells the pipeline what type this will represent at runtime.



Note that the ComponentType field was a Type at runtime, but here it's a string. This is because of the lack of public parameterless constructor as noted above. Another time you might have different types is when the runtime type is something GPU related, for example a Texture2D (in which case you might have ExternalReference and use a ContentProcessor).






Now we have our 2 types.



I'm going to use the built in XML Importer to go from an XML file to a ComponentPropertyGetMathExpressionContent object and no Processor. A case where you might use your own importer is if you have a heightmap BMP and want to generate vertex data. An example of using a processor is the Font Texture Processor that creates a game Font from a Texture.



Now we need to get from the content type, to the runtime type.


First, lets create the class that writes the XNB file at build time:


[ContentTypeWriter]
public class ComponentPropertyGetMathExpressionContentWriter : ContentTypeWriter

{
protected override void Write(ContentWriter output, ComponentPropertyGetMathExpressionContent value)
{
output.Write(value.ComponentType);
output.Write(value.PropertyName);
}

public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(ComponentPropertyGetMathExpressionReader).AssemblyQualifiedName;

}

public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(ComponentPropertyGetMathExpression).AssemblyQualifiedName;
}
}

Hopefully the above should be self explanatory (note the order we write the fields in).





The reader is implemented as below:


public class ComponentPropertyGetMathExpressionReader : ContentTypeReader
{
protected override ComponentPropertyGetMathExpression Read(ContentReader input, ComponentPropertyGetMathExpression existingInstance)
{
if (existingInstance == null)
existingInstance = new ComponentPropertyGetMathExpression();

existingInstance.ComponentType = Type.GetType(input.ReadString());
existingInstance.PropertyName = input.ReadString();


return existingInstance;
}
}

Note the order we read things in. Also note that the first thing we read is the string that correspond to the ComponentType property. As the runtime type in this case is Type, not string I need to do some simple translation. Ideally you want any translations like this to be as minimal as possible, as unlike anything before, this will run when the game is loading.




See also:


What Is Content? (MSDN)
Using an XML File to Specify Content (MSDN)

Shawn Hargreaves' Blog Index #Content Pipeline


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...