Equipment system for an RPG Unity game

  • 07th Sep 2020
  • by Pav
  • Reading Time: 16 minutes

Introduction

Equipment system, as the name implies, gives players the possibility to equip their characters with weapons and armours on their journey. This kind of feature is common for an RPG style game as part of character’s development. The statistical might of our heroes in battles is dictated by the kind of equipment they wield. It is therefore important to give our players the means to review and equip any piece of equipment we find on our quest. In this tutorial, we will be looking at how we can create such system.

Equipment Systems in final fantasy 9

For the purpose of this article I’m going to integrate the scrollable menu in Unity with button or key controller and inventory system I briefly touch-based in one of my previous posts.

Table of contents:

  1. The base of the equipment system – characters status
  2. The concepts of the equipment system
  3. The core implementation of the equipment system
  4. The equipment system main menu screen

1. The base of the equipment system – characters status

In one of my earliest articles on battle systems I’ve created a very simple ScriptableObject for storing players stats. Back then I have not put too much focus on introduction of different stats attributes. The attributes that would contribute in the calculation of different amounts and numbers during battles. The character had only two attributes defining its HP (Hit Points) and MP (Mana Points) that were interchangeably modifies during a turn based battle by a fixed amount. However, to make the game a bit more challenging we want to dynamically modify these values in relation to characters and enemies battle statistics. To that end I’m going to introduce several attributes representing overall battle capacity of the player. It is really up to you what kind of attributes you want to declare but I will go with the main four, namely strength, defence, magic and magic defence.

using UnityEngine;

[CreateAssetMenu(fileName = "HealthStatusData", menuName = "StatusObjects/Health", order = 1)]
public class CharacterStatus : ScriptableObject
{
    public string charName = "name";
    public int level      = 1;
    public int basemaxhp  = 0;
    public int basemaxmp  = 0;
    public int basehp     = 0;
    public int basemp     = 0;
    public int basestr    = 0;
    public int basedef    = 0;
    public int basemgc    = 0;
    public int basemgcdef = 0;
    public int maxhp      = 0;
    public int maxmp      = 0;
    public int hp         = 0;
    public int mp         = 0;
    public int str        = 0;
    public int def        = 0;
    public int mgc        = 0;
    public int mgcdef     = 0;
}

But hold on! There are more than just four attributes in this listing! Well, yes since we want to calculate the final stats of our character upon equipping our gear, we first need to know his base state. The base state is usually dependant on something that is unrelated to the equipment we currently wear. In most RPG games, that is the character’s level (which is also declared here). It will hopefully make more sense later on so keep on reading!

2. The concepts of the equipment system

In order to create an equipment system we first need to define few concepts behind it. Let’s start by deliberating on higher level conceptual elements that will be part of the system. The most elementary piece is going to be an item residing in our inventory. At any moment in time (as long as the game allows us) we can execute a specific action. That action is going to be strictly coupled with with its type. For instance, we can use potion to heal ourselves or we can equip an armour to increase our defensive stats. Furthermore, we can also drop an item if we want to make space in our inventory. Following this logic, whenever the item is “equitable” we can define its specific slot that can be assigned to, e.g. sword can only be equipped in a hand slot.

The Inventory

The concept of inventory holding a variety of different types of items require a clearly defined architecture. Careful reader will notice that between all items types there exists some commonalities they share. Every item will have a name, potentially an icon representing it in UI and, you guessed it, its type.

What I’m going to do is that I will create a parent class holding all this data. After that, I’ll take the advantage of inheritance to derive two child classes – one for items that modify statistics upon use and one for equipment pieces that player can wear. The reason I’m doing this in such a way is that I can organise the data around specific type of items. For instance, let’s say that in my game I want to have items that modify the health and mana of the characters (potions, elixirs, grenades etc.). On the other hand, I want to have items that modify character’s statistics to improve his or her combat proficiency upon equipping. In such a case, I don’t want these two type of items share specific attributes I’m going to operate on during their use. The below diagram clarifies this philosophy a bit more.

By structuring the items in this manner, I’m making the entire system more expandable and maintainable. Let’s say that at some point later in the development we decide to introduce a concept of “Key Items”. It is often the case in adventure games to stumble upon items that are required to advance the story further. In such a case I simply have to introduce a new class deriving from Item.

The Equipment

As you may have already noticed from the last diagram, the Equipment class carries four “modifiers”. These are the attributes I’m going to assign to each piece of equipment inside my game. Then, whenever the player equips a given piece I will calculate the final stats by adding individual values to corresponding fields being part of character status. This is where our “base” stats will come into play. Without them the entire process would be a bit cumbersome. This is especially true for swapping a component in a given slot.

Let’s imagine that we have defined 5 different slots we can assign different pieces of equipment to. We can see that the character wields short sword in his hand slot. Now he wants to swap it with a legend sword that’s inside the inventory. To preview changes we would have to get a value of a given attribute, subtract the value of the current weapon modifier and add a value of a new weapon modifier.

This may not sound like much but then imagine that our character leveled up, gained some additional points in the stats and unequipped both swords. How much strength points does the character have now? Are we going to keep adding and subtracting the values of weapons modifiers to get that result? Needless to say that it is very error prone and it’s very easy to lose track in the calculations. Just to make our life easier, I’ll simply introduce additional “base” values to remedy this situation.

Now that we understand the concepts, let’s implement our equipment system!

3. The core implementation of the equipment system

I’ll start by defining the items structure outlined in previous chapter. Let’s create the base Item class that will hold all the common attributes. In here, I’ll also declare the ItemType enum that will contain all the item types available in the game (line 3).

using UnityEngine;

public enum ItemType { heal, offensive, defensive, equipment }

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item", order = 1)]

public class Item : ScriptableObject
{
    new public string name = "New Item";
    public Sprite icon;
    public ItemType type;

    public virtual void Use()
    {
        // this function is supposed to be overriden
    }

    public virtual void Drop()
    {
        Inventory.instance.RemoveItem(this);

        // this function is supposed to be overriden
        // if further functionality is needed
    }
}

Line 20 contains reference to the Inventory class that will contain a List<Item> field. It will be used to store all of our items we find on the way.

For the sake of this tutorial we’re not interested in the StatsItem that much, so I’ll not get into the implementation details of the Use() function. However, I’m going to declare to illustrate the difference between standard “usable” items and Equipment.

using UnityEngine;

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Stats Item", order = 1)]

public class StatsItem : Item
{
    public int potency;

    public override void Use()
    {
        base.Use();
        Debug.Log("Using Item " + name + " with potency of " + potency);
    }
}

Like I showed in the diagram earlier, each piece of equipment will have four integer modifiers enhancing the base statistics of our character. In order to differentiate between different types of equipment (weapon, armour etc.) I’m going to declare another enum type field containing all possible slots. The slots themselves are going to refer to individual body parts of our character such as hand, neck, torso etc.

using UnityEngine;

public enum EquipType { HAND, HEAD, NECK, TORSO, FINGERS }

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Equipment")]

public class Equipment : Item
{
    public EquipType equipType;

    public int strModifier;
    public int defModifier;
    public int mgcModifier;
    public int mgcdefModifier;

    public override void Use()
    {
        base.Use();
        EquipmentManager.instance.Equip(this);
        Inventory.instance.RemoveItem(this);
    }
}

Lines 19-20 contain the references to Inventory and EquipmentManager global classes that we will take care of next!

Managing different item types

Every hero needs to have an inventory of items at his disposal to support his ultimate quest. In this short sub-chapter, I’m going to implement a global class that will contain a list of items currently being held by the player. In here, I’ll also make sure to define an enum listing all possible actions we can execute on an item (line 5).

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

public enum ItemAction { USE, EQUIP, DROP }

public class Inventory : MonoBehaviour
{
    public List<Item> items = new List<Item>();

    public delegate void OnItemChanged();
    public OnItemChanged onItemChangedCallback;

    #region Singleton
    public static Inventory instance;

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(this);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
    #endregion

    public void AddItem(Item item)
    {
        items.Add(item);
        if(onItemChangedCallback != null)
            onItemChangedCallback.Invoke();
    }

    public void RemoveItem(Item item)
    {
        items.Remove(item);
        if (onItemChangedCallback != null)
            onItemChangedCallback.Invoke();
    }


}

On line 9 I’m declaring a data structure (List) of type Item. By doing so I will be able to store any kind of item that was derived from a base Item class. Since we are going to need only a single instance of an Inventory for our game, I’m going to initialise it using singleton pattern (lines 14-29). I also defined a delegate callback method that will be invoked every time the state of our inventory changes. This can come in handy when we want to automatically update the UI panels.

Assigning the equipment piece to correct slot

At this point we have our items and an inventory in which we can store them. What we need now is a script that will be responsible for keeping track of the character’s current equipment. First let’s declare an EquipmentManager class. I’ll once again initialize it using the singleton pattern since we only need one instance of it for our game sessions. In addition I’ll declare an array of Equipment type to hold our gear and a delegate callback that will be invoked each time our character equips something.

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

public class EquipmentManager : MonoBehaviour
{
    #region Singleton
    public static EquipmentManager instance;

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(this);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
    #endregion Singleton

    public Equipment[] currentEquipment;

    public delegate void OnEquipmentChangedCallback();
    public OnEquipmentChangedCallback onEquipmentChangedCallback;

}

The code should be relatively straightforward at this point. Next, I’ll initialize the array with the correct number (indexes) of available slots. To do this I’ll use the System.Enum.GetNames() built-in function to count the elements of the EquipType enum we declared earlier inside the Equipment (lines 13-17).

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

public class EquipmentManager : MonoBehaviour
{
    #Singleton

    public Equipment[] currentEquipment;
    public delegate void OnEquipmentChangedCallback();
    public OnEquipmentChangedCallback onEquipmentChangedCallback;
    
    private void Start()
    {
        int numSlots = System.Enum.GetNames(typeof(EquipType)).Length;
        currentEquipment = new Equipment[numSlots];
    }
}

In this class we have to define the most important function – Equip(). As you may already know by the name, it’s main task will be the assignment of a given item to correct slot. However, we also have to take into account situations when we want to assign a new piece of gear to already occupied slot. We then need to ensure that the old item is removed from the slot and taken back to the inventory.

public void Equip(Equipment newItem)
{
    int equipSlot = (int) newItem.equipType;

    Equipment oldItem = null;

    if(currentEquipment[equipSlot] != null)
    {
        oldItem = currentEquipment[equipSlot];
        Inventory.instance.AddItem(oldItem);
    }

    currentEquipment[equipSlot] = newItem;

    StatusManager.instance.UpdateCharacterStatus(newItem, oldItem);  

    onEquipmentChangedCallback.Invoke();    
}

First, I’m getting the index of an enum type of the item we want to equip (line 3). After that I’m checking the occupancy of the slot (line 7). If it already contains an item then I’m adding it back to the Inventory (line 9-10). Finally I’m assigning the item to the correct slot (line 13), updating the character’s stats (line 15) and letting know other interested scripts about the change by invoking a callback (line 17). At this stage the most important line is the one where I’m updating the character stats (line 15). Let’s take care of it next!

Updating the character’s stats

To keep track of the character’s stats and maintain their values I will define another global class called StatusManager. The script itself is not going to be big, but will allow us to separate the equipping logic from the actual statistics. The most important method in this class is going to be UpdateCharacterStatus(Equipment newItem, Equipment oldItem) responsible for updating the character’s stats.

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

public class StatusManager : MonoBehaviour
{
    public CharacterStatus playerStatus;

    #region Singleton
    public static StatusManager instance;

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(this);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

    #endregion

    public void UpdateCharacterStatus(Equipment newItem, Equipment oldItem)
    {
        
    }
}

As you can see, this method is going to be called from the Equip() function in EquipmentManager. The function will take two arguments: references to both, old and new item we want to equip. That way we can get access the numerical modifiers of both pieces of gear.

Firstly, I’m going to check if the oldItem has been provided. If it’s not null then I need to subtract its modifiers values from the current stats.

public void UpdateCharacterStatus(Equipment newItem, Equipment oldItem)
{
    if(oldItem != null)
    {
        playerStatus.str -= oldItem.strModifier;
        playerStatus.def -= oldItem.defModifier;
        playerStatus.mgc -= oldItem.mgcModifier;
        playerStatus.mgcdef -= oldItem.mgcdefModifier;
    } 
}

After that I need to take the modifiers of the newItem and add them to “base” stats. By doing so I’m ensuring the integrity of the numerical status of the character each time the player equips new piece of equipment.

public void UpdateCharacterStatus(Equipment newItem, Equipment oldItem)
{
    if(oldItem != null)
    {
        playerStatus.str -= oldItem.strModifier;
        playerStatus.def -= oldItem.defModifier;
        playerStatus.mgc -= oldItem.mgcModifier;
        playerStatus.mgcdef -= oldItem.mgcdefModifier;
    } 
        
    playerStatus.str = playerStatus.basestr + newItem.strModifier;
    playerStatus.def = playerStatus.basedef + newItem.defModifier;
    playerStatus.mgc = playerStatus.basemgc + newItem.mgcModifier;
    playerStatus.mgcdef = playerStatus.basemgcdef + newItem.mgcdefModifier;

}

Setting the equipment system in Unity

Now that we have all the essential components of our equipment system, let’s put it all together inside Unity.

  • Right-click inside your Assets/ folder and create a bunch of items including the equipment. To keep things clean, let’s create a folder called Items and put them in there. Below are two examples of items: one of the StatsItem type and one of Equipment type.
Equipment system stats item example
Equipment system equipment item example
  • Create a global GameObject called something like GameManager and add Inventory, EquipmentManager and StatusManager scripts to it. You can add some of the items you’ve just created to you inventory. Don’t worry about the size of your list inside your EquipmentManager as it will be automatically initialized when you start the game.
Inventory and EquipmentManager as part of Equipment System
  • Drag and drop the CharacterStatus (and instance of ScriptableObject) to its field in SystemManager.

Now all that’s left is to create some sort of interactive menu panel that will enable the player equipping the gear in appropriate slots. Let’s get down to it

4. The equipment system main menu screen

It is usually the case in many RPG games to have a panel where the player can preview not only his equipable gear but also how it will affect the stats. As for the aesthetic it is entirely up to the designer how to show individual elements on the screen. However, in most cases it is advisable not to over complicate things and to display things as clearly as possible. I’ll go with two panels that will be shown to the player when he enters the “equip menu”. One panel will list all the current statistics and update their value when different pieces of gear are equipped. The other panel will display all possible slots and their occupancy status. For this part of the tutorial I highly recommend reading my previous article on scrollable menus as I will reuse the concepts described there.

What I’m going to implement is a dynamic architecture of collaborating scripts that automatically send notifications in order to update the state of the inventory on different levels of abstraction. Some of the updates are going to be made via delegate callbacks. The following sequence diagram shows how individual functions are going to be called when the player confirms his selection of a gear he want to equip.

Click on the diagram to enlarge it.

Equipment System sequence diagram

The static item prefab

The entire concept is based on the lists so I will start by defining a position item. This essentially going to be a prefab that I’m going to place in the status panel. Each position on that list will represent individual stats taken from CharacterStatus. There is nothing fancy going on here, but the most important thing to remember is to make the element’s height to 64 pixels. That’s because I want a single list element to be exactly of that size.

To make things more crisp, I’m going to use TextMeshPro but you are free to use standard Text UI component. I will have a label and a number on opposite sides of the panel.

Equipment system static item

The Status Panel

We will now create the Stats Panel where the player will be able to see his character’s stats.

  • Create a new Canvas and new Panel under it. If you are planning on adding some fancy fade-in / fade-out animations upon activation / deactivation later on then add Canvas Group component to it. The Canvas Group allows to control the opacity and some other aspects of the UI panel and it’s children simultaneously.
  • Add the image to the panel that will be used as background. Here you want to make sure that the sprite you’re going to use is correctly sliced in the Sprite Editor. This will ensure that the image is not stretched when we change the size of the panel.
Sprite Editor in Unity
Sprite Editor in Unity - slicing the image
  • Set the width to your desired size. For the height, set the size according to the number of your list positions while accounting the top and bottom paddings. For practical reasons, I will create another child Panel and set my paddings there. The formula is the following:

H =\sum_{p=1}^{posNo} * pH + (bP + tP)

where

 H = the height if the panel list

 posNo = the number of positions on the list

 pH = the height of individual item on the list (64 in my case)

 pB, tP = the bottom and top padding of the child panel respectively

  • As mentioned in the previous point, create another Panel and set your desired paddings there. Add Mask component and assign the built-in “Mask” texture to the Panel’s Image component.
Status Panel Wrapper
  • Create on more child panel underneath and place all your static items prefabs here. List them however you want and make sure that the labels correspond to the actual stats in CharacterStats. Your structure should now look like this:
Equipment system status panel structure

Now we have our Status Panel UI but we still need to write a little script that will update the numerical values. Add a new script to the inner-most panel and call it StaticItemsController.

The Static Items Controller

This script will be responsible for updating all the static (for now) text values of the UI we just created.

  • First, let’s add a new delegate callback function in our StatusManager and invoke it each time a new gear is equipped.
public class StatusManager : MonoBehaviour
{
    public delegate void OnStatusChangedCallback();
    public OnStatusChangedCallback onStatusChangedCallback;
    
    ...

    public void UpdateCharacterStatus(Equipment newItem, Equipment oldItem)
    {
        ...        
        onStatusChangedCallback.Invoke();
    }
}
  • After that, let’s add a new function to our StaticItemsController. It will be triggered each time the delegate function is invoked in StatusManager. Its task is to update our fields in the UI panel.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class StaticItemsController : MonoBehaviour
{
    public List<TextMeshProUGUI> fields;

    private void Awake()
    {
        StatusManager.instance.onStatusChangedCallback += UpdateFields;
    }
    
    void UpdateFields()
    {
        
    }
}
  • Before we jump into implementation of UpdateFields() function we have to set few more things. Let’s quickly rename our fields of all static items we have on the list so that they are exactly the same as the fields declared inside CharacterStatus ScriptableObject:
  • After that, add all the numerical text fields to the list of fields in your StaticItemsController.
Equipment System assigning numerical text fields

Updating UI text fields using reflection

Now we are ready to implement the UpdateFields() function. We could go and update each of our texts individually, but there is a better way of doing this. I’m going to use a C# reflection concept in order to go grab and bind all the fields in CharacterStatus by their name. This technique will require using the System namespace so let’s first add it.

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

public class StaticItemsController : MonoBehaviour
{
    ...
}

Next let’s get the values from CharacterStatus that correspond to the renamed numerical text fields. I will run through all the fields that have been added to the list.

void UpdateFields()
{
    Type fieldsType = typeof(CharacterStatus);

    foreach (TextMeshProUGUI field in fields)
    {
        string value = fieldsType.GetField(field.name).GetValue(StatusManager.instance.playerStatus).ToString();
        field.text = value;       
    }
}

The advantage of updating the fields this way is that we can add more statistics in the future without worrying about hard-coding their references in the code. The only thing we have to remember is to name all text fields in our UI so that they read the same as the names of our attributes.

Equipment System renaming text fields to attributes names in CharacterStatus

The Equipment Panel

Now that we have finished the “Status Panel” we still need to write another panel for displaying all our currently equipped gear. Both will form our equipment system. Once again I’m going to use the scrollable list concepts and constructs here so I advise you to read about them first. I’ll start by recreating the same Panel structure as in case of the Status Panel. However, this time I’ll add MenuButtonController script to it instead of StaticItemsController. After that I’ll make a list of slots to which I want the player to assign his equipment to. I’ll reuse the menu button prefab with some slight modification to make it more fancy.

The Equipment Panel list’s single position

Let’s open the MenuButton script attached to individual MenuItem prefab. We now have to account the fact that upon selecting an item from this list we want to open a new panel listing our equipment pieces. It doesn’t make sense to display all of the equipment pieces from our inventory though. What we want is to display all equipment pieces of a specific type that we have in our possession. For instance, it doesn’t make sense to display all swords that we have when the player selects the torso slot.

  • Open the MenuButton script and add fields indicating that a specific type of equipment is assigned to it. In addition I’ll specify what kind of action I want to execute on the item with ItemAction field.
public class MenuButton : MonoBehaviour
{
    public MenuButtonController menuButtonController;
    public Animator animator;
    public int thisIndex;
    [SerializeField] GameObject menuPanelToOpen;

    [Header("Equip Assignment")]
    public bool isEquipmentAssigned = false;
    public EquipType equipType;
    public Item equipItem;
    public ItemAction action;

    void Update()
    {
        ...
    }

    ...
}
  • In the Update() loop make sure to set fields corresponding to the type of equipment we want to see in the next panel. Don’t worry we will adjust the MenuButtonItemMenuController (link) in the next step so that it contains the isEquipmentType and equipSort fields.
void Update()
{
    if(menuButtonController.index == thisIndex)
    {
        animator.SetBool("selected", true);
        if(menuButtonController.isPressConfirm)
        {
            animator.SetBool("pressed", true);
            if(menuPanelToOpen != null)
            {
                if (isEquipmentAssigned)
                { 

menuPanelToOpen.gameObject.GetComponent<MenuButtonItemMenuController>().isEquipmentType = true;
menuPanelToOpen.gameObject.GetComponent<MenuButtonItemMenuController>().equipSort = equipType;
                }

  menuButtonController.gameObject.SetActive(false);
                 menuPanelToOpen.SetActive(true);
            }
        } 
        else if(animator.GetBool("pressed"))
        {
            animator.SetBool("pressed", false);
        }
    } else {
        animator.SetBool("selected", false);
    }
}
  • Let’s now implement the OnEnable() and OnDisable() methods. In here I want to implement the logic for updating the button text with a piece of equipment we just equipped. To do so, I’ll register a new callback method in the EquipmentManager onEquipmentChangedCallback delegate. To safe memory, I’ll also deregister the callback when button is deactivated.
public void OnEnable()
{
    if (isEquipmentAssigned) {
        EquipmentManager.instance.onEquipmentChangedCallback += UpdateItemName;
        UpdateItemName();
    }
}

public void OnDisable()
{
    if (isEquipmentAssigned) {
   EquipmentManager.instance.onEquipmentChangedCallback -= UpdateItemName;
    }
}
  • All that’s left to do here is to add the UpdateItemName() that will update the button text.
void UpdateItemName()
{
    if (isEquipmentAssigned)
    {
        if (EquipmentManager.instance.currentEquipment[(int)equipType] != null)
        {
            string equippedName = EquipmentManager.instance.currentEquipment[(int)equipType].name;
            transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = equippedName;
        }
        else
        {
            transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "Empty";
        }
    }
}

Take note of line 5 and 7. I’m getting a reference to the correct gear from the currentEquipment array by casting the equipType to int.

Creating a dynamic list of filtered equipment items

  • Create one more Panel and add MenuButtonItemMenuController to it. Make sure that is properly wrapped as in case of EquipPanel from earlier.
  • Add the missing fields: isEquipmentType and equipmentSort.
4-5public class MenuButtonItemMenuController : MenuButtonController
{
   [SerializeField] GameObject menuItemPrefab;
    public bool isEquipmentType = false;
    public EquipType equipSort;

    public override void OnEnable()
    {
        ....
    }
}
  • Let’s now modify the OnEnable() function. Firstly, I’ll make sure to filter out all items I currently have in the inventory by type. This is dictated by equipSort and isEquipmentType that were set when we opened the new panel (see 2 bullet points earlier).
public override void OnEnable()
{
    Dictionary<Item, int> inventory = new Dictionary<Item, int>();
    if (isEquipmentType)
    {
        foreach (Item item in Inventory.instance.items)
        {
            if (item.type == ItemType.equipment)
            {
                Equipment equipment = (Equipment) item;

                if (equipment.equipType == equipSort)
                {
                    if (inventory.ContainsKey(item))
                    {
                        inventory[item] += 1;
                    }
                    else
                    {
                        inventory.Add(item, 1);
                    }
                }
            }
        }
    } 
    else 
    {
        foreach (Item item in Inventory.instance.items)
        {
            if (inventory.ContainsKey(item))
            {
                inventory[item] += 1;
            }
            else
            {
                inventory.Add(item, 1);
            }
        }
    }

    ...
}
  • After we have filtered our inventory we need to instantiate the positions from MenuButton prefab for each item. Take note of line 54 and 55 in the next listing. This is where I reference the item inside MenuButton so that we can use use it and specify the action I want to perfom.
public override void OnEnable()
{
    Dictionary<Item, int> inventory = new Dictionary<Item, int>();
    if (isEquipmentType)
    {
        foreach (Item item in Inventory.instance.items)
        {
            if (item.type == ItemType.equipment)
            {
                Equipment equipment = (Equipment) item;

                if (equipment.equipType == equipSort)
                {
                    if (inventory.ContainsKey(item))
                    {
                        inventory[item] += 1;
                    }
                    else
                    {
                        inventory.Add(item, 1);
                    }
                }
            }
        }
    } 
    else 
    {
        foreach (Item item in Inventory.instance.items)
        {
            if (inventory.ContainsKey(item))
            {
                inventory[item] += 1;
            }
            else
            {
                inventory.Add(item, 1);
            }
        }
    }

    maxIndex = inventory.Count - 1;
 
    int index = 0;
    foreach (KeyValuePair<Item, int> entry in inventory)
    {
        if(menuItemPrefab != null)
        {
            GameObject gameObject = Instantiate(menuItemPrefab, Vector3.zero, Quaternion.identity, this.transform) as GameObject;
            gameObject.GetComponent<MenuButton>().menuButtonController = this;
            gameObject.GetComponent<MenuButton>().thisIndex = index;
            gameObject.GetComponent<MenuButton>().animator = gameObject.GetComponent<Animator>();
gameObject.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = entry.Key.name;
gameObject.transform.GetChild(3).GetComponent<TextMeshProUGUI>().text = entry.Value.ToString();
 gameObject.GetComponent<MenuButton>().equipItem = entry.Key;
 gameObject.GetComponent<MenuButton>().action = ItemAction.EQUIP;
            index++;
        }
    }
}
  • Let’s finish the equipment system by modifying the code in the Update() loop of MenuButton. We are going to make it so that if the equipItem is not null, the action indicates EQUIP and the player confirms the selection, the character will equip it. All we have to do here is to execute the Use() member function of the Item.
void Update()
{
    if(menuButtonController.index == thisIndex)
    {
        animator.SetBool("selected", true);
        if(menuButtonController.isPressConfirm)
        {
            animator.SetBool("pressed", true);
            if(menuPanelToOpen != null)
            {
                if (isEquipmentAssigned)
                { 
                    if(equipItem != null && action == IteamAction.EQUIP)
                    { 
                        equipItem.Use();
                    } 
                    else 
                    {
menuPanelToOpen.gameObject.GetComponent<MenuButtonItemMenuController>().isEquipmentType = true;
menuPanelToOpen.gameObject.GetComponent<MenuButtonItemMenuController>().equipSort = equipType;
                    }
                }

  menuButtonController.gameObject.SetActive(false);
                 menuPanelToOpen.SetActive(true);
            }
        } 
        else if(animator.GetBool("pressed"))
        {
            animator.SetBool("pressed", false);
        }
    } else {
        animator.SetBool("selected", false);
    }
}

If you managed to reach this point then congratulations! You have now finished the foundation of your equipment system! At this stage you may wish to refactor some of the code and extend the functionality. You may want to add fancy opening / closing animations, previewing the stats change upon selecting an equipment piece from the list or even add some click sounds.

With a little bit of work you may come up with something like this:

Conclusion

In this tutorial we have looked at creating a basic equipment system. I’ve provided an example of implementation of the dynamic architecture that can be used as a foundation of a more sophisticated equipment system. Firstly, we looked at the overall philosophy behind the system. Secondly, we have progressively implemented the subsequent elements forming its different layers. We have the single list position represented as a button or dynamic text. There are also different scriptable objects representing game items of given types. The status, inventory and equipment managers taking care of keeping track of the current state of inventory. Lastly, there is a script responsible for updating the list so that it displays updated statistics of our character to the player.

References

EQUIPMENT – Making an RPG in UNITY (E07) by Brackeys