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

Complex data structures with ScriptableObjects in Unity3D


I am trying to create a data structure like in the picture, where I can easily add and remove the reward type. It is for a roguelike level-up system where basically I want to get a reward for a specific character like a warrior or mage from a specific rarity.

I am more experienced with Lua than Unity and in Lua language, it would look like this,

`{
    ["Melee"] = 
    
    {
        [1] = {
            [1] = {"Stat", "Strength", 10},
            [2] = {"Ability", "Swirl", 1},
            
        },
        
        [2] = {
            [1] = {"Stat", "Strength", 50},
            [2] = {"Stat", "PhysicalDefense", 10},
        },
    },
    
    ["Healer"] = 
        {
        [1] = {
            [1] = {"Stat", "MagicalAttack", 5},
            [2] = {"Ability", "Regeneration", 1},
        },

        [2] = {

            [1] = {"Stat", "MagicalDefense", 15},
            [2] = {"Ability", "Regeneration", 1},
        },
    },
}` 

then for getting a spesific reward I would do reward = ["Melee][1][1]. However, in Unity, using dictionaries disables scriptableobject's function to add elements inside editor. So how can I create a scriptableobject that I can add elements inside the editor with the same structure?


Solution

  • You don't need dictionaries in order to access items by index.

    Simply use a plain array or List and do

    public class Warrior : ScriptableObject
    {
        public Rarity[] rarities;
    }
    
    public class Rarity : ScriptableObject 
    {
        public Reward[] rewards;
    }
    
    public class Reward : ScriptableObject
    {
        public enum RewardType { Stat, Ability }
    
        public RewardType type;
        public int value;
    }
    

    And finally reference these in a

    public Warrior[] warriors;
    

    Then if really needed for the first level you can still setup a dictionary on runtime like e.g.

    public Dictionary<string, Warrior> Warriors = new ();
    
    private void Start ()
    {
        foreach(var warrior in warriors)
        {
            Warriors.Add(warrior.name, warrior);
        }
    }
    

    Then the rest you would access like e.g.

    var reward = Warriors["Melee"].rarities [0].rewards[1];
    Debug.Log($"{reward.type} - {reward.name} - {reward.value}");
    

    Have in mind that in c# indices are 0-based


    However, if you are more familiar with your way and prefer a general dictionary approach you can totally do so!

    You could simply write this as a JSON and can then chose among the various JSON libraries to convert it into a dictionary again.

    {
        "Melee" : [
            [ 
                {
                    "type":"Stat", 
                    "name":"Strength", 
                    "value":10
                },
                {
                    "type":"Ability", 
                    "name":"Swirl",
                    "value":1
                }            
            ],
            ...
        ],
        "Healer" : ...
    }
    

    then you would not use ScriptableObject and only use e.g.

    [Serializable]
    public class Reward
    {
        public enum RewardType { Stat, Ability }
    
        public RewardType type;
        public int value;
        public string name;
    }
    

    and then deserialize this into e.g.

    var Warriors = JsonConvert.DeserializeObject<Dictionary<string, Reward[][]>>(theJsonString);
    var reward = Warriors["Melee"][0][0];
    Debug.Log($"{reward.type} - {reward.name} - {reward.value}");
    

    where theJsonString can be read form a file or a web request etc

    This example uses Newtonsoft JSON.Net which is available for Unity via the package manager.

    Personally I would though rather go with explicitly named fields and classes instead of those jagged arrays like before

    {
        "Melee" : {
            "rarities" : [
                {
                    "rewards" : [ 
                        {
                            "type":"Stat", 
                            "name":"Strength", 
                            "value":10
                        },
                        {
                            "type":"Ability", 
                            "name":"Swirl",
                            "value":1
                        }  
                     ]
                } 
            ]
        },
    
        "Healer" : ...
    }