This seems like a problem that is solved with some minor thing that I just don’t know about yet.
I want my ScriptableObjects to have “New Game” (aka “Default”) values, and “Current” values.
So I have the following ScriptableObject “Doodad” set up:
using UnityEngine;
[System.Serializable]
public class Doodad : ScriptableObject
{
public string DoodadTitle;
private string DefaultDoodadTitle;
public ClueForDoodad[] clueArray;
private ClueForDoodad[] DefaultclueArray;
public void LoadThatData()
{
DoodadTitle = DefaultDoodadTitle;
clueArray = DefaultclueArray;
}
public void SaveThatData()
{
DefaultDoodadTitle = DoodadTitle;
DefaultclueArray = clueArray;
}
}
The ClueForDoodad[ ] is set up this way:
using UnityEngine;
[System.Serializable]
public class ClueForDoodad
{
public bool ClueIsViewable;
private bool DefaultClueIsViewable;
public string ClueText;
private string DefaultClueText;
}
And in a script on my ‘debugging’ interface, I have some buttons that can call the “SaveThatData” and “LoadThatData” functions:
using UnityEngine;
public class DoodadSaverLoader : MonoBehaviour
{
public Doodad[] resettableScriptableObjects; // All of the scriptable object assets that should be reset for a NewGame.
public ClueForDoodad[] eachClueArray;
public void TimeToLoadData ()
{
// Go through all the scriptable objects and call their LoadThatData function.
for (int i = 0; i < resettableScriptableObjects.Length; i++)
{
resettableScriptableObjects[i].LoadThatData ();
Debug.Log("Should be loaded.");
}
}
public void TimeToSaveData ()
{
// Go through all the scriptable objects and call their SaveThatData function.
for (int i = 0; i < resettableScriptableObjects.Length; i++)
{
resettableScriptableObjects[i].SaveThatData ();
Debug.Log("Should be saved.");
}
}
}
Instead of boring everyone with all the things I tried that failed, I can summarize:
All my non-array data saves and loads just fine.
The array data does not save and load, but it also doesn’t give any errors.
Any help would be appreciated!
Your default strings and arrays are private which Unity won’t serialize unless you mark them as serializable so when you load you are just setting the public variables to null/empty ones unless you are populating the private variables somehow.
I think you want to make copies of the arrays, not just assign the arrays. Arrays are actually reference types even if they contain value data (strings).
To copy an array you can just write the for loop yourself, or you can use System.Array.Copy
Edit: oh yeah, what @WarmedxMints_1 said too: those private vars won’t save unless you decorate them with SerializeField attributes.
But all the Private fields that aren’t arrays do work properly. Anything like this works perfectly:
public string DoodadTitle;
private string DefaultDoodadTitle;
I thought that’s what the [System.Serializable] thing before the class was supposed to do…that it made everything inside of it Serializable.
I replaced all “private” with “public,” but nothing changed.
Non-array fields saved/loaded fine, the arrays didn’t save or load, but also I got no errors.
I looked up copying the Array, and changed the lines to
System.Array.Copy(DefaultclueArray, clueArray, DefaultclueArray.Length);
for loading and
System.Array.Copy(clueArray, DefaultclueArray,clueArray.Length);
for saving.
But that was exactly as before—no errors, but it didn’t work, either.
For the array copying, did you also first allocate an array of enough size, such as:
DefaultclueArray = new string[ clueArray.Length];
because otherwise it would probably be a zero-length array.
And after the array copy, when you look at the ScriptableObject instance in the editor, is the data present?
I didn’t do that!
I didn’t realize I needed to. (This is one of those “Tiny but crippling errors I didn’t know about.”) I thought the third parameter in the System.Array.Copy was supposed to handle that.
I added those lines to both the save and load functions:
(For the sake of folks with this problem, I’m pasting it here again, modified)
using UnityEngine;
[System.Serializable]
public class Doodad : ScriptableObject
{
public string DoodadTitle;
private string DefaultDoodadTitle;
public ClueForDoodad[] clueArray;
private ClueForDoodad[] DefaultclueArray;
public void LoadThatData()
{
DoodadTitle = DefaultDoodadTitle;
clueArray = new ClueForDoodad[DefaultclueArray.Length];
System.Array.Copy(DefaultclueArray, clueArray, DefaultclueArray.Length);
}
public void SaveThatData()
{
DefaultDoodadTitle = DoodadTitle;
DefaultclueArray = new ClueForDoodad[clueArray.Length];
System.Array.Copy(clueArray, DefaultclueArray, clueArray.Length);
}
}
Unfortunately, nothing seems to change.
The data for the ScriptableObject stays the same in the inspector.
If I save the data, and then make changes, and then load the data, the non-array fields are properly restored.
The array fields stay the same.
(And by the way, I really appreciate your help! I’m trying to be brief for the sake of politeness.)
So for me, Unity does NOT serialize the private fields, as expected. You don’t see them in the editor and you do not see them in the scriptable object asset on disk:
Now, if I mark them as public, everything gets saved as expected, visible in editor and visible in the disk file:
To me this is 100% consistent with Unity behavior as I know it. What part of your work flow contradicts this exactly?
Disregard the “I typed a title again” difference; I had to roll back and resnap it and got out of sync.
Also, how are you creating the disk assets, a custom script? You can actually just decorate the ScriptableObject with this attribute to make them trivially in the right-click popup:
I actually do have [CreateAssetMenu(fileName = "Doodad")]
at the top of the first script, I just didn’t include that line in what I shared for the sake of brevity. (There are actually dozens of variables, all of which are loading/saving properly. It’s just the one array that is giving me trouble out of all of these.)
I honestly have no idea why this is happening, then!
All my regular fields that aren’t in that array are loading/saving just fine, when I look in the inspector…Bools, ints, strings, gameobjects…
But the Array is stubborn.
I am on a Windows machine, not a MacOS one, so that may make a difference.
And…this is also very weird.
The data saves and loads properly in the Doodad type ScriptableObject.
I edited in a text editor like you did to see what was happening…And the private fields didn’t show up. But it still saves and loads fine.
public class ClueForDoodad : ScriptableObject
@Hikiko – Something like this was going to be how I approached it next, if I couldn’t just get it to work.
Unfortunately, when I add : ScriptableObject
, I lose a lot of the usefulness of it being in a single inspector view, and a single ScriptableObject. Most doodads have many (like, 10-20) clues. I’d far prefer to have just one file, if I can. (This makes it so each clue is its own ScriptableObject asset.)
Yeah, I think I got around that by writing a custom inspector when I ran into that problem a long time ago. I think I started off with custom classes but moved everything to scriptableobjects because it fit my use case, but what you’re doing should work.
I think you should be more explicit with your serialization. Seems like none of us are quite sure how the serialize flags work with scriptableobjects and custom classes. I’m always explicit with every field when I use them.
Perhaps scriptable objects are specifically made to be serialized, so when you mark them as serializable, even private members get serialized unless you mark them as nonserializable. I see references to people complaining about that when I look it up (version bug maybe). Where as with custom classes, I think you need to be explicit with private fields. I’d just be explicit with everything, because, why not.
Try and cut an actual build and I think you’ll find that the private-marked variables are null or default when you run it in the build.
1 Like
@Hikko — I don’t suppose you happen to have that custom inspector-related scripty goodness lying around somewhere convenient, like your clipboard, such that you could just copypasta it in, could ya?
(Hey, it’s worth a shot, before I have to go and make my own thing, eh?) 
@Kurt-Dekker — I am going to simply take your word on that—it sounds like the sort of thing I’d have done. So thank you for the heads up! Starting now, I’m changing literally all “private” to “public,” but I am adding the [HideInInspector]
tag (which I learned about happenstance while trying to figure all this out).
You can do the HideInInspector trick, but if you really like things that are private to remain private, the other more-common approach in Unity is:
[SerializeField]private string foo;
That way nobody else can get at it, which is the standard signal to “please don’t use this, it’s mine,” and yet you’ll find it still serializes the same way as a public variable.
So it was indeed a tiny crippling problem.
I had a little more code, and I omitted it because I couldn’t get it to work.
It was just another for loop where I’d forgotten to add the array index.
Here’s the functional code:
using UnityEngine;
/*
This script is for 2 things:
"Saving" the current values of all Doodad ScriptableObjects to invisible "Default" values.
This is only something I do as a developer; it is not for players to ever do.
Also, "Loading" the default values into the current values.
This is good for NewGames.
*/
public class DoodadSaverLoader : MonoBehaviour
{
public Doodad[] resettableScriptableObjects; // All of the scriptable object assets that should be reset at the start of the game.
public ClueForDoodad[] eachClueArray;
public void TimeToLoadData ()
{
// Go through all the scriptable objects and call their Reset function.
for (int i = 0; i < resettableScriptableObjects.Length; i++)
{
Debug.Log(resettableScriptableObjects[i].DoodadName + "Should be loaded");
resettableScriptableObjects[i].LoadThatData ();
eachClueArray = resettableScriptableObjects[i].ClueArray;
for (int g = 0; g < eachClueArray.Length; g++)
{
eachClueArray[g].LoadThatArray();
}
}
}
public void TimeToSaveData ()
{
// Go through all the scriptable objects and call their Reset function.
for (int i = 0; i < resettableScriptableObjects.Length; i++)
{
resettableScriptableObjects[i].SaveThatData ();
Debug.Log(resettableScriptableObjects[i].DoodadName + "Should be saved.");
eachClueArray = resettableScriptableObjects[i].ClueArray;
for (int g = 0; g < eachClueArray.Length; g++)
{
eachClueArray[g].SaveThatArray();
}
}
}
}
And now I can breathe easily.
Thanks for the help everyone. And I hope this helps someone else, too.
1 Like
Also as an extra bonus to anyone else with this (or a similar) problem:
I was able to find all the objects of type “Doodad” this way:
Doodad[ ] resettableScriptableObjects = Resources.LoadAll<Doodad>("Prefabs/Doodads");
It searched all subfolders and only got the ones that were of ScriptableObject type Doodad. A huge time-saver.
Resources.LoadAll() is very handy, but it is still wise to put your intended types of files into a subdirectory, as you have done.
The reason is, at least on existing versions of Unity, Resources.LoadAll() is not smart enough to only load the assets of type T.
Rather it will stream into memory every single asset it finds in the search directory, fully expanding it into RAM and all, and then ultimately only return you an array of T-typed objects.
You can imagine the implications on a memory-constrained context like mobile or console.
1 Like
Thank you so much. I have been able to speed up all menu-related things dramatically by separating every kind of thing into its own resources folder. (ScriptableObjects in one, Icons in another, &c)
1 Like