Custom content Writer/Reader

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:

  1. Creating the ImportedAsset class of outgoing content.
  2. Create the SpriteAsset class and inherit the ImportedAsset.
  3. Create a wrapper class ImportedContent (gets rid of the ReflectiveReader).
  4. Creating an abstract class for meta data.
  5. Creating the SpriteMeta class.
  6. Creating the AssetProcessor content process.
  7. Create an AssetWriter.

Actions in a game project or portable library:

  1. Creating an abstract Asset class. This one will be used in your game as the main content.
  2. 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:
image
image


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!!!

1 Like

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.