Morning all, I’m having trouble accessing data I’ve stored in an asset. I’m trying to use ScriptableObjects to store information for an editor extension I’m creating. I’ve successfully figured out how to create the asset and then add a subobject to it with AssetData.AddObjectToAsset(), what I can’t figure out is how to access that subobject later for loading it.
For example I have:
Asset Foo
-SubObject Bar1
-SubObject Bar2
-SubObject Bar3
I can load the Foo asset, but I can’t find a way to access any of the SubObject Bars in it. I’m sure I’m missing something simple, but I’ve yet to find an explanation for how to do this anywhere.
Thanks in advance for any help.
You will probably need to keep references to them in the container ScriptableObject in some array for example.
I’m actually trying to access them so I can build a list from them. I have a list of scriptable objects that just isn’t saving, so I’m adding each of the SO’s in the list to the asset as a subobject, and then trying to access those and regenerate the list from them.
AssetDatabase.LoadAllAssetsAtPath (or similar) finds all subassets in the editor.
Yeah, that’s what I’m looking at now. Is there a simple way to compare types?
I know I can get the type of a loaded asset with Object.GetType(), but how would I then compare that to a given Type type?
I’ve tried:
Object.GetType().Equals(type)
Object.GetType() == type
Neither of these work, and both throw an error. When I load all the assets, how would I make sure that I’m only grabbing the ones I want to add to the list? I know that converting the type names to a string and comparing the string would technically work, but I feel like there should be a better way to achieve that.
There are several ways, here’s one:
if (yourObject.GetType() == typeof(YourClass))
How about
if (yourObject is YourClass)
…which also returns true if yourObject is a class that is derived from YourClass
Alright, sorry, but going to be a bit more complex.
I’m creating a generic method that I can use to load these types:
public static List<ScriptableObject> GetSubObjectOfType<ClassType>(ScriptableObject asset){
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(asset));
List<ScriptableObject> ofType = new List<ScriptableObject>();
foreach(Object o in objs){
if(o.GetType() == ClassType)){
oftype.add((ScriptableObject)o);
}
}
return ofType;
}
When I try this I get an error: Method Not Found; ‘System.Type.op_Equality’.
1 Like
Face Palm THANK YOU!
Thank you both for the help, it works! there appear to be some lingering issues with data within a subobject not saving, but I’ll spend a bit of time on that before seeking help.
Thanks again.
So I was a bit inspired…
public static List<T> GetSubObjectsOfType<T>(Object asset) where T : Object{
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(asset));
List<T> ofType = new List<T>();
foreach(Object o in objs){
if(o is T){
ofType.Add(o as T);
}
}
return ofType;
}
public static List<ScriptableObject> GetSubObjectsOfTypeAsScriptableObjects<T>(ScriptableObject asset) where T : ScriptableObject{
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(asset));
List<ScriptableObject> ofType = new List<ScriptableObject>();
foreach(Object o in objs){
if(o is T){
ofType.Add(o as ScriptableObject);
}
}
return ofType;
}
public static List<T2> GetSubObjectsOfTypeAsType<T1, T2> (Object asset) where T1 : Object where T2 : T1{
Object[] objs = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(asset));
List<T2> ofType = new List<T2>();
foreach(Object o in objs){
if(o is T1){
ofType.Add(o as T2);
}
}
return ofType;
}
3 Likes
Lol, looks good. Thank you… I really like that last one, I think I’m going to use that outright if you don’t mind.
I am having a bit of trouble beyond this though. I can’t seem to get the scriptable objects to save data that is stored in them. Here’s the setup:
//This class holds and manipulates the ELScrs stored in the list
[Serializable]
public class ELManager : ScriptableObject(){
List<ELScr> elevators;
}
//This script hold info about the elevator's name, as well as how many levels it connects to
[Serializable]
public class ELScr :ScriptableObject
{
public string name;
public in numLevels;
}
Whenever an ELScr instance is created, numLevels is set to 1. Whenever I load it back in (using the method above), numLevels is 0. There’s more to each of these functions, but this is the important part that’s giving me trouble.
So I tried to replicate your problem, but it all seems fine to me.
I used the classes you posted above, but in separate files without the [Serializable] attribute.
I also didn’t add public string name to ELscr, as ScriptableObjects already have name, I called it elName instead.
I am using the where T : Object generic above too.
[MenuItem("EL/ELManager")]
public static void CreateELManager() {
ELManager d = ScriptableObject.CreateInstance<ELManager> ();
d.name = "elmanager";
AssetDatabase.CreateAsset (d, "Assets/elmanager.asset");
}
[MenuItem("EL/Add ELScr")]
public static void AddELScrs() {
ELManager d = AssetDatabase.LoadAssetAtPath("Assets/elmanager.asset", typeof(ELManager)) as ELManager;
for (int i = 0; i < 4; i++) {
ELScr s = ScriptableObject.CreateInstance<ELScr>();
s.name = "elscr"+i;
s.numLevels = 12*(i+1);
AssetDatabase.AddObjectToAsset(s, d);
}
d.elevators = GetSubObjectsOfType<ELScr>(d);
AssetDatabase.SaveAssets();
}
And I made elevators public too.
using UnityEngine;
using System.Collections;
public class ELScr : ScriptableObject {
public string elName; // ScriptableObjects already have name
public int numLevels;
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ELManager : ScriptableObject {
public List<ELScr > elevators;
}
Looking at your code, it seems like you’re only concerned with generating the ELScr isntances and adding them to the asset. I’ve got that working just fine. What I’m having trouble with is that when I close Unity and reopen it. Here’s the basic sequence for what’s happens:
I try to open to custom window for editing elevators:
Because this is the first time the editor has been opened this session, it tries to rebuild manager using the information save in elmanager.asset.
All of the ELScrs saved in elmanager.asset are added back into the elevators list, but all of the individual variables within them are reset to default values, which is my problem.
I am using EditorUtilities.setDirty(manager), and then calling AssetDatabase.Save() each time an ELScr is added to the list or edited, so I don’t think it’d be that. Besides that, the numLevels variable is set before the asset is created, so it should be saved as 1. Also, each class is a separate file, I don’t know if that’d have an affect, but just in case, they are.
Thank you for all the help, I appreciate it.
Honestly, at this point, I might just clean the slate and start over on this particular system… or, I might just try to write my own serializer. If I could figure out how to save a direct reference to a GameObject then I probably would go that route, but I can’t figure that out either. >_>
If you look at the raw data in the inspector if you select one of the elscr in the project panel, they have also reset all their information?
That particular value isn’t being shown… I bet this has to do with the fact that it’s a property…
That’s it isn’t it, I remember reading that ScriptableObjects can’t serialize properties…
I’m using:
public int numLevels{get; set;}
…I guess I should have been a bit more specific in describing what I had…
Indeed, the property isn’t serialized properly.
I had the similar issues with editing scriptable objects via code, but different cause.
I was not calling EditorUtility.SetDirty on all the objects I edited.
I tried it with the objects I was testing for you and just simply saved the asset that was used to create the first asset, not any of the sub assets. Same thing happens, changes lost when exiting Unity.
The solution is to SetDirty on all them assets, not just the original.
Wow… that’s kind of crazy. Maybe I really would be better off making my own serializer >_>
Thank you for all the help!
1 Like
Having to load all assets via path is kind of madness. Why isn’t there a way to access the list directly from the parent object?