Search code examples
c#unity-game-enginecollectionshashsetscriptable-object

How can I add each instance of a scriptable object to a collection?


hello :)

I wish to add each and every instante of an scriptable object class to a HashSet (which is in a static class in a separate script).

I did this by creating a method that adds said class to the hashset and I called this method in the constructor of the class I'm instantiating. Here's the thing though... it works, but not always? Here's the code, I'll go further into the problem at the end:

//first script where the hashset is:
public static class Record
{
    private static HashSet<MyClassToInstantiate> MyHashSet = new HashSet<MyClassToInstantiate>();

    public static void AddToMyHashSet(MyClassToInstantiate obj)
    {                
        MyHashSet.Add(obj);                
    }

}       

//the other script:
[CreateAssetMenu(menuName = "X")]
public class MyClassToInstantiate : ScriptableObject
{
    [SerializeField] string someRandomData1;
    [SerializeField] int someRandomData2;

    public MyClassToInstantiate()
    {
        Record.AddToMyHashSet(this);
    }
}

This works perfectly except the first time I open Unity. The first time I always have to go into any instance of my "MyClassToInstantiate" and change something (anything) and it just starts working (I'm using the [createAssetMenu] attribute btw).

ps. I'm sure I have blatant theoretical problem that I'm not seeing! Thanks in advance to anyone who gives me some idea of what is going on


Solution

  • So here's the main issue. A scriptableObject instance is an asset that lives permanently in your unity project. Your static class with the hashset is data that only exist while unity is running. Once you leave the application its gone so you would need to regenerate the data. Now I"m not 100% sure why unity calls the scriptableObject's constructor it seems on validate (when you select it/make changes) but relying on something like that will surely cause issues ;) .

    You could get all instances when you access the data.

     public static class Record
    

    {

    private static HashSet<MyClassToInstantiate> MyHashet;
    
    public static HashSet<MyClassToInstantiate> GetHashSet() 
    {
        RefreshHash();
    
        return MyHashet;
    }
    
    public static void RefreshHash() 
    {
        MyClassToInstantiate[] allInstances = GetAllInstances<MyClassToInstantiate>();
        MyHashet = new HashSet<MyClassToInstantiate>();
        for (int i = 0; i < allInstances.Length; ++i)
        {
            MyHashet.Add(allInstances[i]);
        }
    }
    
    private static T[] GetAllInstances<T>() where T : ScriptableObject
    {
        string[] guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(T).Name);  //FindAssets uses tags check documentation for more info
        T[] a = new T[guids.Length];
        for (int i = 0; i < guids.Length; i++)         //probably could get optimized 
        {
            string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
            a[i] = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
        }
    
        return a;
    }
    

    }

    The GetHashSet() function will then return all instances of your scriptable object.

    But if you want a cleaner solution, having an other scriptableObject with links to all MyClassToIntanctiate instances you want may be better. It would let you edit the list so they aren't all added by default.