Static Lists of Objects

I’m trying to make a class, which holds a few hundred unit types, that can be instantiated with a lookup from other classes. Ideally I’d want it so you could use it like this:

UnitFactory.CreateUnit("GenericUnit");

The problem being I’m struggling to create static lists of GameObjects, without using the resources folder (as I’ve read online that it’s a fair bit slower). What I have right now is this:

    [System.Serializable]
    public class UnitFactory : RTSBase
    {
        private static UnitFactory _unitFactory;
        [SerializeField] private List<GameObject> units = new List<GameObject>();
      
        public static UnitFactory unitFactory
        {
            get
            {
                if(_unitFactory == null)
                {
                    GameObject obj = Resources.Load("UnitFactory") as GameObject;
                    _unitFactory = obj.GetComponent<UnitFactory>();
                }
                return _unitFactory;
            }
        }

        public static GameObject CreateUnit(string prefabName)
        {
            for(int i=0; i < unitFactory.units.Count; i++)
            {
                if(unitFactory.units[i].name == prefabName)
                {
                    GameObject obj = Instantiate(unitFactory.units[i], Vector3.zero, Quaternion.identity) as GameObject;
                    return obj;
                }
            }
            return null;
        }
    }

I get a threading issue from this, saying:

I mean I know I could just make this a singleton and be done with it… but ideally I envisioned a static lookup service, and don’t like to really find workarounds unless it actually isn’t a good idea or not possible.

The problem I’ve encountered is:

  • Setting a List as static means you cannot serialize it. So the only way to instantiate GameObjects from this list would be from the Resources folder, which is slower.
  • Setting the list as [SerializeField] private List means you need to instantiate the UnitFactory, meaning that I’ll need to either have one in every scene (With a DontDestroyOnLoad).
  • I can’t instantiate a prefab with all referenced GameObjects from a static get, as the prefab would need serializing, and a static get, cannot reference a member variable…

Is there a way through this?

“get_name can only be called from the main thread” sounds like this line:

if(unitFactory.units[i].name == prefabName)

is being called from a constructor. The .name property is turned into a get_name method when the code compiles.

You would be calling it from the constructor if you’re doing something like this somewhere:

public class SomeMonoBehaviour : MonoBehaviour {

    private GameObject someObject = UnitFactory.CreateUnit("myUnit");

    ...

}

If that’s the case, assign someObject in Awake instead.

For how you would implement it:
TL;DR:Use the singleton

You need a serialized list somewhere that you can edit in the inspector and load on runtime. So either you make a static class that reads from a ScriptableObject, or you have a ScriptableObject that you load as a singleton. That object lives in the Resources folder. Yes, it’s (somewhat) expensive to load, but you only need to load that object once, and then it stays in memory, so you’re good on that front.

The static class solution that wraps a different scriptable object is just a convoluted singleton, so I’d go with the singleton solution. A static lookup service and a singleton that’s responsible from doing lookup will look exactly the same from the outside, and will have the same up- and downsides.

What I’d do for your issue is:

  • Get some kind of serializable dictionary. There’s a bunch out there, and it’s not hard to roll your own
  • Create a ScriptableObject that you put in your resources folder, where you create a serializable unit-type to unit-prefab dictionary.
  • On runtime, load that ScriptableObject it as a singleton;
public class UnitFactory : ScriptableObject {

    [SerializeField]
    private SerializableDict<string, GameObject> unitDict;

    private static UnitFactory _singleton;
    private static UnitFactory singleton {
        get {
            if(_singleton == null)
                _singleton = Resources.Load<UnitFactory>("Path_to_Singleton");
            return _singleton;
        }
    }

    public static GameObject CreateUnit(string id) {
        GameObject prefab;
        if(singleton.unitDict.TryGetValue(id, out prefab)) {
            return Instantiate(prefab);
        }
        return null;
    }
}

This would be massively improved if you ditched the string-based identifier (which comes from using the name of the prefab), and used an enum for all the unit types. Adding or removing enums as you add or remove units is not a big overhead, and you’d be safe from the CreateUnit spitting out a null unit if you misspell the identifier.