- Home /
How to serialize/deserialize a class without knowing its type
Ok so my goal is to replicate the modability of Kerbal Space Program. In that game each part is defined by a config file that lists all the PartModuals and their variables (PartModuals being scripts extended from their PartModual script). Whats more is that as long as you extend from their PartModual class, you can add your own custom scripts and then add them to parts in the same way in the configs.
I have been trying to do this in XML. If I specifically name the class:
var AClassIcanSave : ThatClass = ThatClass();
I can serialize that, however if I try to use a child of that class it throws an error telling me the class I am trying to save is not a primitive:
class BetterClass extends ThatClass
{
var betterFactor : float = 10;
}
var AClassIcanSave : ThatClass = BetterClass();
I also tried:
var AClassIcanSave = BetterClass();
and
class BetterClass extends ScriptableObject
{
var betterFactor : float = 10;
}
var AClassIcanSave : ScriptableObject = BetterClass();
Which also fail with the same error.
How can I serialize a class without specifically referencing it so that I can save and load any class that my potential modders could create?
You might have better luck on StackOverflow. That being said, I have some experience with serialization and all the problems that it brigns when done in Unity, so if you can give me an elaborate example of how the module system works in terms of classes involved, maybe I can help you.
@CiberX15: I'm a bit confused what you actually want. Do you want:
to have Unity to serialize the class in the editor (Unity's serialization system)?
to serialize / deserialize the class at runtime. So you want to store a class in a binary / text form somewhere?
to create an instance of an unknown class at runtime.
Since on the one hand you talk about serialization and on the other about modding you should clarify your question. Where in your code is the serialization part you talked about? Have you tried to use the XmlSerializer or something else?
$$anonymous$$eep in $$anonymous$$d that you can't serialize $$anonymous$$onoBehaviour or ScriptableObject classes with normal .NET serialization methods. If you need to serialize / deserialize $$anonymous$$onoBehaviours
So to break down my ultimate goal, I want to have an xml file that stores a list of classes for an object and their public variables, so that my load script can add that script to a gameobject at runtime and set all of the variables. For example:
There might be a generic "block" script that defines how all placeable blocks work in the game, but each block would have differing health such as Bricks would have 100 health but wood planks would have 50. So when the load read the xml for the wood planks, it would see it needs to add the block script and set its health variable to 50;
I don't necessarily need to serialize the whole script but it seemed like the easiest way to save/load variables when I don't know how many variables the script may have nor what those variables would be named since I wont necessarily be the one writing the script. $$anonymous$$odders for example may want to add new scripts that can be added like the block script in the example above after my code has already been compiled, so it would be impossible for me to reference variables in their code by name.
The examples I gave above are pretty much pseudo code. I didn't have enough time when I was writing the question to use my real code because its currently a mess with all the random things I tried and I thought it would make things more confusing. I will try to add the real working code when I get the chance.
I am using XmlSeralizer
I am aware that I cannot serialize $$anonymous$$onoBehaviors although I read somewhere else that you could serialize ScriptableObjects? In any case my workaround for that was to create a monobehavior class that could store a "SerialScript" class which looked something like this:
class SerialScript
{
protected var $$anonymous$$yObject : GameObject;
function Become(me : GameObject)
{
$$anonymous$$yObject = me;
}
function Start()
{
}
function Update()
{
}
}
The mono monobehavior class would call start and update on SerialScript and all subclasses and also set the $$anonymous$$yObject variable so that the serialscript and all subclasses would have access to the game object at runtime. The SerialScript and its subclasses then would be what would be serialized, and the monobehavior could be hardcoded because it could run any serialscript.
The problem I am running into is it lets me save serialscript to xml, but if I try to save a subclass of serial script, it compiles fine but throws an error at runtime when it actually tries to save it.
Well, i don't know where is should start to answer this question as you don't seem to know what you actually want ^^. ScriptableObject and $$anonymous$$onoBehaviour derived classes share the same limitations. They are serialized only in the Unity editor by their own serialization system. The only reason to use a ScriptableObject would be to have it serialized in the editor at edit time.
So, if you want to provide an interface that can be used by your players to create subclasses you shouldn't use $$anonymous$$onoBehaviour or ScriptableObject as base class. Designing a mod API isn't that easy as you have to think about what things a user should be able to access.
Also do you really plan to create your project with Unityscript? You know that users can't write scripts that you can load at runtime as the UnityScript compiler is part of the UnityEditor. You can't even create a DLL project in UntiyScript. (well you can indirectly with the UnityEditor but that might be even too complicated for your users ^^).
The XmlSerializer can only be used to serialize / deserialize known classes as you have to specify the type / fields / attributes in your code. An alternative way is to use Reflection and create a class instances based on the class name given as string. Saving would work the same way. I certainly will not provide example code in UnityScript as it's a pain to work with that language ^^. Could you at least provide something to work with? The script you provided (SerialScript) doesn't contain any data that could be serialized. The only thing you could serialize is the type of the class ^^.
Answer by CiberX15 · May 09, 2015 at 05:19 PM
Figured it out!
The Load Script:
#pragma strict
import System.Xml;
import System.Xml.Serialization;
import System.Collections.Generic;
// Import all the stuff we need
var LoadObject : String; //Name of the thing we want to load
//Main Load Function
function Load()
{
var serializer : XmlSerializer = new XmlSerializer(SerialObject);
var stream : Stream = new FileStream(Application.dataPath + "/StreamingAssets/" + LoadObject + ".xml", FileMode.Open);
var ObjToLoad : SerialObject = serializer.Deserialize(stream) as SerialObject;
stream.Close();
var NewObject : GameObject = Resources.Load("1x2Brick") as GameObject;
NewObject = Instantiate(NewObject, new Vector3(0, 0, 0), Quaternion.identity);
NewObject.name = ObjToLoad.SerialName;
for(var i = 0; i < ObjToLoad.scripts.Count;)
{
var NewScript = NewObject.AddComponent(ObjToLoad.scripts[i].SerialName);
for(var j = 0; j < ObjToLoad.scripts[i].SerialVars.Count;)
{
var NewVar : SerialVar = ObjToLoad.scripts[i].SerialVars[j];
NewScript.GetType().GetField(NewVar.SerialName).SetValue(NewScript, NewVar.Value);
j++;
}
i++;
}
}
// Data Classes
public class SerialObject
{
@XmlAttribute("Name")
public var SerialName : String;
@XmlArray("ClassList")
@XmlArrayItem("Script")
public var scripts : List.<SerialScript> = new List.<SerialScript>();
}
public class SerialScript
{
@XmlAttribute("Name")
public var SerialName : String;
@XmlArray("Variables")
@XmlArrayItem("var")
public var SerialVars : List.<SerialVar> = new List.<SerialVar>();
}
public class SerialVar
{
@XmlAttribute("Name")
public var SerialName: String;
public var Value;
}
The Config it is loading (saved as MudBrick.xml) :
<?xml version="1.0" encoding="Windows-1252"?>
<SerialObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="ExampleObject">
<ClassList>
<Script Name="Example">
<Variables>
<var Name="Speed"><Value xsi:type="xsd:float">0.1</Value></var>
<var Name="Rot"><Value xsi:type="xsd:float">64</Value></var>
</Variables>
</Script>
</ClassList>
</SerialObject>
The script the Config is referencing:
#pragma strict
public var Speed : float;
public var Rot : float;
function Start ()
{
}
function Update ()
{
transform.position += Vector3(Speed,0,0);
transform.rotation = Quaternion.EulerAngles(0,Rot,0);
}
So what this is doing is the load function is given the name of the xml file I want to load. The first variable in the xml file is the name of the script. This is easily used to create an instance of that script by name with the gameobject.addcomponent("nameofscript") function. This allows for easy loading of monobehaviors.
The next part is more tricky. Using reflection the line "NewScript.GetType().GetField(NewVar.SerialName).SetValue(NewScript, NewVar.Value);" references public variables in the newly instance monobehavior and set them based on the values found in the xml. That said reflection is slow so my plan is to create a list of gameobjects using the xml on initial load and then I can instance them from the list as needed at runtime.
Thanks everyone for the nudge in the right direction. Hope this helps anyone trying to do a similar task.
Just updated to Unity 5 and they no longer support AddComponent("String") which I used above to load the script from config. Easy workaround is simply use Type.GetType("String") as such:
var NewScript = NewObject.AddComponent(Type.GetType(ObjToLoad.scripts[i].SerialName));
ins$$anonymous$$d of
var NewScript = NewObject.AddComponent(ObjToLoad.scripts[i].SerialName);
Your answer
Follow this Question
Related Questions
Save and load system not working please help 1 Answer
A node in a childnode? 1 Answer
Differences between mobile save/load data 1 Answer
How To Make Settings File Over XML? 1 Answer
How do I load an xml file? 1 Answer