So I was working on another topic altogether, replacing enums with ScriptableObject place holders (just trust me, this is a great way to decouple your architecture, and give control to the designer) when I came upon the problem of saving an EquipmentItem along with its slot. It occurred to me that I could use the same mechanism used to save/restore an InventoryItem reference to save the EquipmentItem reference.
static Dictionary<string, InventoryItem> itemLookupCache;
public static InventoryItem GetFromID(string itemID)
{
if (itemLookupCache == null)
{
itemLookupCache = new Dictionary<string, InventoryItem>();
var itemList = Resources.LoadAll<InventoryItem>("");
foreach (var item in itemList)
{
if (itemLookupCache.ContainsKey(item.itemID))
{
Debug.LogError(string.Format("Looks like there's a duplicate GameDevTV.UI.InventorySystem ID for objects: {0} and {1}", itemLookupCache[item.itemID], item));
continue;
}
itemLookupCache[item.itemID] = item;
}
}
if (itemID == null || !itemLookupCache.ContainsKey(itemID)) return null;
return itemLookupCache[itemID];
}
So I copied the logic from InventoryItem into my ScriptableEquipSlot and adapted it to load the SES instead of the InventoryItem…
static Dictionary<string, ScriptableEquipSlot> itemLookupCache;
public static InventoryItem GetFromID(string itemID)
{
if (itemLookupCache == null)
{
itemLookupCache = new Dictionary<string, ScriptableEquipSlot>();
var itemList = Resources.LoadAll<ScriptableEquipSlot>("");
foreach (var item in itemList)
{
if (itemLookupCache.ContainsKey(item.itemID))
{
Debug.LogError(string.Format("Looks like there's a duplicate ID for objects: {0} and {1}", itemLookupCache[item.itemID], item));
continue;
}
itemLookupCache[item.itemID] = item;
}
}
if (itemID == null || !itemLookupCache.ContainsKey(itemID)) return null;
return itemLookupCache[itemID];
}
That’s when I started thinking… what if I need to use this yet again… this is starting to look like what we call a “WET” solution… (“We Enjoy Typing”/“Write Everything Twice”/“Waste Everyone’s Time”)… Whenever possible, we should be using DRY solutions (“Don’t Repeat Yourself”).
So I created an alternate solution using Generics. Generics allow you to declare a class using a generic placeholder… it’s saying “Ok, I have this class, but I don’t want to determine what it is right now, I’ll determine what it is when I use it.” Traditionally, that generic type is represented with a T.
public class List<T>{}
This is probably the most well known example of a generic class. It makes whatever class you want and turns it into a powerful flexible array.
I decided to use this powerful tool to make a Resource Retriever capable of loading any ScriptableObject from the Resources folder (subject to a simple contraint, The SO must contain the method GetItemID();
First, I need a simple interface to help with the GetItemID() function.
public interface IHasItemID
{
string GetItemID();
}
Just make any ScriptableObject you want to be able to load from a resource folder implement this simple interface, and the rest of the code will work flawlessly.
Now here’s the class that does the work:
// The Where T: ScriptableObject, IHasItemID means only in item that has
//BOTH of these things qualifies.
public class ResourceRetriever<T> where T : ScriptableObject, IHasItemID
{
static Dictionary<string, T> itemLookupCache;
public static T GetFromID(string itemID)
{
if (itemLookupCache == null)
{
itemLookupCache = new Dictionary<string, T>();
var itemList = Resources.LoadAll<T>("");
foreach (var item in itemList)
{
if (itemLookupCache.ContainsKey(item.GetItemID()))
{
Debug.LogError(string.Format("Looks like there's a duplicate ID for objects: {0} and {1}", itemLookupCache[item.GetItemID()], item));
continue;
}
itemLookupCache[item.GetItemID()] = item;
}
}
if (itemID == null || !itemLookupCache.ContainsKey(itemID)) return null;
return itemLookupCache[itemID];
}
}
So now, when you want to load a resource, you simply have to call the ResourceRetriever. You can remove the GetFromID() from the Inventory Item (or any other class you’re using it in), or in the case of InventoryItem, you might want to change the function to read:
public static InventoryItem(string ItemID)
{
return ResourceRetriever.GetFromID(ItemID);
}