Passing data as scriptable object or struct

Hello! I recently learned about scriptable objects and I have been playing around with them. They are a nice way to pass data between scenes and to make config settings that can be easily swapped. I have been using AssetDatabase.LoadAssetAtPath to get the SO and storing it during Awake().
Like this:

MySO mySO = (MySO)AssetDatabase.LoadAssetAtPath(soPath, typeof(MySO));

Then I pass mySO around in whatever method parameters I need to. However, I have also played around with the jobs system and burstcompile recently, and my understand is that structs are better to pass data around.

So my question is, would it be better to load my scriptable objects during Awake() but then store the data in a struct and then pass that to whatever method parameters that need it?

AssetDatabase is editor only, so you can’t use your current solution in a build anyway.

Why not just reference the scriptable object via the inspector?

If I’m using it as a settings config would I want to have all the different setting configs referenced? For example I have this scriptable object:

[CreateAssetMenu(fileName = "TerrainType", menuName = "Scriptable Objects/TerrainType")]
public class TerrainType : ScriptableObject
{
    [Header("Terrain Data")]
    [SerializeField] private Texture2D _heightMapTemplate;
    [SerializeField] private float _templateWeight;
    [SerializeField] private TopographyData _topographyData;

    [Header("Perlin Noise Data")]
    [SerializeField] private int _numOfOctaves;
    [SerializeField] private float _scale;
    [SerializeField] private float _persistance;
    [SerializeField] private float _lacunarity;

    public Texture2D HeightMapTemplate { get { return _heightMapTemplate; } }
    public float TemplateWeight { get { return _templateWeight; } }
    public TopographyData TopographyData { get { return _topographyData; } }
    public int NumOfOctaves { get { return _numOfOctaves; } }
    public float Scale { get { return _scale; } }
    public float Persistance { get { return _persistance; } }
    public float Lacunarity { get { return _lacunarity; } }
}

I then create a bunch of different SOs like Plains, Hills, Mountains, Coast, etc. When I get to actually generating the map I only need 1 of those. Should I store all of them as a reference?

Then only reference the one you need?

Or if this is for a general world generation scene, pass through scriptable object needed for world generation.

Currently I have a main menu scene. Here I choose different options like the size of the map, seed, terrain type . Then that data is stored in a persistent singleton. When ‘Generate’ is clicked it loads a new scene. When the scene loads a generation manager gets the data from the singleton and loads up the terrain scriptable object and creates the map. Are you saying that I should have all the terrain types referenced in the first scene, then the chosen one is passed through the singleton to the next scene?

It would make the most sense, or wrap them all up in another container scriptable object. Some editor coding helps to automatically populate the container object.

Then you can design your UI to generate options for all the config options in the container, so you don’t really have to do any work when you design new terrain types.

Ok, let me see if I understand what you mean. Are you saying that I could have a scriptable object that acts like a list to store my terrain types. That way I would only have to reference the one scriptable object. Then somewhere else, like on a resource manager, I would have code that would load all the created terrain types into that scriptable object. So I wouldn’t have to add/remove them manually each time I create/remove a new terrain type? I’m assuming that resource manager could use LoadAllAssetsAtPath as it wouldn’t have to be ran at runtime?

The container scriptable object can just have a custom inspector to update its collection containing all terrain types. Another asset doesn’t need to be the one to do it.

Otherwise that’s roughly what I said.

Bottom line is just reference things via the inspector, and encapsulate data and functionality as needed. Fairly standard stuff.

Ok, looks like I be tackling custom inspectors next then. Unfortunately ‘fairly standard’ is groundbreaking news to me for now. Thanks for the help. As an aside question, how would I load something at runtime?

Or is that just considered bad practice?

You need some means beforehand to load the asset via either Resources, Addressables, or your own system (sometimes a wrapper around either of the previous two).

For Resources that’s the path to the asset, Addressables would be an asset’s GUID. In both cases you can’t get this from the asset itself, the information needs to be available - usually cached - beforehand.

Your own system can introduce your own ID system and your own means to look up the asset, which can be available from the assets.

Definitely not. It’s a common way to handle save games. My current project uses indirect references to assets (internally it only serializes the asset’s unique ID, and an implementation for how to locate said asset), so I can safely serialize them to disk and use those indirect references to get the asset when the date is later deserialized.

Ok, I’ll add that to the ever growing list of new things to learn next. Thank you again.