- Home /
Handling change in Class variables with saving ScriptableObject
I've been playing with saving Scriptable Objects using binary serialization. This is the loading from file code:
public static T ReadFromBinaryFile<T>(string filePath)
{
using (Stream stream = File.Open(filePath, FileMode.Open))
{
var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
return (T)binaryFormatter.Deserialize(stream);
}
}
Then using this class (short version):
using UnityEngine;
public class Item : ScriptableObject
{
[SerializeField]
int value;
}
My apprehension in using this for things like my Item database, is that I don't see how I could implement an additional variable to my classes and then still be able to load the old data and save with the new variables. When I change this class to add, for example, a float durability;
, this doesn't match the saved file anymore and this has so far just wiped my database. Is there a standard way to solve this?
So far I've been trying out adding a version number to a save and comparing the different classes, but then I get into having to write extensive 'conversion scripts' which put the data from the old class versions into new ones and all this also means I need to keep copies of my old classes around etc. Which I'd really prefer not to.
Could you provide a little more concrete detail on what you're trying to and and what the problem is? Whenever you add a new serialized field to a ScriptableObject, it will just get the default value from its field initializer if there is not data for it on disk. So assume I have this class:
using UnityEngine;
[CreateAsset$$anonymous$$enu]
public class Item : ScriptableObject {
[SerializeField]
private int m_Value;
}
Then I create a bunch of assets and realize I want to add a field for weight:
using UnityEngine;
[CreateAsset$$anonymous$$enu]
public class Item : ScriptableObject {
[SerializeField]
private int m_Value;
[SerializeField]
private float m_Weight = 2f;
}
Now, all items will automatically have a default weight of 2 if there is no value manually serialized for it (i.e. you did not manually select the item asset, change the weight, and save the project).
Thanks, I updated the question with more data. So you're saying I should just save it in binary with one class type and then just load it back into another class?
So what you are doing is basically what Unity does in native code when we change the serialization layout of something (i.e. bump version number and add some logic to handle changes), but I guess I would ask why you need to maintain your item database as external, binary-serialized data in the first place?
i removed a comment about scriptableobject which isn't valid enough
use the JsonUtility class in unity, and then you can serialise your item database into a nicely formated json string, write that to a file, and then back again into your program... you can add any number of extra properties from that point on, and keep bring data set's forward through build iterations. trim$$anonymous$$g excess properites can be a little annoying, growing is always easy with that helper class
Answer by Adam-Mechtley · Mar 30, 2017 at 07:03 AM
If you are using ScriptableObjects to define your items, then you should just save them as assets in your project, and your database should just be another ScriptableObject asset in your project that serializes references to them. (You can also use AssetDatabase.AddObjectToAsset() to make the items children of the database asset, if it makes them easier to manage.)
It would look something like this (note you can of course just make your serialized fields public if you want to go that route and trust all consumers of your class):
// in Item.cs
using UnityEngine;
[CreateAssetMenu]
public class Item : ScriptableObject {
public int Value { get { return m_Value; } set { m_Value = value; } }
[SerializeField]
private int m_Value;
public float Weight { get { return m_Weight; } set { m_Weight = value; } }
[SerializeField]
private float m_Weight;
// and so on...
}
// in ItemDatabase.cs
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class ItemDatabase : ScriptableObject {
[SerializeField]
private List<Item> m_Items = new List<Item>();
public int Count { get { return m_Items.Count; } }
public Item this[int index] { get { return m_Items[index]; } }
// and so on...
}
The whole point of ScriptableObjects is basically to allow you to serialize and save persistent, arbitrary data in your project, so the extra step of dumping the data into another format—whether binary or JSON—is unnecessary. Another benefit of this approach is that you can select multiple ScriptableObjects at once in the Editor and edit them at the same time. As I noted in my comment above, adding a new field then simply becomes a matter of applying a default value in the field initializer, and you can then quickly edit several items together when you need to apply different values to them.
there's a big difference in our requirements which is now much clearer..
I have to be able to save data into a transportable format, and i can't store the data inside unity, and I have to avoid as much as possible complex unity objects, and it all must work at runtime, not in the editor.... :D
so this has been useful to see. thanks for sharing
Yes then in that case you are best off using a custom serializable type in conjunction with the JsonUtility class. Something like this:
using System;
using UnityEngine;
[Serializable]
public class Item {
public int Value { get { return m_Value; } set { m_Value = value; } }
[SerializeField]
private int m_Value;
// and so on...
}
:D exactly.... would you be inclined to convert my comment which states that into an answer? I could just delete my comment and post it again as an answer, obviously, but this has been a useful discussion
Answer by dvandamme · Mar 30, 2017 at 05:22 AM
jsonutility is just a serialiser that works nicely for converting a dataset into a big string, and then you use a filewriter to send that string to a file... your data structure (the item database) should be entirely separate to this process. FileIO and object manipulation shouldn't live in the same class all but the most tiny projects...
If you use the jsonutility, then growing the structure over time is not even a thing to worry about. The api will do all the heavy lifting for you and you can just worry about your datastructure doing what you want it to do. .
this is the entire datastructure i use for a full application that is saved to file by the user, and allows drawing lines with various colours and thickness, making user notes on their drawing, placing any number of 3d objects in 3d space, and a few other things. that gets churned into json for saving, but its i can be a 10,000 item datastructure without any effor or slowdown in processing
[Serializable]
public class DataStructures
{
[Serializable]
public class DataSet
{
public List<Item> items = new List<Item>();
// public List<Item> lines = new List<Item>();
//public List<Item> nodeConnections = new List<Item>();
public DataSet()
{
}
}
[Serializable]
public class BasicStruct
{
public string id;
}
[Serializable]
public class Item : BasicStruct
{
public string prefabReference;
public string value;
public Vector3 position;
public Quaternion rotation;
public Vector3 point1;
public Vector3 point2;
public Color color;
public float width;
public List<string> links = new List<string>();
public Item()
{
links = new List<string>();
position = Vector3.zero;
rotation = Quaternion.Euler(Vector3.up);
}
}
}
and then i use this to write to a file
string jsonfile = JsonUtility.ToJson(datset); //
using (StreamWriter writer = new StreamWriter(filepath, false))
{
writer.Write(jsonfile );
}
Your answer
Follow this Question
Related Questions
Drag from custom editor ObjectField 0 Answers
Is there any way to associate a "custom asset" with a particular file type? 1 Answer
How do I associate my custom object for the serializedProperty objectReferenceValue 1 Answer
Scriptable Objects, how to force Include in Compile / Build without referencing it in the scene? 1 Answer
Custom Editor Script Sets Scene as Dirty Regardless of Changes 1 Answer