Загрузка текстур из ссылки на встроенный контент в файле XML - PullRequest
6 голосов
/ 08 декабря 2011

Цель

Я пытаюсь загрузить пользовательский класс, содержащий Texture2D, из xml-файла, используя импортер по умолчанию (содержимое XML), без процессора.


подход

Множество исследований в Интернете и множество других ошибок приводят меня к этому XML:

<?xml version="1.0" encoding="utf-16"?>
<XnaContent xmlns:Components="Entities.Components">
  <Asset Type="EntitiesContentPipeline.EntityTemplateContent">
    <Name>entity name</Name>
    <TestTexture>
      <Reference>#External1</Reference>
    </TestTexture>
  </Asset>
  <ExternalReferences>
    <ExternalReference ID="#External1" TargetType="Microsoft.Xna.Framework.Graphics.Texture2D">C:\Documents and Settings\GDuckett\My Documents\Visual Studio 2010\Projects\Gravitron\Gravitron\Gravitron\bin\x86\Debug\Content\Bullet.xnb</ExternalReference>
  </ExternalReferences>
</XnaContent>

Да, мне также не нравится жестко заданный путь, но если я смогу заставить его работать без специального читателя и / или писателя для каждого типа, содержащего Texture2D, я могу жить с ним.

Ниже приведена моя версия содержимого класса (используется конвейером):

[ContentSerializerRuntimeType("Entities.Content.EntityTemplate, Entities")]
public class EntityTemplateContent
{
    public string Name;
    public ExternalReference<Texture2D> TestTexture;

    public EntityTemplateContent()
    {

    }
}

Ниже моя рабочая версия:

public class EntityTemplate
{
    public string Name;
    public Texture2D TestTexture;

    public EntityTemplate()
    {

    }
}

Проблема

Если я попытаюсь сделать var test = Content.Load<EntityTemplate>("BulletTemplate"); ниже, я получу ошибку:

Ошибка загрузки «Пуля». ContentTypeReader Microsoft.Xna.Framework.Content.Texture2DReader, Microsoft.Xna.Framework.Graphics, Version = 4.0.0.0, Culture = нейтральный, PublicKeyToken = 842cf8be1de50553 конфликтует с существующим обработчиком Microsoft.Xna.Framework.Content.ReflectiveReader`1 .Xna.Framework.Graphics.Texture2D, Microsoft.Xna.Framework.Graphics, Версия = 4.0.0.0, Культура = нейтральная, PublicKeyToken = 842cf8be1de50553]], Microsoft.Xna.Framework, Версия = 4.0.0.0, Культура = нейтральная, PublicKeyToken = 842cf8be1de50553 для типа Microsoft.Xna.Framework.Graphics.Texture2D.

Похоже, что читатель во время выполнения нашел 2 читателя для работы с активом Texture2D, читателем ReflectiveReader<Texture2D> и Texture2DReader.


Вопрос

Как я могу решить эту проблему, чтобы я в итоге получил правильно заполненный объект со свойством Texture2D, ссылающимся на загруженную текстуру?

Примечание: я не хочу добавлять другое строковое свойство и создавать метод для моего объекта с именем LoadContent или что-то в этом роде. Мне бы хотелось, чтобы Content.Load было единственной вещью, по которой мне нужно позвонить.

Я также хочу не писать своих собственных читателей / писателей для каждого типа, который содержит свойство Texture2D.

В идеале я хочу избежать создания класса-обертки для Texture2D или подкласса, но если нет альтернативы, я рад за решение, которое делает это.

1 Ответ

0 голосов
/ 16 декабря 2011

Точное сообщение об ошибке было вызвано наличием другого поля Texture2D в объекте содержимого.

Общая проблема получения ссылки на тип времени выполнения из ExternalReference<T> в типе контента была решена с помощью приведенного ниже.

В настоящее время это действительно класс для проверки концепции, поэтому он работает для классов, которые я к нему уже приложил, но, вероятно, завершится с чем-то более сложным.

Он использует отражение для преобразования любых полей или свойств ввода, которые ExternalReference<T>, во встроенные версии запрошенного типа, создав соответствующую версию ContentProcessorContext.BuildAsset<T,T> и вызвав ее. Он рекурсивно перемещается по дереву объектов, чтобы сделать то же самое для ссылок на другие объекты.

[ContentProcessor(DisplayName = "ExternalRefObjectContentProcessor")]
public class ExternalRefObjectContentProcessor : ContentProcessor<object, object>
{
    private void ReplaceReferences(object input, ContentProcessorContext context)
    {
        Func<ExternalReference<object>, string, object> BuildAssetMethodTemplate = context.BuildAsset<object, object>;
        var BuildAssetMethod = BuildAssetMethodTemplate.Method.GetGenericMethodDefinition();

        foreach (var field in input.GetType().GetFields().Where(f => !f.IsStatic && !f.IsLiteral))
        {
            Type fieldType = field.FieldType;
            object fieldValue = field.GetValue(input);

            if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(ExternalReference<>))
            {
                var GenericBuildMethod = BuildAssetMethod.MakeGenericMethod(fieldType.GetGenericArguments().First(), fieldType.GetGenericArguments().First());

                object BuiltObject;

                try
                {
                    BuiltObject = GenericBuildMethod.Invoke(context, new object[] { fieldValue, null });
                }
                catch (Exception Ex)
                {
                    throw Ex.InnerException;
                }

                field.SetValue(input, BuiltObject);
            }
            else if (fieldValue is IEnumerable && !(fieldValue is string))
            {
                foreach (var item in (fieldValue as IEnumerable))
                {
                    ReplaceReferences(item, context);
                }
            }
            else if (fieldValue != null && !(fieldValue is string))
            {
                ReplaceReferences(fieldValue, context);
            }
        }

        foreach (var property in input.GetType().GetProperties().Where(p => p.CanRead && p.CanWrite))
        {
            Type propertyType = property.PropertyType;
            object propertyValue = property.GetValue(input, null);

            if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(ExternalReference<>))
            {
                var GenericBuildMethod = BuildAssetMethod.MakeGenericMethod(propertyType.GetGenericArguments().First(), propertyType.GetGenericArguments().First());

                object BuiltObject;

                try
                {
                    BuiltObject = GenericBuildMethod.Invoke(context, new object[] { property.GetValue(input, null), null });
                }
                catch (Exception Ex)
                {
                    throw Ex.InnerException;
                }
                property.SetValue(input, BuiltObject, null);
            }
            else if (propertyValue is IEnumerable && !(propertyValue is string))
            {
                foreach (var item in (propertyValue as IEnumerable))
                {
                    ReplaceReferences(item, context);
                }
            }
            else if (propertyValue != null && !(propertyValue is string))
            {
                ReplaceReferences(propertyValue, context);
            }
        }
    }

    public override object Process(object input, ContentProcessorContext context)
    {
        ReplaceReferences(input, context);

        return input;
    }
}
...