Item System

Item System guide page explains item system architecture, different type of items, how to create and use them.

Brief Explanation

The implementation of the item system has only two simple goals;
Customizable item creation with scriptable objects and a simple setup on prefabs via the Unity Editor.
To align with these goals, the code-first approach is used to ensure maximum extendability while also utilizing the component pattern that Unity uses.

Each Item instance in the world uses concrete classes derived from their related base. This is important because we create this instances by their full class name via DependencyInjection which gives the flexibility t have simple GameObjects with less components.

Item Instances are wrapped and used in Unity Components

Item Class Hierarchy

It’s a big tree of abstract and concrete classes you can find the full diagram below.

Draw.IO -> Item Class Diagram

Idea is to have Data classes which are configurable assets as ScriptableObjects. System creates InstanceClasses by using the values on DataClasses.

Item Resources Folder

Item database singleton loads all the folders specified in the item folders list and system uses this singleton to fetch related item data.

It is required to have related data Asset in Resource folder and ID of the asset should be unique.

Item Data Asset Types

All ItemData classes derived from ItemData

using System;
using System.Collections.Generic;
using UnityEngine;

namespace DTWorldz.Items.Data.Scriptables
{
    [CreateAssetMenu(fileName = "NewItemData", menuName = "Items/ItemData")]
    [Serializable]
    public class ItemData : ScriptableObject
    {
        public string ID;             // Unique identifier for the item
        public string ItemName;        // Name of the item
        public string ItemDescription; // Description of the item
        public GameObject Prefab;      // Reference to the item's prefab
        public Sprite Icon;            // Icon for the item in UI
        public List<Sprite> WorldSprites; // Sprites for the item in the world
        public int MaxStackSize;       // Maximum number of items in one stack
        public AudioClip DropSound;    // Sound played when the item is dropped
        public AudioClip PickupSound;  // Sound played when the item is picked up
        public Material HighlightMaterial; // Material used when the item is highlighted
        public string FullClassName;   // Full class name of the item
    }
}

Resources/Items/ContainersContainerData:

It is used for container type items like chests, bags..

  • using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewContainerData", menuName = "Items/ContainerData")]
        public class ContainerData : ItemData
        {
            public AudioClip OpenSound; // Sound played when the container is opened
            public AudioClip CloseSound; // Sound played when the container is closed
            public AudioClip LockSound; // Sound played when the container is locked
            public AudioClip UnlockSound; // Sound played when the container is unlocked
            public GameObject ContainerUIPrefab; // Reference to the container's UI prefab
            public bool IsLocked; // Flag to indicate if the container is locked
            public int MaxSlots; // Maximum number of slots in the container
        }
    }
    

Resources/Items/Consumables → ConsumableData

It is used to create consumable items like potions, food and maybe some other types in future

  • using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewConsumableData", menuName = "Items/ConsumableData")]
        public class ConsumableData : ItemData
        {
            public int Amount;
            public AudioClip ConsumeSound; // Sound played when the consumable is consumed
            public ParticleSystem ConsumeEffect; // Particle effect played when the consumable is consumed
        }
    }
    

Resources/Items/CraftingDesks → CraftingDeskData

It is used for crafting desks like like forge, anvil, campfire etc..

  • using System.Collections.Generic;
    using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewCraftingDeskData", menuName = "Items/CraftingDeskData")]
        public class CraftingDeskData : ItemData
        {
            public List<AudioClip> CraftingSounds; // Sounds played while crafting the item
            public AudioClip ActiveSuound; // Sound played when the crafting desk is active
            public AudioClip AddFuelSound; // Sound played when fuel is added to the crafting desk
            public GameObject CraftingUIPrefab; // Reference to the crafting desk's UI prefab
            public GameObject CraftingRecipeDB; // Reference to the crafting recipe database
            public string RequiredToolType; // Type of tool required to use the crafting desk
            public ParticleSystem HitParticleEffect; // Particle effect played when the crafting desk is hit
            public Sprite UIImage; // Image of the crafting desk
    
            public bool IsFuelRequired; // Flag to indicate if fuel is required to use the crafting desk
    
            public int StaminaDrain; // Amount of stamina drained after using the crafting desk
    
            public FuelData FuelData; // Data of the fuel required to use the crafting desk
        }
    }
    

    Resources/Items/Equipments → EquipmentData

    It is used for equipable items like shirt, shoes, leggings..

    using UnityEngine;
    using UnityEngine.U2D.Animation; // Required for SpriteLibrary
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewEquipmentData", menuName = "Items/EquipmentData")]
        public class EquipmentData : ItemData
        {
            public SpriteLibraryAsset SpriteLibrary; // Sprite Library for the animations of the equipment
            public Sprite EquippedSprite; // Sprite to display when the equipment is equipped on Character Panel
            public AudioClip EquipSound; // Sound played when the equipment is equipped
            public AudioClip UnequipSound; // Sound played when the equipment is unequipped
            public int Armor; // Armor value of the equipment
            public MaterialData MaterialData; // Material used to craft the equipment
            public int Durability; // Durability of the equipment
        }
    }
    

    Resources/Items/Materials/Coal → FuelData Resources/Items/Materials/TreeLogs → FuelData

    It is used to give some power to the FuelRequired Crafting desks like Forge or Campfire

    using System;
    using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewFuelData", menuName = "Items/FuelData")]
        [Serializable]
        public class FuelData : MaterialData {
            public float EnergyDurationPerUnit; // Duration of energy provided by one unit of fuel
    
        }
    }
    

    Resources/Items/Materials/Ingots → MaterialData

    Resources/Items/Materials/Wools → MaterialData

    Resources/Items/Materials/TreeLogs → MaterialData

    It is used to give some extra bonuses to the equipped items or projectiles

  • using System;
    using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables {
        [CreateAssetMenu(fileName = "NewMaterialData", menuName = "Items/MaterialData")]
        [Serializable]
        public class MaterialData : ItemData {
            public Color Color; // Color of the material to be set on the equipment or projectile
            public int DurabilityBonus; // Bonus durability provided by the material
            public int DamageBonus; // Bonus damage provided by the material
            public int DefenseBonus; // Bonus defense provided by the material
        }
    }
    

    Resources/Items/Projectiles/ → ProjectileData

    It is used to calculate projectile bonuses

  • using UnityEngine;
    
    namespace DTWorldz.Items.Data.Scriptables
    {
        [CreateAssetMenu(fileName = "NewProjectileData", menuName = "Items/ProjectileData")]
        public class ProjectileData : ItemData
        {
            public float LifeTime; // Time after which the projectile is destroyed
            public AudioClip HitSound; // Sound played when the projectile hits a target
            public ParticleSystem HitEffect; // Particle effect played when the projectile hits a target
            public int Damage; // Damage dealt by the projectile used to calculate the total damage dealt to the target
            public MaterialData MaterialData; // Material used to craft the projectile
        }
    }
    

Resources/Items/Trees → ResourceData

Resources/Items/Trees → ResourceData

It is for harvestable items

using System.Collections.Generic;
using UnityEngine;

namespace DTWorldz.Items.Data.Scriptables
{
    [CreateAssetMenu(fileName = "NewResourceData", menuName = "Items/ResourceData")]
    public class ResourceData : ItemData
    {
        public string RequiredToolType; // Type of tool required to gather the resource
        public int ResourceAmount; // Amount of resource gathered from the item
        public ItemData ResourceItem; // Item data of the resource gathered
        public List<ItemData> OnDestroyItems; // Items dropped when the resource is destroyed
        public AudioClip DestroyedSound; // Sound played when the resource is destroyed
        public ParticleSystem DestroyedParticleEffect; // Particle effect played when the resource is destroyed
        public ParticleSystem HitParticleEffect; // Particle effect played when the resource is hit
        public int StaminaDrain; // Amount of stamina drained when gathering the resource
        public float GatherChanceFactor; // Factor used to calculate the chance of gathering the resource
        public List<AudioClip> HitSounds; // Sounds played when the resource is hit
    }
}

Item Creation

Runtime

It is possible to create items in runtime by directly calling their constructers as below.

var woodenShield = new WoodenShield();
// var woodenShieldResult = InventoryBehaviour.AddItem(woodenShield, 1);
// // if (woodenShieldResult)
// // {
// //     InventoryBehaviour.EquipItem(woodenShield);
// // }

Edit Mode

Creating an item in edit requires two main steps

1- Creating the ItemData scriptable object ob resource folder as it is explained above

2- Creating GameObject in the scene so the data can also be loaded directly via the item’s class instance.

Hierarchy is like below

— GameObject → (Mediator, ItemInitializer)
—— 2DRenderer (SpriteRenderer)
—— ItemLabelCanvas (which is a prefab you can found in PrefabsFolder)

ItemInitializer component has two public fields which are required as below.

  • ItemClassName(Required): This is used to create the instance via injection.

    System.Type itemType = System.Type.GetType(itemClassName);
    if (itemType != null)
    {
        itemInstance = (BaseItem)System.Activator.CreateInstance(itemType);
    }
    else
    {
        Debug.LogError("Failed to create item class instance: " + itemClassName);
    }
    
  • MaterialData(Optional): If item is IMaterializable, the material bonuses will be set to the item in initialization process

Items in the scene are handle via Mediator pattern which is implemented with events. Each item type has it’s own Mediator class which are inherited via MonoBehaviour. Mediator’s will include required componenets for an item via script.
An example Mediator class can be found below.

using DTWorldz.Behaviours.Mobiles;
using DTWorldz.Crafting.Data.Scriptables;
using DTWorldz.Crafting.Behaviours.UI;
using DTWorldz.Items.Behaviours.CraftingDesks;
using DTWorldz.Items.Models.Materials.Fuels;
using UnityEngine;

namespace DTWorldz.Items.Models.CraftingDesks.Simple
{
    public class SimpleCraftingDeskMediator : BaseCraftingDeskItemMediator<BaseCraftingDesk, SimpleCraftingDeskBehaviour, SimpleCraftingDeskAudioBehaviour, SimpleCraftingDeskParticleEffectBehaviour>
    {
        [SerializeField]
        bool isMenuOpen = false;
        MobileBehaviour mobileBehaviour;
        public Animator Animator { get; private set; }
        public override void Awake()
        {
            gameObject.AddComponent<SimpleCraftingDeskBehaviour>();
            gameObject.AddComponent<SimpleCraftingDeskAudioBehaviour>();
            gameObject.AddComponent<SimpleCraftingDeskParticleEffectBehaviour>();
            base.Awake();

            Animator = GetComponentInChildren<Animator>();

            if (ItemBehaviour != null)
            {
                ItemBehaviour.OnFuelAdded += OnFuelAdded;
                ItemBehaviour.OnFuelConsumed += OnFuelConsumed;
                ItemBehaviour.OnFuelDepleted += OnFuelDepleted;
            }
        }

        public override void OnFuelConsumed(int amount){
            base.OnFuelConsumed(amount);
            // nothing here?
        }

        public override void OnFuelDepleted(){
            base.OnFuelDepleted();
            if(Animator != null){
                Animator.Play("notActive");
            }

            // update animation
            // turn off sound
        }

        public override void OnFuelAdded(BaseFuel fuel)
        {
            base.OnFuelAdded(fuel);
            mobileBehaviour.InventoryBehaviour.RemoveItem(fuel.ID, 1);
            if(Animator != null){
                Animator.Play("active");
            }
        }

        public void Interact(MobileBehaviour behaviour)
        {
            OnInteract(behaviour);
        }

        public override void OnInteract(MobileBehaviour behaviour)
        {
            if(isMenuOpen){
                return;
            }
            mobileBehaviour = behaviour;

            // if this is called, it means the player interacted with the crafting desk sucessfully
            // show the crafting UI
            // the crafting UI will use this mediator to interact with the crafting desk
            var uiObject = Instantiate(ItemBehaviour.BaseItem.CraftingUIPrefab);
            var uiBehaviour = uiObject.GetComponent<CraftingDeskUIBehaviour>();
            uiBehaviour.Initialize(this, behaviour);
            behaviour.OnCraftingStarted += CraftingStarted;
            behaviour.OnCraftingEnded += CraftingEnded;
            uiBehaviour.OnClose += () => {
                isMenuOpen = false;
                behaviour.OnCraftingStarted -= CraftingStarted;
                behaviour.OnCraftingEnded -= CraftingEnded;
                mobileBehaviour = null;
            };
            isMenuOpen = true;
        }

        private void CraftingEnded()
        {
            // we don't need to do anything here for now
        }

        private void CraftingStarted(CraftingRecipe recipe)
        {
            StartCoroutine(ExecuteAfterTime(0.5f, () => {
                ItemParticleEffectBehavior.PlayCraftingParticleEffect(recipe);
            }));
            ItemAudioBehavior.PlayCraftingSound(recipe);
        }
    }
}

Different type of Mediators is listed on next topic.

Available Item Mediators

FoodMediator

This can be added to the items which are consumable and they are inherited from BaseFood.

Examples are: CookedRibEye, CookedSmallAnimalMeat, CookedBigAnimalMeat

PotionMediator

This can be added to the items which are consumable and they are inherited from BasePotion.

Examples are: HealthPotion

ChestMediator

This can be added to the items which are inherited from ChestItem

Examples are: WoodenChest

SimpleCraftingDeskMediator

This can be added to the items which are basically inherited from BaseCraftingDesk

Examples are: Anvil, Forge, Campfire, CarpentersDesk etc..

SimpleCraftingDeskMediator

This can be added to the items which are basically inherited from BaseCraftingDesk

Examples are: Anvil, Forge, Campfire, CarpentersDesk etc..

ArrowMediator

This can be added to the arrow type items which are basically inherited from BaseProjectile

Examples are: Arrow

TreeMediator

This can be added to the tree items which are basically inherited from BaseTreeItem

Examples are: BasicTree, Conifers, PlaneTree

OreMineMediator

This can be added to the harvestable mine items which are basically inherited from BaseOreMineItem

Examples are: CopperMine, StoneMine, CoalMine, GoldMine etc..

SimpleItemMediator

This can be added to simple items which has no special ability.

Examples are: Stone

WeaponMediator

This can be added to weapon items

Examples are: Bow, Axe, Katana, Pickaxe, Hamer

TorsoMediator

This can be added to the items which are equipped to the torso layer of the character

Examples are: LongShirt

LeggingsMediator

This can be added to the items which are equipped to the leggings layer of the character

Examples are: LongPants

ShoesMediator

This can be added to the items which are equipped to the shoes layer of the character

Examples are: Shoes

OffHandMediator

This can be added to the items which are equipped to the offhand layer of the character

Examples are: WoodenShield

Updated on