0

I've been trying to develop an UI item dragging and dropping system for the past few days and make it as flexible as possible (it's being developed using Unity 3D although that's not exactly relevant to my problem). Here's the code before I get to explaining exactly what the issue is:

public class ItemHandler<ItemType> : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IHoverHandler where ItemType : Item {

     public ItemType item { get; protected set; }

     ...
}

public abstract class SlotHandler<ItemType> : MonoBehaviour, IDropHandler where ItemType : Item {

    ...

    public ItemHandler<ItemType> itemHandler { get; protected set; }

    ...

    public void OnDrop(PointerEventData eventData) {

        // Retrieve the slots
        SlotHandler<Item> originalSlotHandler = ItemManager.draggedItemParent.GetComponent<SlotHandler<Item>>();
        SlotHandler<ItemType> destinationSlotHandler = this;

        // Retrieve the handlers from their items
        ItemHandler<Item> originalItemHandler = originalSlotHandler.itemObject != null ? originalSlotHandler.itemObject.GetComponent<ItemHandler<Item>>() : null;
        ItemHandler<ItemType> destinationItemHandler = destinationSlotHandler.itemObject != null ? destinationSlotHandler.itemObject.GetComponent<ItemHandler<ItemType>>() : null;

        // Add the original item to the destination slot
        bool canAddToDestination = originalSlotHandler is SlotHandler<ItemType> || originalSlotHandler is SlotHandler<Item>;
        bool canRemoveFromOrigin = originalSlotHandler is SlotHandler<ItemType> || destinationSlotHandler is SlotHandler<Item>;

        // Swap the items
        if(canAddToDestination == true && canRemoveFromOrigin == true) {

            // Remove the old items
            originalSlotHandler.RemoveItem();
            destinationSlotHandler.RemoveItem();

            // Add the new items
            originalSlotHandler.AddItem(destinationItemHandler);
            destinationSlotHandler.AddItem(originalItemHandler);

            ...
        }

        ...
    }

    public virtual void AddItem(ItemHandler<ItemType> itemHandler) {

        this.itemHandler = itemHandler;
    }
}

The idea behind this is to have ItemHandler of a given type (e.g. ItemHandler< Gem >, ItemHandler< Weapon > etc.) and then only allow them to be dropped into SlotHandlers of the same type OR of the same base type.

  • ItemHandler< Gem > goes into SlotHandler< Gem > and SlotHandler< Item >.
  • ItemHandler< Weapon > goes into SlotHandler< Weapon > and SlotHandler< Item >.

I tried doing things this way because I want to limit in which slots I'm able to drop specific items.

The problem comes from the fact that I have to take into account a specific type of slot, SlotHandler< Item > (the Item class you see in the code snippet which is the base class for all Items). The inventory slots should be able to hold every type of item.

Now, since ItemHandler< Gem > isn't a subclass of ItemHandler< Item > (or any other derived type of Item) this won't work. Now you might say why not just store Item instead of storing a Generic ItemType? I would but the idea here is to derive SlotHandler and have specific behaviour in each sub-class (dropping items into different types of slots should do different things).

I think I understand why this won't work the way I'm trying to do it but I can't find a solution I feel comfortable with so I thought I'd try and ask for some help. Sorry If I'm not being clear enough with the problem, please let me know if I should clear something up.

Thanks in advance for any help trying to solve my problem!

  • are you doing this in the UI system (Canvas), or in the main game scene? – Fattie May 20 '16 at 17:33
  • Unity is an ECS system. It is not, in any way even vaguely, an OO system. (The language Unity happens to use to write components at the moment, c#, is indeed an OO language. But that's - nothing, irrelevant. They could change to some other language for the components any time.) I probably wouldn't do it anything like you're doing. What I'd do is write a component "Draggable" which you can attach (to anything of course - it's an ECS system) which makes that thing draggable. And that's the end of it. End of story. Regarding the "slots", if you want them to behave.... – Fattie May 20 '16 at 17:36
  • ...behave in some way - then write a behavior for that. ie, they may reject some items dragged to them, they may play fireworks in some cases .. whatever. That "slot behavior", again of course (it's Unity) you should be able to drop on to anything at all. Here's a recent essay on a related matter http://stackoverflow.com/a/37243035/294884 – Fattie May 20 '16 at 17:37
  • In short, you might be confusing ***the thing*** with "components you happen to be writing which you can drop on objects in Unity". Notice for example you used the grammar tense `SlotHandler`. What is a SlotHandler? No such thing exists in Unity, nor can it ever exist in Unity. All it is is a GameObject. If you want to make a *behavior*, perhaps `AcceptDrops`, do that. Like any script you write in Unity (aka, any component, any behavior), it's just a behavior you can drop on any GameObject. – Fattie May 20 '16 at 17:41
  • @JoeBlow: YOur whole explanation would make a lot more sense with a link to "*ECS System*". – Pieter Geerkens May 22 '16 at 03:43
  • Hi @PieterGeerkens - just wikipedia really. ECS is something of a silly "made-up" term basically to describe game engines - things with instantiated game objects ("everything is a singleton") that you stick behaviors on – Fattie May 22 '16 at 13:02
  • I am writing behaviours, ItemHandler and SlotHandler are both behaviours. SlotHandler is a behaviour attached to an object that allows that object to have other objects dropped into it. ItemHandler is basically a component I look for whenever something is dropped into my SlotHandler object. It's perfectly fine to use an OO approach in Unity and my problem doesn't have to do with the OO approach in itself, since it could still be presented in a non-unity context and still make sense. – Tiago Reis May 23 '16 at 14:20
  • The real issue here is that C# does not support variance for generic types and what I'm trying to do is getting around that issue. Either way thanks for your input! – Tiago Reis May 23 '16 at 14:23

1 Answers1

0

To me it seems that although you need to keep track of the types, all you really need is a record of the parents' type (and their parents) for each item. Rather than using the type itself in a programming sense why not just use a string label and create a list in the base class for the hierarchy of valid types for the item. That way you can have complex connections and organization and only need to compare a single string to a list to see if it's valid.

using UnityEngine;
using System.Collections.Generic;

public class Item {

  public List<string> validInvTypes = new List<string>();

  //...
}

public class Gem : Item {

  //...
}

public class Ruby : Gem {

  public Ruby()
  {
    validInvTypes.Add("Ruby");
    validInvTypes.Add("Gem");
    validInvTypes.Add("Item");
  }

  //...
}

public abstract class SlotHandler<ItemType> : MonoBehaviour, IDropHandler where ItemType : Item {

  //...

  public void OnDrop(PointerEventData eventData)
  {
    //...

    //string currentInvSlot
    //Item droppedItem

    if (droppedItem.validInvTypes.Contains(currentInvSlot))
      // Swap the items...
  }
}
cjmarsh
  • 292
  • 2
  • 12
  • That would work and is probably an easier / simpler solution, the way I'm trying to do things right now is by assuming that the items are initially in the correct slot and i'm only validating item drops according to the initial slot type and the final slot type (basically disregarding the actual items type). If I do run into any issues with this approach I'll give yours a try. Thank you for your input! – Tiago Reis May 23 '16 at 14:17