- Home /
Grid Inventory : Move Item & Slot Highlighting
Hi !
I'm working on an inventory system with large items (Diablo/Men of War/Commandos/"insertnameofagamehere"-style) for an RPG, but I can't find a proper way to move an item from one group of slot to another.
At first I was seeking for a way to get the UI elements that were under another UI element (dragging the item, get the hovered slots, check if they are occupied, red highlight them if they are, green highlight if they aren't, etc), but I didn't find a way to do that.
Of course, I tried to use the Event Handlers interfaces (and using the PointerEventData too). Sadly it doesn't behave exactly like I wanted because, for instance, PointerEventData.hovered returns all the UI elements under the Pointer, not the ones under the InventoryItem that I am dragging.
Raycasts could do it but I think it is a bit "overkill" to use raycasts between UI elements just for that, I'm sure there is a more performance-friendly way to do it.
I also tried colliders but the OnCollider/TriggerEnter/Exit don't seem to work on UI, or maybe I got it wrong.
I roamed Unity Answers and Youtube to find a way to do it but ain't find what I was looking for. If you got an idea or a tutorial that I missed that could do, please share it with me. I hope the post is clear enough so any can understand the problem here. Thank you by advance.
Here are the concerned scripts at the moment so everyone can understand where I am standing at the moment :
InventoryItem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class InventoryItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
[SerializeField] Image image;
[SerializeField] Animator animator;
Item item;
List<InventorySlot> containingSlots = new List<InventorySlot>();
List<InventorySlot> hoveredSlots = new List<InventorySlot>();
Inventory containingInventory;
public void Initialize(Item newItem, List<InventorySlot> newContainingSlots, Inventory newContainingInventory)
{
item = newItem;
containingSlots = newContainingSlots;
containingInventory = newContainingInventory;
name = item.name;
GetComponent<RectTransform>().sizeDelta = new Vector2(50 * item.width, 50 * item.height);
GetComponent<RectTransform>().anchoredPosition = containingSlots[0].GetComponent<RectTransform>().anchoredPosition;
image.sprite = item.icon;
}
void IPointerEnterHandler.OnPointerEnter (PointerEventData eventData)
{
animator.SetBool("Highlighted", true);
}
public void OnPointerExit (PointerEventData eventData)
{
animator.SetBool("Highlighted", false);
}
public void OnBeginDrag(PointerEventData eventData)
{
// This allows to set the contaning slots to be set to innocuppied when starting to drag the item
containingInventory.RemoveItemAtSlot(containingSlots[0].slot);
}
public void OnDrag(PointerEventData eventdata)
{
// Some elegant logic to make the InventoryItem follow the pointer smoothly
transform.position = new Vector2(transform.position.x + eventdata.delta.x, transform.position.y + eventdata.delta.y);
// Some logic to get the slots that are hovered by the InventoryItem, which is our issue here that would look like this
/*
hoveredSlots.Clear();
for(int i = 0; i < SomeEventData.hovered.Count; i++)
{
if(SomeEventData.hovered[i].GetComponent<InventorySlot>() != null)
hoveredSlots.Add(SomeEventData.hovered[i].GetComponent<InventorySlot>();
}
*/
}
public void OnEndDrag(PointerEventData eventData)
{
CheckForRoom();
containingInventory.AddItemAtSlot(item, containingSlots[0]);
}
void CheckForRoom()
{
if(hoveredSlots[0].slot.containedItem == null)
{
for(int i = 0; i < hoveredSlots.Count; i++)
{
if(hoveredSlots[0].slot.occupied)
return;
}
}
containingSlots = hoveredSlots;
}
}
interrogating the UI for that kind of information is problematic at best. you need to build some more heavy duty things. View $$anonymous$$odel Controller is a good pattern for populating UIs with useful data and manipulating that data - which is exactly what you're trying to do.
Use the $$anonymous$$VC pattern for this (at the vest least) that means the View, the grid layout, you have a script for the controller, and you have a List of objects for the model.
you populate the grid layout based on what the data structure stores, and you use the controller to do that.
so the data structure would be a List
and the customItem would have a bunch of properties like
class customItem()
{
int id;
int cellIndex;
string name;
bool isFilled;
}
The controller is used to populate the grid, so it would need a populate function, which goes through the datastructure and takes the name and cellIndex and finds a prefab image or gameObject to stick into the button.
so, useful functions become the things you asked for
You want to test if an item can go into a slot on the grid, now you have the controller look at the data structure via button index, to see if the slot is available
You can highlight a button with a red colour by asking the data structure if 'isFilled' is true, when trying to place another item into that slot
etc etc.
this is not trivial, its the basis of a lot of application program$$anonymous$$g, so good luck
I'm sorry @dvandamme but I think you misunderstood the issue here. Actually all that pretty stuff has already been written (an Inventory class containing a List of Inventory.Slot, which can contain an Item or simply be set occupied).
I got $$anonymous$$onos that are populated with some of theses classes instances to make the Inventory Panel work properly, and it is working as expected, except for the Item drag for the moment. But thank you for taking of your time to interest yourself to the problem =)
thats cool. then you've already got the heavy lifting sorted... i use a different framework for 2D (non-game) stuff, and inventory management is similar to that, so off loading these kinds of process in unity in the same way is normal for me.
Answer by McGregor777 · Apr 14, 2017 at 10:35 AM
In the end I preferred this way :
On dragging, the InventoryItem that is dragged is snapped on the pointer. On drop, the slot that is under the pointer is checked to see if it isn't containing an item, and check the x slots on the right and the y slots under to make sure they are innocupied (x is the width of the item, y is its height). If it is the case, the InventoryItem takes place in the slot that is under the pointer and occupies the others. If it isn't, the InventoryItem naturally retakes place on the original slot and reoccupies the slots that are required.
Answer by rob5300 · Mar 28, 2017 at 10:00 AM
This system you want is achievable with the UI events. These can be accessed by implementing the correct interface for the event or by also using the EventTrigger component (you can see all the event interfaces on this page as well) and joining a method to the event just like you buttons. I believe extending this class gives access to all events at once.
But instead of trying to explain an example on how to use them you should look at the examples in this UI example pack by Unity. One of the examples is a drag and drop system working. I would re purpose this to work for your inventory (which i have done in the past).
Hope this helps, typed this on my phone soo apologies for any mistakes!
The issue with using the EventTriggers (or implementing the EventHandler interfaces in my $$anonymous$$onoBehavior classes which is the same thing) is that they are only Pointer Events, Basically, what it means is that when I click, drag and drop a UI element with my InventoryItem script on it on a slot, it only returns one slot : the one which was under the pointer. And I want not one but all the slots under the InventoryItem.
You could have the InventoryItems be children of the slot or the InventoryItems have a reference to the slot they current sit in, meaning that you can then get the slot it lives in and then swap them or what you wish to do.
How i did it when i did something similar was: I just had inventory slots that had the info of the item that lived in that slot in the inventory, showing in the slot. $$anonymous$$eaning i always was draging slots arround and dropping them above other slots.
What I said above should not be hard to do. Either way its doable.
Edit: Also what do you mean when you say "all slots under the inventoryitem"?
The items take several slots depending on their width and height (and item that has 3 width and 2 height would take the slots [0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]. This is why the PointerEventData doesn't fit the role as it is.