Hello, so I’m trying to save an array of Structs. Here’s how it looks like:
[Serializable]
public struct ItemSlotST
{
[NonSerialized] public ItemSO itemSO; //scriptable object
public string itemSO_ID;
public int quantity;
etc
It all works well, but since SO is non serialzied, I can’t set it in the Inspector, which is kind of a bummer for many reasons.
Is there a way to make a field non serializable and still be able to edit it in the inspector? This is probably a dumb question.
Not really. You could implement your own serialization and custom Editor for the Inspector but …
… why wouldn’t you serialize the SO in the first place?
It seems like it belongs there. It also seems like you want to index the SO by string for some reason, which would make the lookup brittle as it would depend on the asset’s name or path.
Rule of thumb: avoid string-based identifiers like the plague. Same reason why everyone recommends not to use GameObject.Find. Such an ID can always be an integer, and it could even be an index into a list/array so that lookup is simple and fast. While you could use the asset GUID serialized either as string or GUID type, this would waste a lot of space. I believe a GUID is four integers (16 bytes), and as a string it’s around 30-40 bytes. Last thing you want is a bloated serialized asset since every byte adds to the loading time (as well as memory usage).
The alternative is to split it up. Move the SO to a separate list of SOs that has the same number of items (and then you could use an integer ID/index), or move the SO one level higher if it’s the same SO for all of these structs.
I’m trying to implement a save system, which is a huge a pain for me, since I’m dumb. I am using asset GUID serializer, I’ll stick with it for now, cause it wasn’t easy to figure out as well. Besides I’m not sure what alternatives are, I’ve seen one tutorial with custom GUID thing, it uses ints instead of strings, maybe I’ll switch to that. But right now, I’m more interested to see the whole thing work.
I’m not sure I got that part.
This is how this works right now: OnSave I save the array without SOs, OnLoad I go through every element of the array and set its SO according to its ID. The downside to not seeing SOs in the inspector is that I can’t test inventory properly. I should’ve probably mentioned that I’m trying to save an Inventory.
I am thinking of making a new array specifically for saving, this is currently a WIP. I tried to think of easy way out first.
So here’s how I kinda solved it: by making a new Struct and a new array specifically for saving.
//new struct specifically for saving
[Serializable]
public struct ItemSlotST_Saving
{
public string itemSO_ID;
public int quantity;
public float durability;
//not including constructor
//initial struct
[Serializable]
public struct ItemSlotST
{
public ItemSO itemSO;
[HideInInspector] public string itemSO_ID;
public int quantity;
public float durability;
[field: NonSerialized] public bool isLocked;
[field: NonSerialized] public bool isEquipped;
And here’s the saving process itself (inside the Inventory script)
//no idea why fancy formatting is gone
public ItemSlotST[] itemSlots = new ItemSlotST[0]; //initial array
private ItemSlotST_Saving[] itemSlots_Saving = new ItemSlotST_Saving[0]; //new array for saving
//there are 36 elements in both arrays
//making a struct for saving
[Serializable]
private struct InventoryData
{
public ItemSlotST_Saving[] itemSlots_Saving;
}
public object SaveState()
{
//first go through every element and set what needs to be saved
for (int i = 0; i < itemSlots.Length; i++)
{
if (itemSlots[i].itemSO == null)
itemSlots_Saving[i] = new(); //creating empty struct if the slot is empty
else
{
itemSlots_Saving[i].itemSO_ID = itemSlots[i].itemSO_ID;
itemSlots_Saving[i].quantity = itemSlots[i].quantity;
itemSlots_Saving[i].durability = itemSlots[i].durability;
}
}
return new InventoryData
{
itemSlots_Saving = itemSlots_Saving,
};
}
//then the opposite on load
public void LoadState(object state)
{
InventoryData data = (InventoryData)state;
for (int i = 0; i < itemSlots.Length; i++)
{
if (itemSlots_Saving[i].itemSO_ID == string.Empty)
itemSlots[i] = new();
else
{
//this is from the asset i'm using, probably the most important part
itemSlots[i].itemSO = IDFinder.GetScriptableObject<ItemSO>(data.itemSlots_Saving[i].itemSO_ID);
itemSlots[i].itemSO_ID = data.itemSlots_Saving[i].itemSO_ID;
itemSlots[i].quantity = data.itemSlots_Saving[i].quantity;
itemSlots[i].durability = data.itemSlots_Saving[i].durability;
}
}
UpdateAllSlots();
So I have to do a for loop for every save/load. This works and I can modify array in the inspector, however this is probably not the most optimal way. So if there are suggestions or other ideas, please let me know.
Also here’s another question: I think I’ll have 100-200 SOs to serialize, will it really make a difference if I use int based ID system instead of GUID?
I don’t see the issue with having the item there as a non-serialized member in the same struct. You could have it lazily-load the Item when trying to access it for the first time.
If you want to be able to view it, then you would need to write a PropertyDrawer
for your struct.
The biggest issue was that I couldn’t modify Inventory during runtime to test things out, since SO wasn’t showing up. And I already have quite a few instances where I set SO directly into the struct in the Inspector. I could’ve set SO as a separate field of course, but I’d have to change quite a few prefabs.
And I’m afraid I’ve no idea what PropertyDrawer is, my guess it’s like a custom editor? Figuring out all the serializing and saving and all that has already mentally scarred me.
I mean why ask me when you can ask the documentation: Unity - Scripting API: PropertyDrawer
The property drawer can be set up to assign the value of the ID when assigning the object field as well.
1 Like
Thanks, I’ll check it out when my brain think good, this is quite clumsy what I currently have.
1 Like