【问题标题】:Unity3D & YamlDotNet Deserializing Data into Monobehaviour-derived classesUnity3D 和 YamlDotNet 将数据反序列化为 Monobehaviour 派生类
【发布时间】:2015-11-04 22:23:52
【问题描述】:

我正在尝试将数据序列化到/来自我的类​​,这些类派生自 MonoBehaviour,它不能从客户端代码创建(例如,使用 new 关键字),而是必须由特定于 Unity3D 的创建方法,GameObject.AddComponent<T>()。如何使用YamlDotNet 框架为我的类填充值,而不必为每个类创建一个适配器?是否有某种我可以配置的内置适配器,这样 YamlDotNet 不会实例化它尝试序列化的类?

一个典型的文件可能包含项目的映射,例如,

%YAML 1.1
%TAG !invt! _PathwaysEngine.Inventory.
%TAG !intf! _PathwaysEngine.Adventure.
---

Backpack_01: !invt!Item+yml
  mass: 2
  desc:
    nouns: /^bag|(back)?pack|sack|container$/
    description: |
      Your backpack is only slightly worn, and...
    rand_descriptions:
    - "It's flaps twirl in the breeze."
    - "You stare at it. You feel enriched."

MagLite_LR05: !invt!Lamp+yml
  cost: 56
  mass: 2
  time: 5760
  desc:
    nouns: /^light|flashlight|maglite|lr_05$/
    description: |
      On the side of this flashlight is a label...

      (Type "light" to turn it on and off.)

...

标签是我的项目的完全指定的类名,例如,PathwaysEngine.Inventory.Lamp+ymlPathwaysEngine 是我用于我的游戏引擎代码的命名空间,Inventory 处理项目和诸如此类的东西,Lamp+yml 是编译器如何表示嵌套类,ymlLamp 内。 Lamp+yml 可能看起来像这样:

public partial class Lamp : Item, IWearable {
    public new class yml : Item.yml {
        public float time {get;set;}
        public void Deserialize(Lamp o) {
            base.Deserialize((Item) o);
            o.time = time;
        }
    }
}

我对从Awake() 派生的所有对象调用Deserialize(),即一旦游戏中存在MonoBehaviour 类。在其他地方,我已经创建了一个非常复杂的Dictionary,其中填充了Someclass+yml 类型的对象,然后Deserialize 获取了一个真实的运行时类Someclass 的实例并用值填充它。必须有一种更清洁的方法来做到这一点,对吧?

我该怎么做:

  1. 告诉Deserializer我的课程是什么? 有关上述问题的良好解决方案,请参阅第二次编辑

  2. 在不尝试创建我的MonoBehaviour 派生类的情况下获取数据?

编辑:我已经解决了这个问题,并且发现了一种处理自定义数据的好方法(在我尝试从我的数据中解析正则表达式的特殊情况下,并且有它们不被视为字符串,因此不可转换为正则表达式)是为该特定字符串使用IYamlTypeConverter。但是,将 YamlDotNet 与 Unity3D MonoBehaviours 一起使用仍然是一个问题。

另一个编辑:上面的例子使用了一种非常丑陋的方式来确定类型。就我而言,最好的办法是先用反序列化器注册标签,例如,

var pre = "tag:yaml.org,2002:";
var tags = new Dictionary<string,Type> {
    { "regex", typeof(Regex) },
    { "date", typeof(DateTime) },
    { "item", typeof(Item) }};
foreach (var tag in tags)
    deserializer.RegisterTagMapping(
        pre+tag.Key, tag.Value);

然后,我在*.yml 文件中使用!!tag 表示法,例如,

%YAML 1.1
---

Special Item: !!item
  nouns: /thing|item|object/
  someBoolean: true

Start Date: !!date 2015-12-17

some regex: !!regex /matches\s+whatever/

...

【问题讨论】:

  • 通常序列化框架有一些自定义的方法来初始化东西。我从未使用过 yaml,但如果没有办法解决这个问题,最好的办法是使用 MonoBehaviour 作为另一个类的包装器。您只需序列化内部类,然后自己设置 MonoBehaviour
  • 是的,这基本上就是我对嵌套的yml 类所做的。

标签: unity3d yamldotnet


【解决方案1】:

您可以将IObjectFactory 的自定义实现传递给Deserializer 类的构造函数。每次反序列化器需要创建一个对象的实例时,它都会使用IObjectFactory来创建它。

请注意,您的工厂将负责创建反序列化的每个类型的实例。最简单的实现方式就是围绕DefaultObjectFactory创建一个装饰器,比如:

class UnityObjectFactory : IObjectFactory
{
    private readonly DefaultObjectFactory DefaultFactory =
        new DefaultObjectFactory();

    public object Create(Type type)
    {
        // You can use specific types manually
        if (type == typeof(MyCustomType))
        {
             return GameObject.AddComponent<MyCustomType>();
        }
        // Or use a marker interface
        else if (typeof(IMyMarkerInterface).IsAssignableFrom(type))
        {
             return typeof(GameObject)
                 .GetMethod("AddComponent")
                 .MakeGenericMethod(type)
                 .Invoke();
        }
        // Delegate unknown types to the default factory
        else
        {
            return DefaultFactory(type);
        }
    }
}

【讨论】:

  • 虽然这很有帮助,但我的问题是我想将我的项目和其他东西放在编辑器中,所以它们已经存在于场景中,我的类已经添加为组件。这可能使我正在使用的黑客成为必要。无论如何,这可能会对人们有所帮助,我可能会接受这个答案并修改我的问题,如果我无法弄清楚,请询问另一个人,等等。无论哪种方式,感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-05
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-17
相关资源
最近更新 更多