I’m creating a save system, reading and writing data to a json file. Some of the data are scriptableobjects, so I was wondering if that would work as is.
Everyone seems to tell me that when serializing scriptableobjects, they will be serialized in their entirety, but that’s not my experience, I noticed that when I serialize one, and then load it again (in the editor, haven’t tried in a build), it points straight to the one in my project, as if it serialized the reference to the object in my project (or its guid?), rather than everything in it.
Which is fine by me, and at first it seemed to work fine, but now it seems that the smallest change I make in the project, makes the data being loaded complete nonsense, I assume because the guids get changed constantly, and so it takes the wrong object.
How should I fix this?
I’m thinking of loading all scriptable objects through Resources (by storing the path to it), is that a good solution or should I do it differently?
(I already do it this way for saving and loading images)
You can’t serialise reference to Unity objects - or the objects in their entirety - to disk and deserialise that back. It’s just not supported.
There’s a number of workarounds and it’s really just a case of using one that suits your project. Substituting data and reversing that is one option, or using inherently indirect references is another (as in, rather than a direct reference to an Unity objects, you store some unique ID that can be used to look up from your own database of sorts).
Where did you hear this from, I have to ask? What serialiser where you using, and what sort of guid was written out? The JsonUtility writes out the InstanceID of a Unity object, which is not consistent across application sessions.
Also, I read that in a ton of different forum threads here and on stackoverflow, but that was weeks ago, so I can’t remember exactly.
Do you think my solution of loading it through Resources is a good idea or are there drawbacks?
The only bad thing about it AFAIK is that it loads data from a path, which is slower.
Yeah via JsonUtility you’ll only get the InstanceID, which is generate per application runtime. It won’t be consistent across application sessions.
I mean the question will be how to you convert these references to paths at runtime? Unless you reference everything via paths, which would be hell development-wise. This is on top of the other issues that step from using Resources.
The more common approach is to give these scriptable objects a unique ID, and to write that to disk instead. Then, it can be used to look up the ID from your own simple databases when deserialising. Using the name of an asset is another, lazier and more fragile option, but requires less work to implement.
I would highly suggest using a more capable serialiser, such as Unity’s serialisation package, Newtonsoft.JSON (also available via the package manager), or the Odin serialiser. These all will have tools in their API that let you convert data back and forth when serialising/deserialising, so that you don’t need serialisable surrogate classes for everything that has a reference to a Unity object.
Yeah, I underestimated how complex it would get, not only that, but my sciptableobjects contain references to prefabs, so it wouldn’t work.
So I changed it to having a big list of these scriptableobjects, and serializing the index in the list (which I think is what you were suggesting)
The downside is I now always have all of these loaded in memory (might not be such a bad thing though, but it could get big), and also, if I change the order in the list, the save file will be incorrect again
Give your scriptable objects a unique identifier. This should be serialised into the scriptable object itself, so it can be read from and compared to as needed during runtime. For example, this could be a number that is randomly generated when the scriptable object is created.
Encapsulating the ID into a reusable class/struct, alongside abstracting it’s implementation via an interface is also a good way to share the implementation across any number of scriptable object and potentially monobehaviour assets.
It’s not too complicated. It’s just made up of a number of smaller parts.
An ultra basic implementation of a UUID might look like this:
[System.Serializable]
public struct UUID : IEquatable<UUID>
{
[SerializeField]
private int _uuid;
public static UUID NewUUID()
{
int value = System.Security.Cryptography.RandomNumberGenerator.GetInt32(0, maxValue);
UUID uuid = new()
{
_uuid = value;
};
return uuid;
}
public bool Equals(UUID other)
{
return _uuid == other._uuid;
}
public override int GetHashCode()
{
return _uuid.GetHashCode();
}
}
public interface IHasUUID
{
UUID UUID { get; }
}
public class ExampleScriptableObject : ScriptableObject, IHasUUID
{
[SerializeField]
private UUID _uuid = UUID.NewUUID();
public UUID UUID => _uuid;
}
Though the UUID struct can be expanded upon by a lot.
Then most serialisers have an API to ‘convert’ data. Such as when it tried to serialise a Unity object, you can make it serialise the UUID instead. And then on deserialisation, it can read the UUID and replace the value with the asset again. Though going into how to make your own simple asset look up system is beyond a forum post.
This is an important question indeed. You need to ask yourself how many scriptable objects of varying types you intend to save to disk. If it’s a tiny or small amount, then probably not. If it ends up being a lot, then I’d say a robust system is definitely worth looking at.
I like and agree with everything Spiney says above except the UUID part… I am allergic to extra bits of crap lying around like that.
Instead, just use the name of the ScriptableObject, and discipline yourself to have unique names for all, either via putting all SO instances in a directory (which lets the OS enforce name uniqueness), or else have your own tooling to enforce uniqueness, or at least complain when you make two with the same name to give you an immediate chance to fix the duplication.
Also, it is way easier to see ShortSword3 in your savefile than something like cca0cfdc-112f-11ef-86c2-b8e85646c3c6
Then as you save, you will save a proxy string.
Loading/Saving ScriptableObjects by a proxy identifier such as name:
It is worth noting this works up until a point. Once your game is live, you can’t rename your items/assets least you break people’s save files.
In certain item/inventory systems this probably doesn’t work either, especially if you’re runtime generating items. I know that using names is out of the question for my project.
Also you can just make a save-file editor window to view the save data in a readable and editor manner.
I think the downside of this is you can’t make these at runtime, as you can only instance them via a guid which we can’t retrieve at runtime (yet?).