Search Unity

Any way to XML-serialize GameObjects?

Discussion in 'Scripting' started by by0log1c, Jun 3, 2011.

  1. by0log1c

    by0log1c

    Joined:
    Jan 30, 2011
    Posts:
    40
    Hey guys,

    so I've had to notice there's no way to directly serialize a GameObject to XML. If I remember correctly, it actually spits an error about Transform not being serializable. I've asked about it and been told there was no way to override Unity's Transform class, something I'd avoid to do anyway, as I'd break something ;). So I've worked around using GameObject Pool and customIDs. I dislike having to do that and I'm thinking one of you might have a better solution.

    The goal is simple: being able to create objects of a custom class, and include GameObjects if I damn need to, rather than make sure to include no GameObject nor Transform in the class I intend to XML-serialize later :confused:.

    Any hints are welcome. Thanks!
     
  2. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    You can do it manually via reflection.

    Instead of relying on an XML Serializer, just use an XML writer reader to create and serialize objects.
    I've got a really good one(all base value types + lists/arrays/enums + any other structure recursively down to base types), but sadly am unable to share. Although if you write the serializer you could avoid having to structure out the extra syntax and build something that reads your own formatted set to create it's objects. It can sound daunting, but it can be pretty compact (about 300 lines of code) and is much more portable (the other .NET serialization can be potentially kinda buggy on iOS) if you rely on the reflection namespace to do the complicated work.
     
  3. bdev

    bdev

    Joined:
    Jan 4, 2011
    Posts:
    656
    Most likely you'll have to take the approach of writing this functionality externally via utility class. I'm not sure what you mean by gameobject pool and custom ids. Are you trying to have it so xml declares parsable types which build a game object and or its children?

    Basically when it comes to transform you'll want to write just the localPosition, localRotation, and localScale. If your trying to store an hierarchy you'd do something like this
    Code (csharp):
    1.  
    2. static void Write(Transform self, XML write ) {
    3.     Write local position, rotation and scale as attributes or whatever.
    4.     foreach( Component component in self.gameObject.GetComponents() ){
    5.          writer.BeginElement("component");
    6.          // write the component type at minimum.
    7.          writer.EndElement();
    8.     }
    9.  
    10.     foreach(Transform child in self){
    11.         write.BeginElement("transform");
    12.         Write(child, write);// recurse.
    13.         write.EndElement();
    14.      }
    15. }
    16.  
    17. static Transform Read(XML xml)
    18. {
    19.      System.Type[] components;
    20.      {
    21.          List<System.Type> componentNames = new List<string>();
    22.          Parse all component elements out of the xml storing their type into the above list.
    23.          components = componentNames.ToArray();
    24.      }
    25.      GameObject go = new GameObject( xml.Attribute("name"), components );
    26.      Transform self = go.transform;
    27.      // set local position, rotation, and scale from xml.
    28.      foreach( element named transform in xml )
    29.          Read( element ).parent = self; // recurse
    30.      return self;
    31. }
    32.  
    33.  
    don't know how helpful this is. But your fighting a few things when you do this this way. Remember Awake message gets called similar as a constructor. So if your componts require the transform to be positioned prior to their awake so, you wouldnt be able to use the GameObject constructor like above, instead you'd need to gameObject.AddComponent.

    Generally i'd suggest avoiding the need to do somthing like this because the amount of maintenance required. But you know what your doing.

    The other approach would be to use reflection. But i dont know how much of that is accessible on our side of the script.
     
  4. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    I realize I was a bit of a showoff in the first post without actually helping much :(

    Basically I structure my XMLs like this:

    Code (csharp):
    1.  
    2. <GameObjects>
    3.     <ObjName1>
    4.         <Transform>
    5.             <Position>
    6.                 <x>10</x>
    7.                 <z>5</z>
    8.             </Position>
    9.         </Transform>
    10.         <MyComponent>
    11.             <ABool>True</ABool>
    12.             <SomeString>Hey Look At This</SomeString>
    13.         </MyComponent>
    14.     </ObjName1>
    15.     <ObjName1>
    16.         <MyComponent>
    17.             <ABool>False</ABool>
    18.             <SomeClass>
    19.                 <InnerVariable>10.5</InnerVariable>
    20.                 <SomeList>
    21.                     <Int>2</Int>
    22.                     <Int>5</Int>
    23.                 </SomeList>
    24.             </SomeClass>
    25.         </MyComponent>
    26.     </ObjName1>
    27. </GameObjects>
    28.  
    I only store modified values, and create a property for each value I want to edit. If something is set to a default value, I ignore it because it will be set automatically. List children just have the type associated with them as their name, though that name is not actually necessary, so long as the value is there.

    The basic steps involved to read this out are:

    First Spawn a new GameObject and find the proper element in the XML for that GameObject
    Then us System.Assembly.GetExecutingAssembly().GetType([The Component Name of the inner Entry]).
    http://msdn.microsoft.com/en-us/library/system.reflection.assembly.aspx

    Then using AddComponent(Type type) add it to the gameobject. And store it around for later use, that will help you determine the underlying tyes for later use.

    This is where it gets more complicated.
    http://msdn.microsoft.com/en-us/library/kz0a8sxy.aspx
    GetProperty will get you a PropertyInfo object of the child node based on the type of the parent node

    Using that PropertInfo object you can just call SetValue with the parent object and a System.Object type created based on what you want to support. Using Boxing and Unboxing, you convert to what you need, set it to Object and then when it gets set internally it will sort itself out. This is improvable if you store something in bytes rather than strings for avoiding the need to parse.

    Supporting of different types consists of Base Types (float, int, bool), Lists, any necessary generics, enums (easy with Enum.Parse), and repeating earlier processes with more complicated types until they turn into the base types. The functionality becomes recursive and compact, with the child being set into the parent, up until it all gets connected to the Component.

    This method allows you to automatically support all built in Components, allows you to store any value normally edittable in the inspector (and more), and will automatically support any subsequent types (barring the one liners for base types) once implemented. Also it is iOS safe, which is not always the case with the built in serializers.