Best practice for loading ScriptableObject-Like data from the web

Hey!

Until now, levels in my game have always been just prefabs. This changes now, as I want to add a level editor and online levels. For this, I first created this ScriptableObject schema for saving level data and metdata efficiently:

(dont worry, I am not handling things like author name, upload date or likes locally, these are placeholders when fetching online levels)

public class LevelSO : ScriptableObject
{
    public string title;
    public string description;
    public string author;
    public int id;
    public DateTime date;
    public int length;
    public LevelDifficulty difficulty;
    public int downloads;
    public int likes;
    public List<LevelObjectInfo> levelObjectList = new();

    public void AddLevelObjectInfo(LevelObject levelObject)
    {
        LevelObjectInfo levelObjectInfo = new LevelObjectInfo(levelObject);
        levelObjectList.Add(levelObjectInfo);
    }

    public void ClearLevelObjectList()
    {
        levelObjectList.Clear();
    }

    public string Serialize()
    {
        string jsonData = JsonConvert.SerializeObject(this, Formatting.Indented);
        return jsonData;
    }
}

When serialized to JSON, the data for the level looks something like this:

{
  "title": "Example title",
  "description": "Example description",
  "author": "Creeperkatze",
  "length": 1000,
  "date": "2025-01-09T20:06:18.077Z"
  "id": 1,
  "levelObjectList": [
    {
      "type": 3,
      "position": {
        "X": 0,
        "Y": 1,
        "Z": 850
      }
    },
    {
      "type": 1,
      "position": {
        "X": 3.5,
        "Y": 1,
        "Z": 100
      }
    }
  ]
}

Now my question:

When actually loading data from the web

LevelSO level = JsonConvert.DeserializeObject<LevelSO>(request.downloadHandler.text);

I get warnings for creating new ScriptableObjects at runtime, which is not supported.

Should I just use a simple class for handling level data (Most of the level data isn’t really meant to be manually edited anyway, so maybe I dont need ScriptableObjects)?

[Serializable]
public class LevelData
{
    public string title;
    public string description;
    public string author;
    public int id;
    public DateTime date;
    public int length;
    public LevelDifficulty difficulty;
    public int downloads;
    public int likes;
    public List<LevelObjectInfo> levelObjectList = new();

    public void AddLevelObjectInfo(LevelObject levelObject)
    {
        LevelObjectInfo levelObjectInfo = new LevelObjectInfo(levelObject);
        levelObjectList.Add(levelObjectInfo);
    }

    public void ClearLevelObjectList()
    {
        levelObjectList.Clear();
    }

    public string Serialize()
    {
        string jsonData = JsonConvert.SerializeObject(this, Formatting.Indented);
        return jsonData;
    }
}

How is a situation like this typically handled?

Thanks in advance for any answers!

If you don’t need any of the lifetime events and behaviour of scriptableobject or you don’t to need have them additionally packaged in an assetbundle + editable using unity serialization I would suggest going the route of using just a simple class. Makes it easier and probably more performant as unity doesn’t need to do their native side allocations.

If you want to use scriptable objects together with JsonConvert you will need to keep in mind that the function you are using will return a new instance which isn’t supported as ScriptableObjects always need to be created using ScriptableObject.CreateInstance.
JsonConvert seems to have a method PopulateObject which takes the already created instance to be filled with your data so you can use that together with CreateInstance.

2 Likes

To extend on billy’s response, I wanted to point out that this is one of the reasons why I rarely put consumable fields directly in a SO instance like above. Instead, I wrap these in a regular c# class of its own so it can be serialized to json etc with ease:

[Serializable]
public class TheData
{
    public string title;
    public string description;
    public string author;

    // rest omitted
}
public class LevelSO : ScriptableObject
{
    public TheData Data = new();

    // rest omitted
1 Like