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 DiagramIdea 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/Containers → ContainerData:
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