Sunday 8 February 2015

A Inventory System for Unity

So, ages a go, well before Xmas I think (2014) I spotted a fellow indie developer, and now MS Evangelist Dave Voyles post about an Inventory system he had written for a SMUP he was working on, and thought I would have a go at creating an Inventory  system in Unity of my own. By the time I got around to doing this Unity 4.6 was released, so all the code snippets are from that version of Unity, so it’s using the new Unity GUI, which I have to say is a huge improvement.

So, the first thing I did was write a simple shooter, so, you have a ship and some asteroids, so we can get damaged by hitting asteroids, destroying asteroids yields gold and silver coins, both of which you can collect. So, we need to pick up coins, first aid kits, and ammo, and that’s where the inventory system comes in.

image

As you can see in the screen shot, we are storing the inventory items along the bottom of the screen. So, what do these inventory items look like in code?

First thing we need to do is set up some rules, a way to define the different types of items so we know what to do with them, we can also then bunch like types together in the system. I have done this with an Enum

InventoryItemTypesEnum

public enum InventoryItemTypesEnum : int
{
DeadSpace = 0,
Bullet,
MissileSeeker,
GoldCoins,
SilverCoins,
MediPack,
}





In this Enum we have DeadSpace, not used, but wanted to have something like this in case I need to hold blank spaces. The other types are pretty self descriptive I think.


InventoryItemScript


Using the above Enum I can now create a script that can be used to create each and every inventory item. Using this script I can then create prefabs for each of the different types and have them used in both the game and the inventory system.


using UnityEngine;
using System.Collections;

public class InventoryItemScript : MonoBehaviour
{
public string ItemName;
public int Quantity = 0;
public InventoryItemTypesEnum InventoryItemType = InventoryItemTypesEnum.DeadSpace;

public int ScoreModifier = 0;

// Use this for initialization
void Start ()
{

}

// Update is called once per frame
void Update ()
{

}

void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Player")
{
// Add it to the Inventory..
InventoryScript Inventory = collider.gameObject.GetComponent<InventoryScript>();
collider.gameObject.GetComponent<ShipScript>().Score += ScoreModifier;
Inventory.AddItemToInventory(this);

Destroy(gameObject);
}
}
}

In here we have the name of the item, a quantity for it and it’s type dictated by the Enum, set initially to DeadSpace as well as if the item has a score modifier, again you could add other properties in here that are pertinent to you game.


I am using a trigger because I don’t want the collection of the object to impact the craft, when the “Player” collides with the inventory item we get the InventoryScript, this script has the actual inventory system in it, we apply the score modifier and then add this item to the inventory system before we then destroy it.


We can now create prefabs for each of the inventory types:-


Bullet


image


MissileSeeker


image


MediPack


image


GoldCoins


image


SilverCoins


image


InventoryScript


Here is where all the magic happens :) First thing I do is set up three public GameObjects. InventoryItem is the part of the HUD that will be used to display the inventory items in, a UI.Image


image


The InventoryItemPrefab is a prefab used to instance images in the InventoryItem GamObject


image


 


We then have the Selector GameObject, again this is an object in the HUD and I use that to indicate which inventory is currently selected.


image


We then have a Dictionary of InventoryItemScript with a string key for the name of the item that is stored in this location of the inventory. There is also a list of GameObjects called ItemUI, these are used to render the representation of the inventory item in the HUD and given the relative name for the inventory item.


We then have a variable for the spacer, for padding the UI, a count for the number of items types in our system, and finally a variable to store the index of the current selected item in the system.


    public GameObject InventoryDisplay;
public GameObject InventoryItemPrefab;
public GameObject Selector;

Dictionary<string,InventoryItemScript> Items = new Dictionary<string,InventoryItemScript>();

List<GameObject> ItemUI = new List<GameObject>();

float spacer = 16;

int ItemTypesCount = 0;

int SelectedIndex = -1;










Lets start with the method that adds items to the inventory system, the same method we saw earlier in the InventoryItemScript, AddItemToInventory


public void AddItemToInventory(InventoryItemScript item)


    public void AddItemToInventory(InventoryItemScript item)
{
if (!Items.ContainsKey(item.ItemName))
{
Items.Add(item.ItemName, item);
GameObject ii = (GameObject)Instantiate(InventoryItemPrefab);

ii.name = item.ItemName;
ii.transform.parent = InventoryDisplay.transform;

SetUIItemText(item, ii);

ii.GetComponent<Image>().sprite = item.GetComponent<SpriteRenderer>().sprite;

RectTransform srt = InventoryItemPrefab.GetComponent<RectTransform>();
RectTransform trt = ii.GetComponent<RectTransform>();

int x = (int)(srt.anchoredPosition.x + ((srt.sizeDelta.x + spacer) * ItemTypesCount));
trt.anchoredPosition = new Vector2(x, srt.anchoredPosition.y);

ItemTypesCount = Items.Keys.Count;
ItemUI.Add(ii);
}
else
{
Items[item.ItemName].Quantity += item.Quantity;

GameObject ii = ItemUI.SingleOrDefault(uii => uii.name == item.ItemName);

if (ii != null)
SetUIItemText(Items[item.ItemName], ii);
}

}

The parameter for this method is the InventoryItemScript item to add to the system. First thing we do is check if we already have an item of this type, if we do, we simply find it in the system and increment it’s quantity, then update the UI text for this item. If we don’t then we add it to our Dictionary then create it’s UI counterpart to go in the list by instancing a new InventoryItemPrefab, we set it’s name, make it a child of InventoryDisplay, set it’s text value, then pull out the sprite so we can then position it in the UI correctly then add it to out ItemsUI list.


void Update()


Using the left and right arrow keys, the user can select the inventory item they want to use, and by pressing Enter, use it. This is all managed in Update, in here we also manage the SeelctedItemIndex. The Selector is rendered by getting the RectTransfer of the item we want it to appear over and setting it’s anchor position to the RectTransform anchor position. I did this with a new Vector2, but this should be a regular one for one swap.


    void Update()
{
if (ItemTypesCount == 0)
Selector.SetActive(false);
else
{
if(!Selector.activeInHierarchy)
{
SelectedIndex = 0;
Selector.SetActive(true);
}

if (Input.GetKeyDown(KeyCode.RightArrow))
{
SelectedIndex++;
if (SelectedIndex > ItemUI.Count - 1)
SelectedIndex = 0;
}

if (Input.GetKeyDown(KeyCode.LeftArrow))
{
SelectedIndex--;
if (SelectedIndex < 0)
SelectedIndex = ItemUI.Count - 1;
}

RectTransform trt = ItemUI[SelectedIndex].GetComponent<RectTransform>();
Selector.GetComponent<RectTransform>().anchoredPosition = new Vector2(trt.anchoredPosition.x, trt.anchoredPosition.y);

if (Input.GetKeyDown(KeyCode.Return))
UseItem(Items[ItemUI[SelectedIndex].name]);
}
}

public void UseItem(InventoryItemScript item)


When the player hits Enter, they then “Use” the selected item. This method is a simple switch statement, in here put what ever you want your items to to to or for the player, in this sample  I am only doing this for MediPack types, and increasing the players health. This in turn calls RemoveItemFromInventory.


    public void UseItem(InventoryItemScript item)
{
switch (item.InventoryItemType)
{
case InventoryItemTypesEnum.MediPack:
GetComponent<ShipScript>().Health += .1f;

RemoveItemFromInventory(item.ItemName, 1);
break;
default:
break;
}

if (SelectedIndex > ItemUI.Count - 1)
SelectedIndex = ItemUI.Count - 1;
}

public void RemoveItemFromInventory(string ItemName, int qty)


This will only actually remove an item from the system if it’s quantity is less than or equal to zero, otherwise it just subtracts the given quantity from the inventory items quantity and update the UI text for it. If it removed the item from the system it has to then shuffle all the other items in the UI back so there is no gap.




    public void RemoveItemFromInventory(string ItemName, int qty)
{
if (Items.ContainsKey(ItemName))
{
Items[ItemName].Quantity -= qty;

GameObject ii = ItemUI.SingleOrDefault(uii => uii.name == ItemName);

if (Items[ItemName].Quantity <= 0)
{
RectTransform srt = ii.GetComponent<RectTransform>();
// Move them all back one from this one on..
foreach (GameObject goi in ItemUI)
{
RectTransform trt = goi.GetComponent<RectTransform>();
if (trt.anchoredPosition.x > srt.anchoredPosition.x)
trt.anchoredPosition = new Vector2(trt.anchoredPosition.x - (srt.sizeDelta.x + spacer), trt.anchoredPosition.y);
}

ItemUI.Remove(ii);
Destroy(ii);
Items.Remove(ItemName);
ItemTypesCount = Items.Keys.Count;


}
else
SetUIItemText(Items[ItemName], ii);
}
}

And well, that’s about it, quite simple really, and I hope you can see how easy it is to extend :)


If you want to play with the whole project, then the lovely Dave Voyles as it on his sky drive. If however you find this link is broken, then please let me know and I can host the code from my server. Grab a copy here :)


As ever, if you have any questions or suggestions on this post, or any of my posts, please feel free to post here and let me know what you think.

No comments:

Post a Comment