Hello everybody! I specifically specify the ContentReader type. Why is it the ReflectiveReader that is written in the XNB file, and not the AssetReader?
After a little investigation, I realized that the ReflectiveReader is used automatically if the passed class is inherited. Is it possible to somehow forcibly specify a ContentReader?
And so, what was my idea to simplify the creation of custom content with additional parameters.
I will show an example of creating a Sprite with additional data. The sprite has a texture and a rectangle.
Actions in the ContentPipeline project:
- Creating the ImportedAsset class of outgoing content.
- Create the SpriteAsset class and inherit the ImportedAsset.
- Create a wrapper class ImportedContent (gets rid of the ReflectiveReader).
- Creating an abstract class for meta data.
- Creating the SpriteMeta class.
- Creating the AssetProcessor content process.
- Create an AssetWriter.
Actions in a game project or portable library:
- Creating an abstract Asset class. This one will be used in your game as the main content.
- Create Sprite and inherit from Asset.
Class ImportedAsset
public abstract class ImportedAsset : IImportedAsset
{
public abstract string GetRuntimeType(TargetPlatform targetPlatform);
public abstract void Write(ContentWriter output);
}
Class SpriteAsset
public class SpriteAsset : ImportedAsset
{
public TextureContent Texture { get; set; }
public Rectangle? Source { get; set; }
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(Sprite).AssemblyQualifiedName;
}
public override void Write(ContentWriter output)
{
output.WriteObject(Texture);
output.WriteObject(Source);
}
}
Class ImportedContent
public sealed class ImportedContent
{
public object Asset { get; set; }
public void Write(ContentWriter output)
{
if(Asset == null)
throw new ArgumentNullException(nameof(Asset));
IImportedAsset importAsset = (IImportedAsset)Asset;
output.Write(importAsset.GetRuntimeType(output.TargetPlatform));
importAsset?.Write(output);
}
}
Class AssetMeta
public abstract class AssetMeta<TInput> : IAssetMeta
{
Type IAssetMeta.InputType => typeof(TInput);
public abstract ImportedContent Process(TInput input, ContentProcessorContext context);
object IAssetMeta.Process(object input, ContentProcessorContext context)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
if (context == null)
{
throw new ArgumentNullException("context");
}
if (!(input is TInput))
{
throw new InvalidOperationException("input is not of the expected type");
}
return Process((TInput)input, context);
}
}
Class SpriteMeta. Don’t pay attention to the properties. This is just a demonstration
public class SpriteMeta : AssetMeta<TextureContent>
{
[ContentSerializer(Optional = true)]
public bool AutoGenerate { get; set; }
[ContentSerializer(Optional = true)]
public Point AutoSize { get; set; }
[ContentSerializer(Optional = true)]
public Point StartOffset { get; set; }
[ContentSerializer(Optional = true)]
public Point EndOffset { get; set; }
[ContentSerializer(Optional = true)]
public Rectangle? Source { get; set; }
public override ImportedContent Process(TextureContent input, ContentProcessorContext context)
{
// apply the modification, if need
return new ImportedContent() { Asset = new SpriteAsset() { Texture = input }};
}
}
Class AssetProcessor
[ContentProcessor(DisplayName = "Asset-Meta Processor - ZZZ")]
public class AssetProcessor : ContentProcessor<object, ImportedContent>
{
public override ImportedContent Process(object input, ContentProcessorContext context)
{
return (ImportedContent)LoadMeta(context).Process(input, context);
}
private IAssetMeta LoadMeta(ContentProcessorContext context)
{
// Load your meta-file. I use XML XNA format.
}
}
Class AssetWriter
[ContentTypeWriter]
public class AssetWriter : ContentTypeWriter<ImportedContent>
{
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(Asset).AssemblyQualifiedName;
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(AssetReader).AssemblyQualifiedName;
}
protected override void Write(ContentWriter output, ImportedContent value)
{
value.Write(output);
}
}
Class Asset
public abstract class Asset : IAsset
{
public abstract void Read(ContentReader input);
}
Class AssetReader
public class AssetReader : ContentTypeReader<IAsset>
{
protected override IAsset Read(ContentReader input, IAsset existingInstance)
{
var typeName = input.ReadString();
Type type = Type.GetType(typeName);
IAsset asset = (IAsset)Activator.CreateInstance(type, true);
asset.Read(input);
return asset;
}
}
Class Sprite
// Sprite inherited from Asset
public Rectangle? Source { get; private set; }
private Texture2D texture;
public override void Read(ContentReader input)
{
texture = input.ReadObject<Texture2D>();
Source = input.ReadObject<Rectangle?>();
}
// ...
A few screenshots:
And the answer to the question itself: you can specify a specific type of ContentReader only for classes that are not inherited. And my post above solves this problem (exactly for me)
I am not sure if it’s the same thing we are trying to do here, but I had a very similar issue in the past.
https://github.com/MonoGame/MonoGame/issues/5257
Yes, this is exactly the case. As I understand it, the question is still open and in the future, most likely, this problem will be fixed. In the meantime, I will use my own version above. Thanks for the answer!!!
And by the way, I’ll steal your decision about the Sprite class - I’ll inherit it from Texture2D. This solution seems to me the best. Thanks.