Thursday, November 30, 2017

Inventory




Here is a inventory system i've made. It is inspired by systems like the one in resident evil 4
and focuses on items taking up physical space and shape, and requiring the player to manage their inventory space. For example, high value items could take up larger space, or be a strange shape, thus making it more difficult to carry. Note that objects can be rotated.




I tried to write my code in a very modular fashion, allowing for it to be built upon, so if someone wanted to add stacking items or more attributes they easily could. More importantly, it is very easy to add more containers, i only created two separate containers for the sake of demonstration, but its possible to make as many containers as you want.

Here are more configurations i've made:


Now I'll quickly go over some of my code:

Firstly, the Items class. This is an array that describes how an item should function. If a developer wanted to add a new item to the game, they could do so through unity's ui, rather than editing the code.

At the moment, i only have two elements, being Name and Parts. Parts is an array of each part of an inventory object, its variables contain the sprites and offsets in order to construct a complete item.
Any more attributes of an item, such as world mesh, action, etc. can be added into this class.

 
    [System.Serializable]
    public class Item
    {
        public string ItemName;
        public ComplexItemPart[] Parts;
    }
    [System.Serializable]
    public class ComplexItemPart{
        public Vector2 ItemPosition;
        public Sprite ComplexPartIcon;
    }


The Container Architecture is a bit verbose for what i have here, but like I've said i really wanted to keep this code easily expandable. This makes it easy to have separate containers, as you can see above in the demonstration I've created two, but its easy to make more. This way, containers can be held on separate objects, making things like chests, or trading between players online, very easy.

 
   [System.Serializable]
    public class Container
    {
        public InventoryItems[,] ContainingItems;
        public int columns = 5;
        public int row = 5;
        public Image[,] SlotImgs;
    }
    public class InventoryItems
    {
        public int ItemID;
        public Vector2 ParentSlot= new Vector2(-1,-1);
        public bool SlotRot;
    }   

    public Container PlayerInventory;
    public Container OtherContainer;    
Each container is generated based on the specifications written here, So the public variables PlayerInvetory and OtherContainer are used to "setup" those containers.

The cursor is fairly self explanatory:
    void UpdateCursor(){ //create a new cursor
        foreach (Image cursorsimgs in CursorParts){
            Destroy(cursorsimgs.gameObject);
        }
        if (Holding.InHandID!=0){
            CursorParts = new Image[ItemList[Holding.InHandID - 1].Parts.Length];
            for (int i = 0; i < ItemList[Holding.InHandID - 1].Parts.Length; i++) {
                GameObject CreatedCursorElement = Instantiate(CursorObj, Cursor.position, Quaternion.identity);
                CursorParts[i] = CreatedCursorElement.GetComponent();
                CursorParts[i].transform.parent = Cursor;                
                CursorParts[i].sprite = ItemList[Holding.InHandID - 1].Parts[i].ComplexPartIcon;
            }
            RotateCursor();
        }
        else{
            CursorParts = new Image[0];
        }
    }
The cursor is build and destroyed in sections for each item, i could have made items one large image, but i wanted to be able to mix and match parts (if you're familiar with tilesheets this makes more sense). And as an added bonus, i can fit all my items onto a single sprite sheet.


I'll skip over slot generation for the sake of brevity.


Finally, the most complex segment of the code, slotting items in and out of slots.
Note that there is no reference to the players inventory, as this function works with whatever container you throw at it. I think i could have done a better job by defining some temporary variables just for the sake of readability, as i do repeat long lines a few times.
 
     public void ClickSlot(Vector2 slotclicked, Container C) { // the player has clicked on an item slot
        Vector2 Parent = C.ContainingItems[(int)slotclicked.x, (int)slotclicked.y].ParentSlot;
        ///////////////////Pick Up///////////////////////////
        if (Parent!= new Vector2(-1,-1) && Holding.InHandID == C.ContainingItems[(int)Parent.x, (int)Parent.y].ItemID|| Holding.InHandID ==0) { // hands are empty or the  player has the same item in their hand, so you should pick it up        
            if (C.ContainingItems[(int)slotclicked.x, (int)slotclicked.y].ItemID != 0){ //slot is not empty
                Holding.InHandID = C.ContainingItems[(int)Parent.x, (int)Parent.y].ItemID;
                C.ContainingItems[(int)Parent.x, (int)Parent.y].ParentSlot = new Vector2(-1,-1);
                foreach (ComplexItemPart parts in ItemList[Holding.InHandID - 1].Parts){    //Remove all complex parts of the item
                    Vector2 parttoSlot;
                    if(C.ContainingItems[(int)Parent.x, (int)Parent.y].SlotRot) parttoSlot= Parent + parts.ItemPosition;
                    else parttoSlot = Parent + new Vector2(-parts.ItemPosition.y, parts.ItemPosition.x);
                    C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].ItemID = 0;
                    C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].ParentSlot = new Vector2(-1, -1);
                    C.SlotImgs[(int)parttoSlot.x, (int)parttoSlot.y].sprite= EmptySlot;
                }
                UpdateCursor(); //clear the cursor
                return;
            }
        }
        ///////////////////Put Down///////////////////////////
        else if (Holding.InHandID != 0){  // the player has an item in their hand
            bool canfit = true;
            Vector2 parttoSlot;
            foreach (ComplexItemPart parts in ItemList[Holding.InHandID-1].Parts) {    //can this part fit into the players inventory?             
                if (rot) parttoSlot = slotclicked + parts.ItemPosition;
                else parttoSlot = slotclicked + new Vector2(-parts.ItemPosition.y, parts.ItemPosition.x);
                if (parttoSlot.x<=-1||parttoSlot.x >= C.columns ||parttoSlot.y <= -1 || parttoSlot.y >= C.row){ //overflows
                    canfit = false;
                    break;
                }
                if (C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].ItemID!=0){ //collides with other item
                    canfit = false;
                    break;
                }
            }
            if (canfit){ //item can fit, slot it in
                foreach (ComplexItemPart parts in ItemList[Holding.InHandID - 1].Parts){    //put all complex parts into correct slots
                    if (rot){ //check for rotation
                        parttoSlot = slotclicked + parts.ItemPosition;
                        C.SlotImgs[(int)parttoSlot.x, (int)parttoSlot.y].transform.eulerAngles = new Vector3(0, 0, 0);
                    }
                    else{
                        parttoSlot = slotclicked + new Vector2(-parts.ItemPosition.y, parts.ItemPosition.x);
                        C.SlotImgs[(int)parttoSlot.x, (int)parttoSlot.y].transform.eulerAngles = new Vector3(0, 0, -90);
                    }
                    C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].ItemID = Holding.InHandID;
                    C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].ParentSlot = slotclicked;
                    C.ContainingItems[(int)parttoSlot.x, (int)parttoSlot.y].SlotRot = rot;
                    C.SlotImgs[(int)parttoSlot.x, (int)parttoSlot.y].sprite = parts.ComplexPartIcon;
                }
                Holding.InHandID = 0;
                UpdateCursor();//generate a new cursor
            }
            return;
        }
    }



No comments:

Post a Comment