Ever wanted to create a game like Harvest Moon in Unity? Check out Part 4 of our guide here, where we go through how to create an item management system. You can also find Part 3 of our guide here, where we went through how to set up farmland elements that our player character will interact with.
This is a loose transcription of our accompanying video guide on Youtube. We suggest you watch the video guide instead and use this article as a supplement to it.
When working with scripts in Unity, we usually inherit from MonoBehaviour. However, this will not be suitable for creating our items because:
- It will generate needless copies of values whenever the player gets more than one of the same item
- It is going to be difficult to scale the more items we have as it will slow down performance.
- It is dependent on being attached to a GameObject in a Scene which makes the data non-persistent in itself.
Since the data we want to record is going to be persistent and unchanging, it would be better for us to create data containers instead.
1. Using ScriptableObjects to store data of our items
This is where ScriptableObjects
come in. Unlike MonoBehaviour
classes, they allow us to save information on our items as custom Assets within our project. The information can then be read to be used at runtime.
They will be especially useful because:
- They are bound to the project, making their data persistent
- They will save us a lot of data, as there is only 1 copy for our other scripts to reference.
- It is easy to scale up and allows us to make as many of them as we want.
a. Setting up the base ScriptableObject
Create a new script and call it ItemData
. Instead of inheriting from MonoBehaviour
, make it inherit from ScriptableObject
, like this:
ItemData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Item")] public class ItemData :MonoBehaviourScriptableObject {// Start is called before the first frame update void Start() { } // Update is called once per frame public void Update() { }}
Similar to the Friends of Mineral Town (2003) entry of the Harvest Moon series, we’ll segregate the Tools (Items players use for farming) from other items. The player will see it like this:
Create a folder to store the ScriptableObjects called Data. In it, create the 2 subfolders to categorise them. The structure should look like this:
- Data
- Items
- Tools
Each Item should have the following properties:
Name | Type | Use |
description | string | Explain the use of the item in the Inventory UI |
thumbnail | Sprite | Icon to be displayed in the Inventory UI |
gameModel | GameObject | The GameObject in the scene for the player to interact with |
Add these into the script:
ItemData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Item")] public class ItemData : ScriptableObject { public string description; //Icon to be displayed in UI public Sprite thumbnail; //GameObject to be shown in the scene public GameObject gameModel; }
Article continues after the advertisement:
Now Create > Item in the Assets/Data/Items folder and name it ‘Cabbage’ to test our new item. You should see our newly-declared properties in the Inspector.
Within the Tools subcategory, we have 2 subclasses that need to store very different sets of information:
- Equipment: Various tools used to prepare the land for farming (clearing obstacles and changing the state of the Land prefab).
- Seeds: Items the player can plant to grow into a crop.
b. EquipmentData
Making it Scaleable: We could technically just use the ItemData
class for our equipment ScriptableObjects
, and code the interaction scripts to just check for the name of our items. But we decided to record each of the Equipment types with enumerations (i.e. enums) instead so it’s easier to create tool upgrades and variants should we want to do it in future.
For this part, we will set up the following tools:
- Axe: To chop down wood obstacles
- Hoe: To till the land for farming
- Pickaxe: To clear rock obstacles
- Watering Can: To water the land.
Note: In the Harvest Moon and Story of Seasons games, it’s usually a hammer that is used to clear rock obstacles. We changed it to a pickaxe because it’s easier to find sprites of it online.
Since we’re pretty much recording the same set of data from ItemData
on top of its own set of properties, we can just inherit its class from ItemData
. Create a new script called EquipmentData
with the following:
EquipmentData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName = "Items/Equipment")] public class EquipmentData :MonoBehaviourItemData { public enum ToolType { Hoe, WateringCan, Axe, Pickaxe } public ToolType toolType;// Start is called before the first frame update void Start() { } // Update is called once per frame public void Update() { }}
Notice that we set the ScriptableObject‘s menu name to be a submenu of ‘Items’. We have to make ItemData‘s asset menu creation to have a submenu as well:
ItemData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Item")] public class ItemData : ScriptableObject { public string description; //Icon to be displayed in UI public Sprite thumbnail; //GameObject to be shown in the scene public GameObject gameModel; }
Under Assets/Data/Tools, create an asset for each Equipment you have in your game:
c. Getting item sprites
We’ll need some 2D assets for the thumbnail of our items. Download the following assets:
- Farming Tool Icons by Calciumtrice, usable under Creative Commons Attribution 3.0 license.
- 2D Vegetables by ScratchIO, Public Domain (CC0 license)
Import the sprite sheets to Assets/Imported Asset/ UI. For each of the images, set:
- Texture Type to Sprite (2D and UI), and;
- Sprite Mode to Multiple,
…and click Apply.
As the images are sprite sheets, you see that all of the sprites are on one image. We need Unity to extract them so that we are able to display them individually. Click on Sprite Editor. You might get the following:
To fix this, go to Window > Package Manager and find 2D Sprite. Click Install.
After the package is installed, open up the Sprite Editor and slice up the sprites accordingly:
Article continues after the advertisement:
If done right, this is what it should look like in the editor:
Go to each of our Equipment assets, and assign an appropriate sprite to its thumbnail.
Note: The Farming Tools sprite sheet does not come with a sprite for the Watering Can, so just set its sprite to any placeholder for now.
d. SeedData
We want seeds to have the following properties:
Name | Type | Use |
daysToGrow | int | Amount of time in days before it can be harvested |
cropToYield | ItemData | The item to go into the player’s inventory once it matures and is harvested. |
To this end, create a new script called SeedData
with the following:
SeedData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Seed")] public class SeedData : ItemData { //Time it takes before the seed matures into a crop public int daysToGrow; //The crop the seed will yield public ItemData cropToYield; }
In Assets/Data/Tools, create a folder called Seeds, and Create > Items > Seed, naming it ‘Cabbage Seeds’. Fill in the fields:
2. Setting up the InventoryManager
In the Scene, create an empty GameObject and name it Manager. This will hold our Manager scripts, which will manage various aspects of our game’s state.
As only 1 instance of a Manager script is needed at any point in time, we are going to use the Singleton pattern. This restricts the class to have only 1 instance, which makes it easier to reference in our other scripts.
You can refer to this article for an explanation of Singletons.
Instead of having to use FindObjectOfType()
to find a Manager class every time we want to work with one, we simply can refer to its globally-accessible instance, stored as a static variable, like Manager.Instance
.
Create a new script called InventoryManager
:
InventoryManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InventoryManager : MonoBehaviour { public static InventoryManager Instance { get; private set; } private void Awake() { //If there is more than one instance, destroy the extra if(Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
Assign it to the Manager GameObject.
The Player’s inventory will comprise of the following:
- Tools: 8 slots
- Equipped Tool: 1 Slot
- Items: 8 Slots
- Equipped Item: 1 Slot
The InventoryManager
will keep track of this information with variables:
InventoryManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InventoryManager : MonoBehaviour { public static InventoryManager Instance { get; private set; } private void Awake() { //If there is more than one instance, destroy the extra if(Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } [Header("Tools")] //Tool Slots public ItemData[] tools = new ItemData[8]; //Tool in the player's hand public ItemData equippedTool = null; [Header("Items")] //Item Slots public ItemData[] items = new ItemData[8]; //Item in the player's hand public ItemData equippedItem = null; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
Article continues after the advertisement:
3. Setting up the user interface
Note: You do not have to follow this section to the letter. More importantly, ensure that the hierarchy of the Canvas’ contents is the same. You can refer to the video for a more in-depth demonstration.
Create a new UI canvas as a child of the Manager GameObject.
On its Canvas Scaler component, set the UI Scale Mode to Scale With Screen Size.
Set the Reference Resolution to 1600×900.
a. Setting up the Inventory Panel GameObject
Create an empty GameObject parented to the Canvas and name it ‘Inventory Panel‘. You should see a RectTransform component on it instead of the usual Transform. Stretch it to take up the entire screen.
Create a button parented to the Canvas to open this Inventory Panel ( Right-click Manager, UI > Button ). Place it at the bottom-right of the screen and change the source image to the backpack icon from our Farm Tools sprite sheet.
Article continues after the advertisement:
b. Creating the Tools Section of the Inventory Panel
Right-click Inventory Panel, UI > Image, and resize it like this:
Name this Image GameObject ‘ToolsPanel’. You may give it a colour and a Header with a text component (Right-click ToolsPanel > UI > Text).
Create the Hand slot with the Image component and set the source image to UISprite. Position it like this:
Create an empty GameObject called InventorySlots and resize it within the ToolsPanel GameObject like this:
On the GameObject, add a Grid Layout Group component and give it the following parameters:
- Cell Size: 100×100
- Spacing: 50×50
Create a new folder in Assets/Prefabs called ‘UI‘.
As a child of Inventory Slots, create a new Image GameObject, with the same colour as Hand Slot. Call this ‘Inventory Slot‘. Parented to this, create another Image GameObject called ‘Item Display‘. Make it slightly smaller than its parent:
Save the Inventory Slot GameObject as a Prefab under Assets/Prefabs/UI. Add 7 more of the same prefab to the Inventory Slots object.
c. Creating the Items Panel
- Duplicate the ToolsPanel GameObject.
- Rename it to ItemsPanel
- Change the header text to ‘Tools’
- Move the panel to the right and anchor it there.
Article continues after the advertisement:
We want the Inventory Panel to be in front of the Inventory Button. Swap their positions in the scene hierarchy:
d. Creating the Item Info box.
Add another Image GameObject of the InventoryPanel, and name it Item Info. Add a text component each for the item name and description and position it on the bottom of the panel.
Note: We created the Item Description Text GameObject by just duplicating the Item Name Text and changing up the size. However, we forgot to rename Item Name (1) to Item Description until much later in the video. Do this now so you will not be confused later on.
e. Toggling the Inventory Panel
By default, the Inventory panel should not be showing. Set the Inventory Panel GameObject to inactive. We will make it open when the player clicks the Inventory Button. The easiest way to do so is by configuring it on the Button component.
On the Button component of the Inventory Button GameObject, add a new On Click event, and drag the Inventory Panel GameObject on it.
Set it to GameObject.SetActive, and tick the boolean box.
So far, the player is able to open the Inventory Panel in-game by clicking on the Backpack. However, we have not set up a way for the player to close it. Hence, we’ll add a return button on the inventory panel.
- Create a button in the Inventory Panel and call it ‘Exit Button‘.
- Position it on the bottom right.
- Set the background colour to match the colour scheme.
- Give it the same OnClick event as the Inventory Button.
- Uncheck the boolean.
You should be able to open and close the inventory in-game now.
4. Adding functionality to the Inventory UI
As of now it only displays the basic layout of the inventory, but it does not reflect what is in our player’s inventory at all.
a. Displaying items in the inventory slots
Create a new script, UIManager using the Singleton pattern and add it to the Manager GameObject in the scene:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { }// Update is called once per frame public void Update() { }}
Article continues after the advertisement:
We need each Inventory Slot to keep track of its own contents and display the information accordingly. We’ll set up a script with a function to change its Item Display Image sprite according to the thumbnail value in the ItemData
we feed it.
Create a new script, InventorySlot
:
InventorySlot.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class InventorySlot : MonoBehaviour { ItemData itemToDisplay; public Image itemDisplayImage; public void Display(ItemData itemToDisplay) { //Check if there is an item to display if(itemToDisplay != null) { //Switch the thumbnail over itemDisplayImage.sprite = itemToDisplay.thumbnail; this.itemToDisplay = itemToDisplay; } }// Start is called before the first frame update void Start() { } // Update is called once per frame public void Update() { }}
Add the new script to the prefab GameObject.
We also need to account for how empty slots are to be displayed. Even if we were to give our Item Display Image’s sprite a null value, it will display a white box at most.
When there is no item in the slot, we want only an empty box to be shown. Hence, we will have to disable the Item Display GameObject when nothing is showing:
InventorySlot.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class InventorySlot : MonoBehaviour { ItemData itemToDisplay; public Image itemDisplayImage; public void Display(ItemData itemToDisplay) { //Check if there is an item to display if(itemToDisplay != null) { //Switch the thumbnail over itemDisplayImage.sprite = itemToDisplay.thumbnail; this.itemToDisplay = itemToDisplay; itemDisplayImage.gameObject.SetActive(true); return; } itemDisplayImage.gameObject.SetActive(false); } }
Article continues after the advertisement:
Now that we have set up the InventorySlot class, we will assign all of them to UIManager
to handle. We will need it to do the following:
- Store information of all the InventorySlots of the Tools section
- Store information of all the InventorySlots of the Items section
- Have a function that transfers the information from InventoryManager into the corresponding InventorySlots
To this end, add this to UIManager
:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("Inventory System")] //The tool slot UIs public InventorySlot[] toolSlots; //The item slot UIs public InventorySlot[] itemSlots; private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { RenderInventory(); } //Render the inventory screen to reflect the Player's Inventory. public void RenderInventory() { //Get the inventory tool slots from Inventory Manager ItemData[] inventoryToolSlots = InventoryManager.Instance.tools; //Get the inventory item slots from Inventory Manager ItemData[] inventoryItemSlots = InventoryManager.Instance.items; //Render the Tool section RenderInventoryPanel(inventoryToolSlots, toolSlots); //Render the Item section RenderInventoryPanel(inventoryItemSlots, itemSlots); } //Iterate through a slot in a section and display them in the UI void RenderInventoryPanel(ItemData[] slots, InventorySlot[] uiSlots) { for (int i = 0; i < uiSlots.Length; i++) { //Display them accordingly uiSlots[i].Display(slots[i]); } } }
Now to assign each slot for each section. Make sure you assign the slots to the correct sections!
Article continues after the advertisement:
Assign the Item Display Image to the Item Display Image field in InventorySlot.
b. Toggling the inventory UI programmatically
If everything is working properly, it should show empty Inventory slots, as our inventory is empty.
However, you were to equip or assign items to the Inventory in InventoryManager during runtime, you would not be able to see the changes reflected. This is because the function RenderInventory is only called in UIManager once in the Start
function.
We have to call this function more often. Yet, calling this on Update
will not be optimal as the player will not be checking or changing the inventory state that often. As the Inventory is more likely to be shown and updated every time the player opens or close it, it would make sense to call RenderInventory at those points.
Hence, it would make more sense to toggle the Inventory Panel programmatically rather than what we did in 3e. This would make things a lot easier in the future if we wanted the panel to be opened by keyboard hotkeys too. To this end, let’s set up the function to toggle the panel in UIManager:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("Inventory System")] //The inventory panel public GameObject inventoryPanel; //The tool slot UIs public InventorySlot[] toolSlots; //The item slot UIs public InventorySlot[] itemSlots; private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { RenderInventory(); } //Render the inventory screen to reflect the Player's Inventory. public void RenderInventory() { //Get the inventory tool slots from Inventory Manager ItemData[] inventoryToolSlots = InventoryManager.Instance.tools; //Get the inventory item slots from Inventory Manager ItemData[] inventoryItemSlots = InventoryManager.Instance.items; //Render the Tool section RenderInventoryPanel(inventoryToolSlots, toolSlots); //Render the Item section RenderInventoryPanel(inventoryItemSlots, itemSlots); } //Iterate through a slot in a section and display them in the UI void RenderInventoryPanel(ItemData[] slots, InventorySlot[] uiSlots) { for (int i = 0; i < uiSlots.Length; i++) { //Display them accordingly uiSlots[i].Display(slots[i]); } } public void ToggleInventoryPanel() { //If the panel is hidden, show it and vice versa inventoryPanel.SetActive(!inventoryPanel.activeSelf); RenderInventory(); } }
Assign the Inventory Panel GameObject to the field we declared in UIManager.
On each of the buttons, Exit Button and Inventory Button, set the On Click event to call UIManager.ToggleInventoryPanel
.
Now you can alter the player’s inventory during runtime and the inventory screen will update accordingly every time you clicked the Backpack or ‘Return’ buttons.
Note: Whatever you give the player in the InventoryManager during runtime will be rolled back the moment you stop. To give your player some starting equipment, make sure that you do it in the editor outside of the runtime environment.
Article continues after the advertisement:
b. Displaying the item name and description in the Item Info box
When the player hovers over an inventory slot, the item name and description should appear on the Item Info Box.
Let’s make a function on UIManager that takes in an ItemData parameter and processes it to show the relevant information on the UI elements:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("Inventory System")] //The inventory panel public GameObject inventoryPanel; //The tool slot UIs public InventorySlot[] toolSlots; //The item slot UIs public InventorySlot[] itemSlots; //Item info box public Text itemNameText; public Text itemDescriptionText; private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { RenderInventory(); } //Render the inventory screen to reflect the Player's Inventory. public void RenderInventory() { //Get the inventory tool slots from Inventory Manager ItemData[] inventoryToolSlots = InventoryManager.Instance.tools; //Get the inventory item slots from Inventory Manager ItemData[] inventoryItemSlots = InventoryManager.Instance.items; //Render the Tool section RenderInventoryPanel(inventoryToolSlots, toolSlots); //Render the Item section RenderInventoryPanel(inventoryItemSlots, itemSlots); } //Iterate through a slot in a section and display them in the UI void RenderInventoryPanel(ItemData[] slots, InventorySlot[] uiSlots) { for (int i = 0; i < uiSlots.Length; i++) { //Display them accordingly uiSlots[i].Display(slots[i]); } } public void ToggleInventoryPanel() { //If the panel is hidden, show it and vice versa inventoryPanel.SetActive(!inventoryPanel.activeSelf); RenderInventory(); } //Display Item info on the Item infobox public void DisplayItemInfo(ItemData data) { itemNameText.text = data.name; itemDescriptionText.text = data.description; } }
Assign the Item Info Box references we just declared in the Inspector.
We need to call this function when the player’s mouse enters or exits the Inventory Slot. We can accomplish this with Unity’s IPointerEnterHandler
and IPointerExitHandler
interfaces, which provides us with a callback function for the job.
Interfaces serve as ‘blueprints’ in which they add additional specifications to what a class must include while having the flexibility of letting the class decide how to implement it.
In the case of IPointerEnterHandler
, it requires the class to implement a function that is fired whenever the player hovers over the element. We implement that function and define for ourselves what we want it to do when it is fired.
You can read up more on Interfaces here.
We want it to do the following:
- When the player’s mouse enters the inventory slot: Display the Item name and description
- When the player’s mouse exits the inventory slot: Reset the Item Info Box to display nothing
Let’s implement this interface in InventorySlot
:
InventorySlot.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class InventorySlot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { ItemData itemToDisplay; public Image itemDisplayImage; public void Display(ItemData itemToDisplay) { //Check if there is an item to display if(itemToDisplay != null) { //Switch the thumbnail over itemDisplayImage.sprite = itemToDisplay.thumbnail; this.itemToDisplay = itemToDisplay; itemDisplayImage.gameObject.SetActive(true); return; } itemDisplayImage.gameObject.SetActive(false); } //Display the item info on the item info box when the player mouses over public void OnPointerEnter(PointerEventData eventData) { UIManager.Instance.DisplayItemInfo(itemToDisplay); } //Reset the item info box when the player leaves public void OnPointerExit(PointerEventData eventData) { UIManager.Instance.DisplayItemInfo(null); } }
If we were to test this now, we would get the following error:
This is because just passing a null value into the DisplayItemInfo
function will not just output an empty text. We have to actually account for this scenario in the code. Add the following to UIManager:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("Inventory System")] //The inventory panel public GameObject inventoryPanel; //The tool slot UIs public InventorySlot[] toolSlots; //The item slot UIs public InventorySlot[] itemSlots; //Item info box public Text itemNameText; public Text itemDescriptionText; private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { RenderInventory(); } //Render the inventory screen to reflect the Player's Inventory. public void RenderInventory() { //Get the inventory tool slots from Inventory Manager ItemData[] inventoryToolSlots = InventoryManager.Instance.tools; //Get the inventory item slots from Inventory Manager ItemData[] inventoryItemSlots = InventoryManager.Instance.items; //Render the Tool section RenderInventoryPanel(inventoryToolSlots, toolSlots); //Render the Item section RenderInventoryPanel(inventoryItemSlots, itemSlots); } //Iterate through a slot in a section and display them in the UI void RenderInventoryPanel(ItemData[] slots, InventorySlot[] uiSlots) { for (int i = 0; i < uiSlots.Length; i++) { //Display them accordingly uiSlots[i].Display(slots[i]); } } public void ToggleInventoryPanel() { //If the panel is hidden, show it and vice versa inventoryPanel.SetActive(!inventoryPanel.activeSelf); RenderInventory(); } //Display Item info on the Item infobox public void DisplayItemInfo(ItemData data) { //If data is null, reset if(data == null) { itemNameText.text = ""; itemDescriptionText.text = ""; return; } itemNameText.text = data.name; itemDescriptionText.text = data.description; } }
Conclusion
With that, we have laid the groundwork for our Item Management system. Here is the final code for all the scripts we have worked with today:
ScriptableObject scripts:
ItemData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Item")] public class ItemData : ScriptableObject { public string description; //Icon to be displayed in UI public Sprite thumbnail; //GameObject to be shown in the scene public GameObject gameModel; }
EquipmentData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName = "Items/Equipment")] public class EquipmentData : ItemData { public enum ToolType { Hoe, WateringCan, Axe, Pickaxe } public ToolType toolType; }
SeedData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName ="Items/Seed")] public class SeedData : ItemData { //Time it takes before the seed matures into a crop public int daysToGrow; //The crop the seed will yield public ItemData cropToYield; }
Inventory Management and UI scripts:
UIManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("Inventory System")] //The inventory panel public GameObject inventoryPanel; //The tool slot UIs public InventorySlot[] toolSlots; //The item slot UIs public InventorySlot[] itemSlots; //Item info box public Text itemNameText; public Text itemDescriptionText; private void Awake() { //If there is more than one instance, destroy the extra if (Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } private void Start() { RenderInventory(); } //Render the inventory screen to reflect the Player's Inventory. public void RenderInventory() { //Get the inventory tool slots from Inventory Manager ItemData[] inventoryToolSlots = InventoryManager.Instance.tools; //Get the inventory item slots from Inventory Manager ItemData[] inventoryItemSlots = InventoryManager.Instance.items; //Render the Tool section RenderInventoryPanel(inventoryToolSlots, toolSlots); //Render the Item section RenderInventoryPanel(inventoryItemSlots, itemSlots); } //Iterate through a slot in a section and display them in the UI void RenderInventoryPanel(ItemData[] slots, InventorySlot[] uiSlots) { for (int i = 0; i < uiSlots.Length; i++) { //Display them accordingly uiSlots[i].Display(slots[i]); } } public void ToggleInventoryPanel() { //If the panel is hidden, show it and vice versa inventoryPanel.SetActive(!inventoryPanel.activeSelf); RenderInventory(); } //Display Item info on the Item infobox public void DisplayItemInfo(ItemData data) { //If data is null, reset if(data == null) { itemNameText.text = ""; itemDescriptionText.text = ""; return; } itemNameText.text = data.name; itemDescriptionText.text = data.description; } }
InventorySlot.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class InventorySlot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { ItemData itemToDisplay; public Image itemDisplayImage; public void Display(ItemData itemToDisplay) { //Check if there is an item to display if(itemToDisplay != null) { //Switch the thumbnail over itemDisplayImage.sprite = itemToDisplay.thumbnail; this.itemToDisplay = itemToDisplay; itemDisplayImage.gameObject.SetActive(true); return; } itemDisplayImage.gameObject.SetActive(false); } //Display the item info on the item info box when the player mouses over public void OnPointerEnter(PointerEventData eventData) { UIManager.Instance.DisplayItemInfo(itemToDisplay); } //Reset the item info box when the player leaves public void OnPointerExit(PointerEventData eventData) { UIManager.Instance.DisplayItemInfo(null); } }
InventoryManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class InventoryManager : MonoBehaviour { public static InventoryManager Instance { get; private set; } private void Awake() { //If there is more than one instance, destroy the extra if(Instance != null && Instance != this) { Destroy(this); } else { //Set the static instance to this instance Instance = this; } } [Header("Tools")] //Tool Slots public ItemData[] tools = new ItemData[8]; //Tool in the player's hand public ItemData equippedTool = null; [Header("Items")] //Item Slots public ItemData[] items = new ItemData[8]; //Item in the player's hand public ItemData equippedItem = null; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
This has been a great tutorial so far. Everything is working nicely except the pointer handlers are not firing and I cant figure out why… I replaced onpointerenter with a debug log to see if its doing anything and it isnt… any idea why it wouldnt work? The inventoryslot script is on my itemslot which is parent to the itemdisplay. itemdisplay is set in the inventory slot script on the itemslot. Both are checked as raycast targets. I dont know what i am missing… any ideas? Im using unity 2021.3.
I just noticed my project has an intercepted event for onpointerenter… I guess something else in my project is blocking it so nevermind… I will have to figure out what is blocking it. Really loving this tutorial! Hopefully i can figure it out so i can continue.
Ok I figured it out! For anyone with the same problem, you have to make sure your canvas has the EventSystem under it. My project already had an existing canvas and the EventSystem was under that canvas. I moved the EventSystem under my inventory canvas built with this tutorial and now the OnPointer functions work perfectly.
Hi Nick, thank you for contributing your answers to the comments section. Sorry, it looks like we missed your comment! Feel free to message us on Patreon too (in future) if you need an answer quick.
hi, I can’t drag Item name game and item description on UI manager inspector item name text and item description text, no error shown on console thou
I mean section 8b didn’t work
It’s because I’m using TMP/text mesh pro instead of text legacy, now it’s working thanks!
Hi Milky, thanks for pointing this out. We will add a notice on the post to point this out, as well as link potential fixes to it.
I know this is an a year old article, but sheesh, it really helped. I’ve been pounding my head trying to get my inventory looking nice and functional. Can’t believe I haven’t ran into the IPointer interfaces smh. I’ve bookmarked this article, as it is seriously one of the better ones out there (in terms of simplicity and readability).
Thank you Ethan. Really glad this tutorial helped you.