Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Get all objects that inherit from another class?

Discussion in 'Scripting' started by KyleStank, May 19, 2016.

  1. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    I am making a system in my game where a modder will be to add a "Action" to the game with a Add method. I haven't added the Add method in yet thought, because I have ran into a problem. Every "Action" is inside of an XML file with configurable settings. This works fine if I wasn't allow modding, but since I have to do [XmlInclude(typeof(TYPEHERE))], it doesn't work with modding. The reason I need these lines is because the XmlSerializer class doesn't work without them because of the inheritance going on.

    So basically, how would I do [XmlInclude(typeof(EveryChildOfActionClass))]

    Here is my code, so you know what I mean:
    Code (csharp):
    1.  
    2. [XmlInclude(typeof(Throw))]
    3.     public class Action {
    4.         [XmlAttribute("name")]
    5.         public string name;
    6.  
    7.         public Action() { } //Blank constructor
    8.  
    9.         public Action(string name) {
    10.             //Set up the action's settings
    11.             this.name = name;
    12.         }
    13.  
    14.         public string GetName() { //Returns name of the action
    15.             return name;
    16.         }
    17.     }
    This is what I mean. At the very top, you see XmlInclude, and then a specific type. Well, if I am allowing modder to create their own classes, I wont know every single type that is in the game. So how do I add multiple XmlInclude lines, and at the same time have them not be specifically typed. If I need to explain, please let me know.

    Here is some more code:
    Code (csharp):
    1.  
    2. public class Throw : Action {
    3.         [XmlElement("throw_power")]
    4.         public float throwPower;
    5.  
    6.         public Throw() { } //Blank constructor
    7.  
    8.         public Throw(string name, float throwPower) : base(name) {
    9.             //Set up the action's settings
    10.             this.name = name;
    11.             this.throwPower = throwPower;
    12.         }
    13.  
    14.         public float GetThrowPower() { //Returns name of the action
    15.             return throwPower;
    16.         }
    17.     }
    Notice that this is how an "Action" gets created. A class just inherits from the Action class. If modders create their own class, it will be impossible to know what it is called for the XmlInclude line.

    Code (csharp):
    1. [XmlRoot("action_pool")]
    2.     public class ActionPool {
    3.         [XmlArray("actions")]
    4.         [XmlArrayItem("action")]
    5.         public List<Action> actions = new List<Action>();
    6.  
    7.         public void AddDefaultActions() {
    8.             actions.Add(new Throw("Throw", PlayerManager.Instance.settings.interactSettings.throwPower));
    9.         }
    10.     }
    And this is what the XML file looks like:
    Code (csharp):
    1. <action_pool xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    2.   <actions>
    3.     <action xsi:type="Throw" name="Throw">
    4.       <throw_power>20</throw_power>
    5.     </action>
    6.   </actions>
    7. </action_pool>
     
    Last edited: May 19, 2016
  2. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    Bump... Please help out. I am soo stumped right now Lol.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    Really your problem is that you need the xml serializer be able to recognize types that you yourself don't even know yet, because they will be loaded in, in post.

    ...

    OK, well I don't know the XmlSerializer very well... If I'm using the .net library for my serialization (which it has many serializers, many of which don't interoperate... ugh) I primarily use the System.Runtime.Serialization namespace, primarily the BinaryFormatter:
    https://msdn.microsoft.com/en-us/li...matters.binary.binaryformatter(v=vs.110).aspx

    I like this namespace because it allows tons of tweaking of the serialization pipeline via custom formatters, and hooks like IDataContractSurrogate, IDeserializationCallback, ISerializationSurrogate, and ISurrogateSelector.

    Anyways, by default these serializers can infer any class type when serializing or deserializing without having to do all this 'XmlInclude' nonsense. As long as the type is loaded into the Application Domain it can figure out what it is (this happens when you load up the dll's that contain the custom scripts you allow modders to write).

    So yeah, I'd probably suggest not using the XmlSerializer...

    If you don't like the binary format of the resulting data, you can write a custom formatter to support something else (.net only comes with binary, and soap a subset of xml). I recently wrote a json one, as well as one to support grfon (a format design by another user here on the forums). I can dig them up if you're interested.
     
  4. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    I'm using XmlSerializer for a reason. It does sooo much of the work for me. I wrote my own LoadXML function that will load an XML file if it exists, and if it doesn't exist, then it will create an XML file that is properly made. Using Binary is also not what want at all for a list of every action. You will be able to view every action from the file.

    There are also other files such as "PlayerSetting.xml" which contains data such as the player walk speed. And yes, I am letting players modify this. It fits the type of game I'm making.

    So Binary files are just not what I want lol.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    Thing is, there's no way to tell XmlSerializer about these types at compile time, if you don't know those types exist yet. Unless there's some other way to inform it in post, but as far as I know it does not (I also don't like the fact it doesn't let you control what gets serialized or not, it only serializes public fields... ugh).


    Here's an implementation of IFormatter for XML:
    http://www.codeproject.com/Articles/15639/XMLFormatter-provider-for-serialization
     
  6. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    So actually I solved this problem by using generics. This isn't the best way, but it definitely isn't the worse by far. I am going to show you quite a lot of code so you can see what I'm doing. Basically, there is a ListAsXML method that will serialize OR deserialize an XML file to/from a list.

    Here is the method:
    Code (csharp):
    1. //Serializes/Deserializes a list to/from an XML file
    2.         public static List<ListType> ListAsXML<ListType>(List<ListType> list, bool serializing, string directory, string fileName, string rootName = null) {
    3.             string path = directory + fileName; //Combines both paths
    4.             CreateDirectory(directory); //Creates directory if it doesn't exist
    5.  
    6.             List<System.Type> types = new List<System.Type>();
    7.  
    8.             foreach(ListType obj in list) { //Loops through every item in the list
    9.                 System.Type type = obj.GetType();
    10.  
    11.                 if(!types.Contains(type)) //If types list doesn't have the current object's type on it
    12.                     types.Add(type); //Add current object's type to the list
    13.             };
    14.  
    15.             XmlSerializer serializer; //Initialize serializer
    16.  
    17.             if(rootName == null) //If root name wasn't given
    18.                 serializer = new XmlSerializer(typeof(List<ListType>), null, types.ToArray(), null, null); //Create with default root name
    19.             else
    20.                 serializer = new XmlSerializer(typeof(List<ListType>), null, types.ToArray(), new XmlRootAttribute(rootName), null); //Create with given root name
    21.  
    22.             /* Serilization */
    23.             if(serializing) {
    24.                 using(FileStream stream = File.Open(path, FileMode.Create)) //Automatically closes the file after it's done being used
    25.                     serializer.Serialize(stream, list); //Serializes data to the file
    26.  
    27.                 return list;
    28.             }
    29.             /* Deserilization */
    30.             else {
    31.                 if(File.Exists(path)) { //Makes sure XML file exists
    32.                     using(FileStream stream = File.Open(path, FileMode.Open)) //Automatically closes the file after it's done being used
    33.                         return (List<ListType>)serializer.Deserialize(stream); //Deserializes the data and returns it
    34.                 } else
    35.                     return list;
    36.             }
    37.         }
    See? It is quite a bit of code(not that bad though), and there is even a little more code that is being run, but it isn't too important to be shown. Somehow, I got this to work from using Google, and my mind. This has been the biggest stump I have ever came across, but I have also learned quite a lot lol.

    Thank you for trying to help though, I really do appreciate it. This final method is like a dream come true, because it gets rid of the need to say [XmlInclude()], which really is non-sense as you said ha ha.