Items alias ScriptableObjects. Log #15 (Unity, C#)
01.03.2018

    Now, lets digress from inventory and focus on area that is related to it. Lets focus on items in game. To continue with inventory I need some items and system how to deal with them. In this log I will only explain my way of creating the items in Unity, and in next logs I will making models in Blender and then attaching them to my Unity items, also making them interactable. Only then I can return to inventory. For so long I had mess in my items creating system, til I came across ScriptableObjects on internet.

    Simply said, ScriptableObject is some sort of container for data of any object. For instance if I have item, that has statistics as name, strength, defense and other attributes, ScriptableObject stores this data and allows me to create instances of this object. Another advantage of this is direct relation with Unity editor, meaning that I can create my items through context menu (like creating new sword, then I just fill attributes for this sword). I am using an utility I found on the internet and which very simplifies creating the items. The key of this utility is a generic function, allowing to create items directly into my Asset folder.

 

using UnityEngine;
using UnityEditor;
using System.IO;

public static class ScriptableObjectUtility
{
    /// <summary>
    //	This makes it easy to create, name and place unique new ScriptableObject asset files.
    /// </summary>
    public static void CreateAsset<T>() where T : ScriptableObject
    {
        T asset = ScriptableObject.CreateInstance<T>();

        string path = AssetDatabase.GetAssetPath(Selection.activeObject);
        if (path == "")
        {
            path = "Assets";
        }
        else if (Path.GetExtension(path) != "")
        {
            path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
        }

        string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/New " + typeof(T).ToString() + ".asset");

        AssetDatabase.CreateAsset(asset, assetPathAndName);

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = asset;
    }
}

 

    This class is ready to copy without any need of changing anything. Just put this script into Asset/Editor folder (it's neccesary to put it in Editor folder).  And for creating the item in context menu just add new script with this content.

 

using UnityEditor;

public class ItemAsset
{
    [MenuItem("Assets/Create/Item/Item")]
    public static void CreateAsset()
    {
        ScriptableObjectUtility.CreateAsset<Item>();
    }
}

 

    Line [MenuItem("Assets/Create/Item/Item")] is path in context menu, where I can find my item. In method CreateAsset() I call generic function CreateAsset<T>(), which I "created" above, where T is my item class. So, how should my item class look like?

    Actually anyhow, however there is one thing and that is, it can't inherit from MonoBehaviour class. Because new item class inherits from ScriptableObject and as far as I know C# dont support multiple class inheritance. So my item system will be this - Item class will be superior class inheriting from ScriptableObject and other item classes will inherit from Item. I will explain it on example of Pickaxe. Pickaxe is a tool deriving basic characteristics from Tool class, therefore inherits from class Tool. But any tool is also a weapon, it will have attack number and other weapon attributes. So it will inherit from Weapon class. And same as any armor, weapons are equipment, so Weapon inherits from Equipment, and finally Equipment inherits from superior class Item. Each of these classes have variables, that defines the item. Values of these variables I fill in inspector in new created ScriptableObject. Below is whole mentioned hierarchy.

 

using UnityEngine;

public class Pickaxe : Tool {

    [Header("Pickaxe")]
    public int miningStrength;
    public int miningSpeed;

    public void Instantiate(string itemName, string itemDescription, string itemIconName, string itemPrefabName, int attackStrength, int attackSpeed, int miningStrength, int miningSpeed)
    {
        Instantiate(itemName, itemDescription, itemIconName, itemPrefabName, attackStrength, attackSpeed, true, ToolType.Pickaxe);
        this.miningStrength = miningStrength;
        this.miningSpeed = miningSpeed;
    }
}
using UnityEngine;

public class Tool : Weapon
{
    [Header("Tool")]
    public ToolType toolType;

    public void Instantiate(string itemName, string itemDescription, string itemIconName, string itemPrefabName, int attackStrength, int attackSpeed, bool twoHandedWeapon, ToolType toolType)
    {
        Instantiate(itemName, itemDescription, ItemType.Tools, itemIconName, "Tools/" + itemPrefabName, attackStrength, attackSpeed, false, twoHandedWeapon);
        this.toolType = toolType;
    }
}

public enum ToolType
{
    None,
    Axe,
    Pickaxe
}
using UnityEngine;

public class Weapon : Equipment
{
    [Header("Weapon")]
    public int attackStrength;
    public int attackSpeed;
    public bool longRangedWeapon;
    public bool twoHandedWeapon;

    public void Instantiate(string itemName, string itemDescription, ItemType itemType, string itemIconName, string itemPrefabName, int attackStrength, int attackSpeed, bool longRangedWeapon, bool twoHandedWeapon)
    {
        Instantiate(itemName, itemDescription, itemType, itemIconName, "Weapons/" + itemPrefabName, EquipmentType.RightHand, true);
        this.attackStrength = attackStrength;
        this.attackSpeed = attackSpeed;
        this.longRangedWeapon = longRangedWeapon;
        this.twoHandedWeapon = twoHandedWeapon;
    }
}
using UnityEngine;

public class Equipment : Item
{
    [Header("Equipment")]
    public EquipmentType equipmentType;
    public bool isWeapon;

    public void Instantiate(string itemName, string itemDescription, ItemType itemType, string itemIconName, string itemPrefabName, EquipmentType equipmentType, bool isWeapon)
    {
        Instantiate(itemName, itemDescription, itemType, itemIconName, itemPrefabName, true);
        this.equipmentType = equipmentType;
        this.isWeapon = isWeapon;
    }
}

public enum EquipmentType
{
    None,
    Head,
    Chest,
    Legs,
    Feet,
    RightHand,
    LeftHand,
    Lenght
}
using UnityEngine;

public class Item : ScriptableObject
{
    [Header("Item")]
    public string itemName;
    public string itemDescription;
    public ItemType itemType;
    public Sprite itemIcon;
    public GameObject itemPrefab;
    public bool isEquipment;

    public void Instantiate(string itemName, string itemDescription, ItemType itemType, string itemIconName, string itemPrefabName, bool isEquipment)
    {
        this.itemName = itemName;
        this.itemDescription = itemDescription;
        this.itemType = itemType;
        itemIcon = Resources.Load<Sprite>("UI/Icons/Items/" + itemIconName);
        itemPrefab = Resources.Load<GameObject>("Objects/Items/" + itemPrefabName);
        this.isEquipment = isEquipment;
    }
}

public enum ItemType
{
    Weapons,
    Armor,
    Tools,
    Potions,
    Books,
    Goods
}

 

    Line [Header("Item")] creates title in inspector above the variable, or block of variables til another header. It makes order in new created ScriptableObject, so I know which variable belongs to what class. public enum ItemType is definition of item types which will be used for inventory panels later. Some characterstics I can attach already in Instatntiate() method, for example I know that any tool is ItemType.Tool and I can attach it to ItemType.itemType, so I dont have to fill everything in the inspector. Newly created ScriptableObject, alias my item looks like this.

 

ScriptableObject

 

    This is very solid base for items. So now I can create any items as swords, armors, books etc. and define its attributes. In next log I create some model for my ScriptebleObject item in Blender.