- Home /
.asset file containing ScriptableObject is empty on openingUnity
I've been doing a lot of research on this problem, and to quote someone else's post, I've come to two conclusions:
1) this is a very common problem 2) it has a solution, but I don't know what that is yet
I am creating a system wherein a Monobehaviour object stores lists of behaviors that derive from ScriptableObject.
I ran into a problem where I was attempting to drag these gameobjects into my project in order to make prefabs. When bringing them back into the scene, the lists had the correct number of elements, but all of the references had been lost, yielding null reference exceptions on inspection.
So, I attempted to make an .asset file that stored these lists with a reference to each gameobject's instance ID. So far this works fine, as long as Unity is open. I can store and retrieve data in the .asset file just fine. I can "play" the scene and all is well. But as soon as I quit and restart unity, the asset file comes up empty.
According to what I've read, storing ScriptableObjects is the desired route, and creating an asset file that stores a ScriptableObject should work just fine. After encountering a lot of these problems, I tried to implement ISerializable, which took me almost all the way to success, except that apparently .NET cannot serialize ScriptableObjects. Or, at least, that's the way it seemed based on the SerializationExceptions I got.
Ok, here's a stripped down version of the code I'm working with:
[Serializable]
public class StorageDataBase : ScriptableObject
{
public List<int> keys;
public List<DBEntry> data;
public void SaveEntry(int id, List<StoredBehaviors> a, List<StoredBehaviors> b)
{
if (keys == null)
keys = new List<int>();
if (keys.Contains(id))
{
DBEntry entry = GetEntry(id);
if (entry != null)
{
entry = new DBEntry(id, a, b);
return;
}
}
else
keys.Add(id);
if (data == null)
data = new List<DBEntry>();
data.Add(new DBEntry(id, a, b));
}
public DBEntry GetEntry(int id)
{
if (data == null)
data = new List<DBEntry>();
foreach (DBEntry e in data)
{
if (e.id == id)
return e;
}
return null;
}
}
[Serializable]
public class DBEntry
{
public int id;
public List<StoredBehaviors> aList;
public List<StoredBehaviors> bList;
public DBEntry(int _id, List<StoredBehaviors> a, List<StoredBehaviors> b)
{
id = _id;
aList = a;
bList = b;
}
}
[Serializable]
public class StoredBehaviors : ScriptableObject
{
public void Begin() { }
public void DoStuff() { }
}
and here's how I'm checking the .asset file in my custom inspector: (code snippet.. the editor is rather large)
MonoBehaviourGameObject obj;
StorageDataBase db;
private void LoadBehaviors()
{
db = AssetDatabase.LoadAssetAtPath("Assets/BehaviorAssets/BehaviorDatabase.asset", typeof(StorageDataBase)) as StorageDataBase;
if (db == null)
throw new Exception("behavior database not found: either no data was saved, or it was removed");
obj.a = db.GetEntry(obj.id).a;
obj.b = db.GetEntry(obj.id).b;
bool behaviorsLoaded = obj.ValidateBehaviors(); //used elsewhere
}
private void SaveButton()
{
if (GUILayout.Button((db == null ? "Save" : "Update") + " Behaviors", GUILayout.ExpandWidth(true)))
{
db = AssetDatabase.LoadAssetAtPath("Assets/BehaviorAssets/BehaviorDatabase.asset", typeof(StorageDataBase)) as StorageDataBase;
if (db == null)
{
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<StorageDataBase>(), "Assets/BehaviorAssets/BehaviorDatabase.asset");
Debug.Log("creating new db");
}
db.SaveEntry(obj.id, obj.a, obj.b);
AssetDatabase.SaveAssets();
Debug.Log("saving db entry " + obj.id);
}
}
I've seen one other post that I haven't tried yet that talks about just having a public byte array serialized, but before going that route which seems like it might have the same failings, I wanted to ask what's going on here. I feel as if I'm overlooking something basic.
Thanks in advance
Answer by Bunny83 · Nov 26, 2013 at 11:13 PM
Ok some points that might help you:
First you don't have to load those assets manually. If they are created properly they are linked like any other asset. So if you have a public field of type "StorageDataBase" in a MonoBehaviour, it should be linked to that asset, all the time.
It seems you have more than one scriptable object but it looks like you just save one of them. All ScriptableObjects have to be saved manually as an asset. Keep in mind that you can add multiple assets to the same asset file. See AssetDatabase.AddObjectToAsset. An assetfile doesn't need to represent a single asset. If you add multiple assets to the file all assets will appear with their name as sub-assets (like you see when using fbx modelfiles).
I just created a test with two different scriptable objects. One has a List of the other type. When i create an instance i add it to the list of my "database" scriptable object and as well to the asset file with AddObjectToAsset. That way it physically exists as asset and can be linked as any other asset. When i change the scene or restart unity everything is still fine.
hmm, ok. I'm clearly doing something wrong. I do have a lot of ScriptableObjects happening, but they're all stored as instances in a List, and those lists are stored as public variables in a ScriptableObject, which is then stored in the asset file.
Is that too many levels deep? I'm trying to create modular behaviors that might have many instances that are attached to various objects. You said that you're storing lists, which is also what I'm doing. I'm not sure where the problem lies. But it's reassuring that I'm on the right path.
so, you're saying if I have:
public class SomeBehavior : ScriptableObject
{
//stuff
}
public class Storage : ScriptableObject
{
public List<SomeBehavior> behaviors;
}
that the list won't serialize properly, because each scriptableobject needs to individually be saved?
because this is more or less my current structure
Yes, exactly. You need to save every SomeBehaviour either as seperate asset or you add it as sub asset to your db-file.
btw: If you want to delete such an sub asset from the asset file you have to use:
DestroyImmediate(refToSubAsset, true);
This will delete the object. The Unity editor will still show the asset in the project view until you reimport the asset. If you do a AssetDatabase.ImportAsset right after DestroyImmediate should fix this.
@zombience: If you want to hide certain scripts / components in the inspector, just set the hideflags the way you want. HideFlags.HideInInspector would completely hide a component. You can even hide the Transform component if you want. Usually that's not a good idea since it restricts the user, but if you use the coordinates for other things it would make sense to hide the Transform.
The editor uses this all the time. For example every sceneview has a gameobject with a camera component in the scene, but it's hidden everywhere and not saved. So it's there but you can't select the gameobject and you can't see it. You can create an editor script to make it visible (but since the camera GO is updated each editor event you can't really edit it).
$$anonymous$$eep in $$anonymous$$d that your system should be bug free when you completely hide certain things because you can't delete them manually once hidden.
Just wanted to leave a big thank you for @Bunny83, I spent so much time trying to figure out why my ScriptableObject-based structure weren't "serializing" properly when I saved the custom asset. I was following every serialization rule and best practice I could find, and refactored my code a bunch of times to try and make sense of it.
Then, reading the discussion here, it hit me...I WAS serializing just fine, but I assumed just saving the container object with references to the rest of the object structure would save them all to the asset.
Thanks a lot!
Your answer
Follow this Question
Related Questions
AddObjectToAsset (reference) becomes null 1 Answer
Custom assets give Missing (Mono Script) 0 Answers
[Solved]Why doesn't my ScriptableObject based asset save using a custom inspector ? 1 Answer
Why doesn't my ScriptableObject save using a custom EditorWindow? 3 Answers
AssetDatabase.LoadAssetAtPath() not working after any sort of project change. 0 Answers