Search Unity

Utilizing Inherited Classes in ScriptableObject Dictionary

Discussion in 'Scripting' started by IndieRex, May 29, 2017.

  1. IndieRex

    IndieRex

    Joined:
    May 29, 2017
    Posts:
    5
    Hi All,

    I'm using scriptableobjects and a dictionary for the item database in my game. It's all working well, but I want to expand it to be able to take different types of items that inherit from the base item class.

    For example, right now items attributes are defined in the ItemTemplate class, but I also want to have additional classes with additional attributes (e.g. FoodItemTemplate that would have all of ItemTemplate's attributes, but some additional ones like calories, protein, etc.). Any thoughts on how to get this to work?

    Item Template Script

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [CreateAssetMenu(menuName = "Items/Item Name")]
    6. public class ItemTemplate : ScriptableObject { // ScriptableObject
    7.  
    8.     //Requires Item Database Script
    9.  
    10.     public int itemID;
    11.     public string itemName;
    12.     public string itemDescription;
    13.  
    14.     // To Set An Item in Game
    15.     // ItemTemplate variableName = ItemDatabase.GetItem(itemid);
    16.     // e.g. ItemTemplate itemSlot1 = ItemDatabase.GetItem(2);
    17.  
    18.     //To Refer to Item Stats in Game
    19.     // variableName.statName
    20.     // e.g. item.itemQuantity = 4;
    21.  
    22.     // Note changing an item stat (e.g. itemQuantity) only impacts that instance of the item
    23.  
    24. }
    Example of Desired Additional Item Templates

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [CreateAssetMenu(menuName = "Items/Food Name")]
    6. public class ItemFoodTemplate : ItemTemplate // Food Inherits From ItemTemplate
    7. {
    8.     public int itemCalories;
    9.     public int itemProtein;
    10. }
    Item Database Script

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class ItemDatabase  {
    6.  
    7.     static private Dictionary<int, ItemTemplate> itemsDict;
    8.     static private bool isDatabaseLoaded = false;
    9.  
    10.     static private void ValidateDatabase() // Is list null and/or loaded?
    11.     {
    12.         if (itemsDict == null) itemsDict = new Dictionary<int, ItemTemplate>(); // If list is null, create list
    13.         if (!isDatabaseLoaded) LoadDatabase(); // If database is not loaded, load database
    14.     }
    15.  
    16.     static public void LoadDatabase()
    17.     {
    18.         if (isDatabaseLoaded) return;
    19.         isDatabaseLoaded = true;
    20.         LoadDatabaseForce();
    21.     }
    22.  
    23.     static public void LoadDatabaseForce()
    24.     {
    25.         ValidateDatabase();
    26.         ItemTemplate[] resources = Resources.LoadAll<ItemTemplate>(@"Items"); // Load all items from the Resources/Items folder
    27.         foreach (ItemTemplate item in resources)
    28.         {
    29.             if (!itemsDict.ContainsKey(item.itemID)) // If dictionary doesn't contain item then add it
    30.             {
    31.                 itemsDict.Add(item.itemID, item);
    32.             }
    33.         }
    34.     }
    35.  
    36.     static public void ClearDatabase() // Clear database to free up memory
    37.     {
    38.         isDatabaseLoaded = false;
    39.         itemsDict.Clear();
    40.     }
    41.  
    42.     static public ItemTemplate GetItem(int id)
    43.     {
    44.         ValidateDatabase();
    45.         ItemTemplate item;
    46.         if (itemsDict.TryGetValue(id, out item))
    47.         {
    48.             return ScriptableObject.Instantiate(item) as ItemTemplate;
    49.         }
    50.         return null;
    51.     }
    52.  
    53. }
    54.  
     
  2. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Look more closely at your LoadDatabaseForce. what do you think will happen if you call that while isDatabaseLoaded == false?

    you're loading items in twice. just a potential bug you might be getting...

    that aside, you failed to tell us what your problem actually is. from the looks of this code you basically have it handled.
     
  3. IndieRex

    IndieRex

    Joined:
    May 29, 2017
    Posts:
    5
    Sorry about that and thanks for your help! The problem is when actually trying to refer back to items that are of inherited classes. I can't seem to figure out the correct way to do it. For example, if in another script:

    Code (CSharp):
    1. void Start
    2. {
    3.         ItemTemplate itemSlot1 = ItemDatabase.GetItem(1); // Assume 1 is a "Food" item
    4.         Debug.Log(itemSlot1.itemName); // Works fine because it's an attribute that's in the normal ItemTemplate
    5.         Debug.Log(itemSlot1.itemCalories); // Doesn't work "'ItemTemplate' does not contain a definition for 'itemCalories' "
    6.  
    7.         ItemFoodTemplate itemSlot2 = ItemDatabase.GetItem(1); // This doesn't work either. "Cannot implicity convert type 'ItemTemplate' to 'ItemFoodTemplate' "
    8. }
    And for LoadDatabaseForce() are you saying to just add in an if statement like so?

    Code (CSharp):
    1.     static public void LoadDatabaseForce()
    2.     {
    3.         if (isDatabaseLoaded)
    4.         {
    5.             ValidateDatabase();
    6.             ItemTemplate[] resources = Resources.LoadAll<ItemTemplate>(@"Items"); // Load all items from the Resources/Items folder
    7.             foreach (ItemTemplate item in resources)
    8.             {
    9.                 if (!itemsDict.ContainsKey(item.itemID)) // If dictionary doesn't contain item then add it
    10.                 {
    11.                     itemsDict.Add(item.itemID, item);
    12.                 }
    13.             }
    14.         }
    15.     }
     
    Last edited: May 29, 2017
  4. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    I'd make a generic version of GetItem<T>(). Then your running code can simply try to get the item as the type.

    Code (CSharp):
    1. void Start
    2. {
    3.         var foodItem = ItemDatabase.GetItem<ItemFoodTemplate>(1);
    4.  
    5.         //check if you actually got a food item
    6.         if(!foodItem)
    7.         {
    8.             Debug.LogErrorFormat("Item ID: {0} is not a food or was not found!", 1);
    9.             return;
    10.         }
    11.  
    12.         Debug.Log(foodItem.itemName);
    13.         Debug.Log(foodItem.itemCalories);
    14. }

    As for the ItemDatabase . I would completely delete ValidateDatabase. its not needed and its just confusing everything up.

    Code (CSharp):
    1. public class ItemDatabase
    2. {
    3.  
    4.     static private Dictionary<int, ItemTemplate> itemsDict = new Dictionary<int, ItemTemplate>();
    5.     static private bool isDatabaseLoaded = false;
    6.  
    7.  
    8.     static public void LoadDatabase()
    9.     {
    10.         if (isDatabaseLoaded) return;
    11.  
    12.         LoadDatabaseForce();
    13.     }
    14.  
    15.     static public void LoadDatabaseForce()
    16.     {
    17.         itemsDict.Clear();
    18.         ItemTemplate[] resources = Resources.LoadAll<ItemTemplate>(@"Items"); // Load all items from the Resources/Items folder
    19.         foreach (ItemTemplate item in resources)
    20.         {
    21.             itemsDict[item.itemID] = item;
    22.         }
    23.  
    24.         isDatabaseLoaded = true;
    25.     }
    26.  
    27.     static public void ClearDatabase() // Clear database to free up memory
    28.     {
    29.         isDatabaseLoaded = false;
    30.         itemsDict.Clear();
    31.     }
    32.  
    33.     static public ItemTemplate GetItem(int id)
    34.     {
    35.         if (!isDatabaseLoaded)
    36.             LoadDatabaseForce();
    37.  
    38.         ItemTemplate item;
    39.         if (itemsDict.TryGetValue(id, out item))
    40.         {
    41.             return null;
    42.         }
    43.  
    44.         return ScriptableObject.Instantiate(item) as ItemTemplate;
    45.     }
    46.  
    47.     public static T GetItem<T>(int id) where T : ItemTemplate
    48.     {
    49.         if (!isDatabaseLoaded)
    50.             LoadDatabaseForce();
    51.        
    52.         ItemTemplate item;
    53.         if (!itemsDict.TryGetValue(id, out item))
    54.         {
    55.             return null;
    56.         }
    57.  
    58.         Type baseType = typeof(T);
    59.         Type itemType = item.GetType();
    60.  
    61.         if(baseType.isAssignableFrom(itemType))
    62.         {
    63.             return ScriptableObject.Instantiate(item) as T;
    64.         }
    65.  
    66.         return null;
    67.     }
    68. }
     
  5. IndieRex

    IndieRex

    Joined:
    May 29, 2017
    Posts:
    5
    Thank you again! So I tried implementing this and I get an error for lines 58/59 - "the type or namespace 'Type' could not be found."

    Admittedly I'm very new so this is beginning to get a bit beyond my understanding but I really appreciate it! I want to put in the extra effort at the beginning for a strong base of systems.
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Did it by any chance say that you might be missing an Assembly reference? (ie: using System; \)
     
  7. IndieRex

    IndieRex

    Joined:
    May 29, 2017
    Posts:
    5
    Ah yes - adding the System reference fixed it. That in turn made line 61 an error though for 'isAssignableFrom' being a definition not contained by Type. Am I missing another reference?

    Currently using:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System;
    4. using UnityEngine;
     
  8. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    retype it : IsAssign /// etc...
     
  9. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    yeah it was a Typo. Its IsAssignableFrom()
     
  10. IndieRex

    IndieRex

    Joined:
    May 29, 2017
    Posts:
    5
    It works beautifully! Thank you both - I really appreciate it.
     
  11. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Glad you got it working :)
     
    IndieRex likes this.