Dienstag, 11. April 2017

UniRx - reaktive Programmierung in Unity

Reaktive Programmierung - Ein Begriff den viele Entwickler bereits einmal gehört und gleich wieder vergessen haben. Es gibt doch schon funktionale Programmierung. Warum den jetzt reaktive Programmierung? Auch wenn "Reactive Programming" auf den ersten Blick etwas esoterisch erscheinen mag, so ist es doch ein mächtiges Werkzeug und bietet viele Vorteile.

Über die GitHub Seite von UniRx (den reaktiven Erweiterungen für Unity) bin ich auf folgenden Artikel gestoßen, der eine (meiner Meinung nach) ausgezeichnete Einführung in das Thema "Reactive Programming" bietet. The introduction to Reactive Programming you've been missing

Nachfolgend habe ich versucht meine Erkenntnisse dieses Artikels kurz zusammenzufassen. Zur Info: Ich habe einiges aus diesem Artikel auch direkt übernommen. Danach will ich euch nicht länger auf die Folter spannen und wir versuchen unsere ersten Schritte in UniRx :)

Was macht reaktive Programmierung eigentlich so besonders? Wenn man "reactive" arbeitet ist zunächst einmal ALLES ein Stream! Und genau das wird unser Matra für diesen, möglicherweise auch der nächsten, Blogeinträge sein.


Nun wird es Zeit, dass wir uns auf eine Reise in die Welt der asynchronen Datenströme begeben. Asynchrone Datenströme sind nichts neues. Bussysteme oder die typischen Click-Events repräsentieren asynchrone Event-Streams, auf die man sich registrieren kann. "Reactive" basiert auf der gleichen Idee, nur ++. Das bedeutet, dass man aus allem Streams erzeugen kann, also nicht nur aus den typischen Events (Click, Hover, Focus, etc..) sonder auch aus Variablen, Eingaben, Eigenschaften, usw. Zum Beispiel könnten wir aus solchen Streams ein Hit-Counter System für ein Fighting Spiel erstellen. Zusätzlich könnten wir alle Treffer mitzählen.

Und so beginnt es...

 

Nun da wir wissen das ALLES ein Stream ist, fügen wir noch eine Prise "funktionale" Magie hinzu und schon können wir Streams beliebig kombinieren. Ein Stream kann der Input für einen anderen Stream sein. Dasselbe gilt für mehrere Streams. Streams können zusammengefügt (merge), gefiltert (filter) und auf andere Streams abgebildet (map) werden. Diese und viele weitere Operatoren von Streams können sehr gut mit Kugeln dargestellt werden. RxMarbles geben eine sehr gute Übersicht über gängige Operatoren. Für Unity wurden den Erweiterungen, Unity spezifische Operatoren hinzugefügt. Dazu aber später mehr :)

Betrachten wir zuerst Streams etwas genauer. Für eine bessere Darstellung habe ich, wie im oben verlinkten Artikel, ASCII Diagramme gewählt. Die Beispiele sind großteils einfach aus dem Artikel  The introduction to Reactive Programming you've been missing kopiert, also Kudos to André Stalz :) Wie sieht ein Stream nun aus?

--a---b-c---d---X---|->

a, b, c, d sind die anliegenden Werte
X ist ein Fehler
| ist das 'completed' Signal
---> ist die timeline, also der Verlauf des Streams

Somit zeigt sich, dass ein Stream eine Sequenz von Ereignissen ist, die nach Eintrittszeitpunkt geordnet sind. Dabei kann der Stream 3 unterschiedliche Signale senden: Einen Wert, einen Fehler oder ein "Completed" Signal. Oft werden der Fehler und der Abschluß dabei vernachlässigt und der Fokus rein auf den anliegenden Wert gelegt.

Am Besten wir betrachten das Ganze anhand eines Beispieles. Wir möchten einen Stream, welcher uns anzeigt wie oft das Click Event eines Button oder dgl. auftritt. In den üblichen reaktiven Erweiterungen hat jeder Stream viele Funktionen wie map, filter, scan usw. Wird nun eine dieser Funktionen aufgerufen, wie bei clickStream.map(f), wird ein neuer Stream zurückgegeben, welcher auf dem Click Stream basiert. Der originale Stream wid übrigens NICHT modifiziert. Der Click Stream ist immutable (unveränderlich), eine wesentliche und sehr wichtige Eigenschaft für reaktive Programmierung. Dadurch kann ein Stream von mehreren Beobachtern abonniert werden und alle greifen auf dieselben Daten zu.

Ferner können wir mehrere Funktionen verketten, wie zB.: clickStream.map(f).scan(g) wobei f für eine Funktion mit einem Parameter (auf den die Funktion angewendet wird) steht. Ein Beispiel wäre hier: x => x * 10, welche dafür sorgt, dass ein Stream zurückgegeben wird, welcher die 10fachen Werte zurückgibt. g hingegen steht für eine Funktion mit zwei Parametern, wie zB.: (x, y) => x + y Hier werden alle Werte aufsummiert, wobei x für den Wert steht, welcher am Stream anliegt und y den jeweiligen Summenwert abbildet. Das nachfolgenden Diagramm illustriert den verketteten Aufruf dieser beiden Funktionen. Der counterStream ist dabei der Stream, der zurückgegeben wird.
  clickStream: ---c----c--c----c------c-->
               vvvvv map(c becomes 1) vvvv
               ---1----1--1----1------1-->
               vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
map(f) ersetzt den Click-Wert gegen 1 und gibt einen neuen Stream zurück. Der neue Stream dient als Input für scan(g), welche die einzelnen Werte aufsummiert. Der counterStream ist der final zurückgegebene Stream, welcher die aufsummierten Werte beinhaltet UND diese zum jeweiligen Zeitpunkt eines Klicks zurückgibt.

Da aber eine Summe aller Klicks wohl eher selten verwendet wird, machen wir die Aufgabe etwas schwieriger. Wir wollen prüfen ob zwischen zwei Events ein gewisser Zeitraum liegt (in diesem Bsp. 250ms). Solange Events mit weniger als 250ms Abstand eintreten, geht der Zähler nach oben. Dafür brauchen wir abseits der bereits bekannten Funktionen, zwei weitere. buffer und throttle. Während throttle einen gewissen Zeitraum definiert, übernimmt buffer das Zusammenfassen der Ergebnisse in Listen. D.h. wird in einem Intervall von weniger als 250ms (welches von throttle zur Verfügung gestellt wird) mehrmals geklickt, gibt buffer nach diesem Intervall eine Liste mit den einzelnen Klicks zurück. Hat diese Liste nun zwei oder mehr Elemente, gibt es ein Event auf das wir reagieren können. In diesem Bsp. wird es nur eine Konsolenausgabe sein, in zukünftigen Beispielen werden wir sicher mehr damit anstellen. :) Wenn wir das Diagramm etwas genauer gestalten, können wir uns es folgendermaßen vorstellen:

clickStream: -c--c--c------c----c--------------c------>
             vvvvv Buffer(clickstream.Throttle(250)) vvvv
                          C           C
             -------------C-----------C-------------C->
                          C
             vvvvvvvvv Map(get length of lists) vvvvvvvvv
             -------------3-------------2-------------1->
             vvvvvvvvv Filter(where >= 2) vvvvvvvvvvvvvvv
             -------------3-------------2---------------> 
Soweit die Theorie. Sehen wir uns mal an, wie das Ganze in Unity aussieht. Da wir hier sehr einfach mit geschachtelten Methoden arbeiten können und uns die Implementierung Dinge wie filter mit Where abnimmt, ist der daraus entstehende Codeteil relativ simpel. Wir erstellen zuerst einen clickStream, der jedes Update überprüft, ob mit der linken Maustaste geklickt wurde. Diesen Stream prüfen wir weniger als 250ms zwischen einzelnen Klicks liegen. Ist der letzte Klick länger als 250ms her, erfolgt eine Konsolenausgabe.
using System;
using UniRx;
using UnityEngine;

public class ReactivePlayground : MonoBehaviour {
    void Start () {
        var clickStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));

        clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
            .Where(xs => xs.Count >= 2)
            .Subscribe(xs => Debug.Log("MultipleClick Detected! Count:" + xs.Count));
    }
}

Dieser Codeblock wird nun als Komponente auf ein leeres GameObject gelegt. Wird der Playmode von Unity nun gestartet, werden Klicks im Game Window gezählt.


Wow... wir haben mit nur 2 Codezeilen einen Klick-Zähler gebaut. Wenn man sich nun überlegt, welchen Aufwand man normalerweise betreiben müsste, um so eine simple Funktion zu bauen... Timing erfassen und zwischenspeichern, Klicks zählen & ausgeben... Da bevorzuge ich doch den reaktiven Weg. Auch Meister Pai Mei ist dieser Meinung und gratuliert uns zu unserer ersten Implementierung einer reaktiven Komponente. :)



Sonntag, 19. Februar 2017

Unity UI - Inventarsystem mit Drag and Drop

Unser Freund, das Inventarsystem

Jeder kennt es... Fast jedes Spiel braucht es... Das gute alte Inventarsystem. :) Dazu gehört nicht nur das Umsortieren von Items, sondern auch das Ausrüsten von gefundenen Waffen und deren korrekte Zuordnung. Zudem sollte alles via Drag and Drop veränderbar sein.

Führt man sich nun ein Inventarsystem vor Augen kann man es (wenn man es für sich selbst betrachtet) mE. auf folgende Basisfunktionen beschränken:
  • Umsortieren von Items per Drag and Drop
  • Ausrüsten von Items, anhand eines vorher definierten Modelles
  • Anzeigen einer Itembeschreibung durch Hovern
  • Dynamische Anpassung der Itemslots, bei Größenveränderung

Ein Inventarsystem mit Unity UI?

Der Unity Assetstore bietet bereits eine große Menge an Inventarsystemen, welche mit allen erdenklichen Funktionen ausgestattet sind (Crafting, mehrere Taschen, dynamische Größenanpassung, etc...). Allen voran sei hier der InventoryMaster (InventoryMaster-AssetstoreLink) erwähnt, welcher aber mit Problemen in Unity5 zu kämpfen hat. Hier sind einige Änderungen notwendig, um dieses Asset ohne Probleme einsetzen zu können.

Für alle Interessierten habe ich mal die Basics für ein solches Inventarsystem in einem Blogbeitrag zusammengefasst.

Legen wir los :)

... und erstellen ein neues Unity Projekt. Ein normales 3D Projekt ohne zusatzl. Assets genügt. Danach kümmern uns erstmal um eine gute Ordnerstruktur. Diese umfasst in diesem konkreten Fall 4 Ordner:
  • Images
  • Prefabs
  • Scenes
  • Scripts
Der nächste Schritt ist das Erstellen einer neuen Unity Szene. Ich habe die Szene DragAndDropUi.unity getauft.

In der Hierarchy erstellen wir ein neues Canvas Objekt (Rechtsklick - UI - Canvas) und nennen es MainCanvas. Unity fügt uns hier automatisch ein EventSystem Objekt ein, welches wir hier nicht weiter beachten werden.

Zuerst stellen wir uns die Frage, wie unser Inventarsystem aussehen soll. Ich habe mich für 3 Panele entschieden. Blau sind alle unsere Items, Grün ist der Ausrüstungsteil und Weiß beinhaltet die Beschreibung der Items.

Für diesen Zweck erstellen wir 3 Panels als Child Objekte des MainCanvas (Rechtsklick - UI - Panel) und nennen sie InventoryParentPanel, EquipmentParentPanel und DescriptionPanel. Durch geschickte Einstellungen von Anchor teilen wir die UI in 3 unterschiedliche Bereiche auf. Der nachfolgende Screenshot zeigt wie so etwas aussehen könnte.


InventarPanel

Im Inventarpanel werden sich später alle unsere Items befinden. Damit wir unser Inventarpanel besser einteilen können, wurde im vorherigen Schritt bereits das InventoryParentPanel erstellt. Als Child Objekte werden nun ein Text (Rechtsklick - UI - Text), welcher später als Überschrift dient und ein weiteres Panel (Rechtsklick - UI - Panel) erstellt. Die Namen dieser Objekte sind nicht so wichtig. Bei mir heißen sie InventoryPanelLabel und InventoryPanel. Die Einstellungen sind im nächsten Screenshot ersichtlich. 

Wichtig für ein einfaches Handling der Items ist, dass dem InventoryPanel die GridLayoutGroup hinzugefügt wird. Diese Komponente kümmert sich darum, dass alle direkten Kindobjekte in Form eines Grids organisiert werden. Je nach Einstellungen kann hier die Anordnung und das Aussehen des Inventares verändert werden.


Nun wird es Zeit, die ersten Items in unser Inventar aufzunehmen. Dazu fügen wir dem InventoryPanel 2 Image Child Objekte hinzu (Rechtsklick - UI - Image) und nennen sie ItemSlot und ItemSlot (1). ItemSlot erhält nun ein Image Child Objekt welches wir einfach nur Item nennen.

Um alles optisch etwas aufzuhübschen habe ich Grafik erstellt, welche den Rahmen der einzelnen Slots darstellt. Für die Items habe ich mir aus dem Unity Assetstore Inventar Icons heruntergeladen (RpgInventory-AssetstoreLink). Den ImageSlots wird nun als Source Image das Rahmen Bild eingestellt. Dem Item wird bspw. der Apfel eingestellt und eine CanvasGroup Komponente hinzugefügt. Diese wird später wichtig, um Drag&Drop korrekt umsetzen zu können. Der Screenshot zeigt, wie unsere Umsetzung bis jetzt aussieht:


Bevor wir nun direkt weitermachen, überlegen wir uns, wann ein Item in einem Slot abgelegt werden kann und wann ich erkenne, ob dieser Slot bereits belegt ist. An den beiden bereits erstellten ItemSlots der Hierarchy und der Vorschau kann man diese Bedingungen erkennen. Hat ein ItemSlot keine Child Objekte (Item), ist dieser leer. Ist ein Child Objekt (Item) vorhanden, kann hier kein Item abgelegt werden.

Nach dieser Erkenntnis ist es jetzt soweit und wir kommen an unsere erste selbst geschriebene Komponente. Man könnte Drag&Drop auch mit sog. EventTriggern im Inspector steuern, jedoch ist eine Lösung mittels C# mM. wesentlich eleganter, einfacher und verständlicher. Also legen wir im Ordner Scripts ein neues C# Skript and und nennen es DropItemHandler.

Durch einen Doppelklick öffnen wir Visual Studio (oder wahlweise MonoDevelop) und implementieren folgenden Code:
using UnityEngine;
using UnityEngine.EventSystems;

public class DropItemHandler : MonoBehaviour, IDropHandler {

    public virtual void OnDrop(PointerEventData eventData) {
        // check if there is already an item at the slot
        if (transform.childCount == 0) {
            // set new item position
            eventData.pointerDrag.transform.SetParent(this.transform);
            eventData.pointerDrag.transform.localPosition = Vector3.zero;

            Debug.Log(eventData.pointerDrag.name + " dropped at " + this.gameObject.name);
        }
    }
}
Der DropItemHandler wird jedem ItemSlot hinzugefügt. Somit kann für jeden Slot überprüft werden, ob bereits ein Item vorhanden ist oder nicht. Als nächstes kümmern wir uns um das Item, welches bewegt werden muss. Für diesen Zweck brauchen wir ein weiteres Skript, den DragItemHandler, welcher wieder im Scripts Ordner angelegt wird. 

Zusätzlich wollen wir im EquipmentPanel später auch verschiedene Ausrüstungsgegenstände unterscheiden können, weshalb wir im Scripts Ordner einen neuen Unterordner Enums anlegen, welcher ein Datenmodel (ItemCategory) beinhaltet. Ich habe mich in diesem Bsp. für 6 verschiedene Kategorien entschieden, wobei diese je nach Anwendungszweck variieren können. Das Flags Attribut sorgt dafür, dass Einstellungen im Inspektor, bei einer Aktualisierung nicht verloren gehen. Würde ich zB. dem Enum einen Eintrag hinzufügen und das Flags Attribut wäre nicht vorhanden, würden alle Komponenten welche dieses Enum im Inspektor verwenden den ersten Eintrag None anzeigen.
using System;

public class ItemCategoryModel {

    [Flags]
    public enum ItemCategory {
        None = 0,
        Head = 10,
        Body = 20,
        Hand = 30,
        Hip = 50,
        Feet = 60,
        Special = 70   
    }
}
Zuvor müssen wir uns aber noch mit einer kleinen Nebensache beschäftigen. Unity rendert die UI Elemente so, wie sie in der Hierarchy angeordnet sind. D.h. die Reihenfolge der Elemente entscheidet, ob UI Elemente oberhalb oder unterhalb von anderen UI Elementen liegen. Bricht man diese Tatsache auf unsere bereits bestehenden ItemSlots herab, so wird zuerst der erste ItemSlot gerendert, danach das Item und zuletzt der zweite ItemSlot. Möchte man nun den Apfel auf den zweiten Itemslot legen, wird während des Drag Vorganges der Rahmen des zweiten ItemSlots über dem Apfel gerendert. Um diesen Effekt zu vermeiden, müssen wir dafür sorgen, dass unser Apfel während der Bewegung immer zuletzt gerendert wird (also sich ganz unten in der Hierarchy befindet).

Aus diesem Grund legen wir uns einen Platzhalter an. Dieses, von mir als DragPanel bezeichnete, Objekt ist das letzte Child Objekt des MainCanvas.

Implementieren wir als erstes den DragItemHandler:
 
using UnityEngine;
using UnityEngine.EventSystems;

public class DragItemHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {

    [SerializeField]
    private Transform dragPanel;

    private Transform _startParent;
    private CanvasGroup _canvasGroup;

    void Awake() {
        _canvasGroup = GetComponent<CanvasGroup>();
    }

    public void OnBeginDrag(PointerEventData eventData) {
        // save initial position in hierarchy
        _startParent = transform.parent;

        // nessecary to activate OnDrop after drag ended
        _canvasGroup.blocksRaycasts = false;

        // set item as last silbling, to draw it always on top of UI
        transform.SetParent(dragPanel);
        transform.SetAsLastSibling();
    }

    public void OnDrag(PointerEventData eventData) {
        this.transform.position = Input.mousePosition;
    }

    public void OnEndDrag(PointerEventData eventData) {
        // reactivate to make item dragable again
        _canvasGroup.blocksRaycasts = true;

        // check if item was dropped
        if (transform.parent == dragPanel) {
            transform.SetParent(_startParent);
            transform.localPosition = Vector3.zero;
        }
    }
}

Diese Komponente wird nun für jedes Item hinzugefügt und konfiguriert. Sie kümmert sich darum das Objekt bewegbar zu machen, der Position des Mauszeigers zu folgen und bei Bedarf (sollte das Objekt nicht korrekt positioniert werden) es wieder in die Ausgangsposition zurückzusetzen. 

Wichtig ist, dass die Referenz auf DragPanel hinzugefügt wird, ansonsten wird das Item während des Drag Vorganges nicht korrekt angezeigt. Im Play-Mode kann Drag&Drop nun das erste Mal ausprobiert werden. :) Der nachfolgende Screenshot zeigt wie eine mögliche Realisierung aussehen kann.

Das sieht doch schon ganz gut aus. :) Natürlich geben wir uns damit noch nicht zufrieden. Immerhin fehlt noch einiges. Bevor wir uns aber neuen Parts zuwenden kümmern wir uns darum, dass Informationen welche zu dem jeweiligen Item gehören im DescriptionPanel angezeigt werden, sobald sich der Mauszeiger über einem Item befindet.

Für eine gute Darstellung modifizieren wir erstmal das DescriptionPanel und fügen 3 Texte (Rechtsklick - UI - Text) hinzu, welche wir DescriptionHeadText, DescriptionItemText und DescriptionStatsText nennen.

Je nach Anwendung und Geschmack können diese 3 Texte nun im DescriptionPanel platziert und mit Testwerten versehen werden. Ich habe das folgendermaßen umgesetzt:


Nun brauchen wir noch eine Komponente, welche sich um die Informationen des Items und um die Anzeige im DescriptionPanel kümmert. Dazu erstellen wir im Scripts Ordner ein neues Skript, den HoverItemHandler. Der Code dieser Komponente sieht folgendermaßen aus:

using UnityEngine;
using UnityEngine.EventSystems;

public class HoverItemHandler : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {

    public ItemCategoryModel.ItemCategory ItemCategory {
        get { return _itemCategory; }
    }

    [SerializeField]
    private string _itemName;
    [SerializeField]
    private ItemCategoryModel.ItemCategory _itemCategory;
    [SerializeField]
    [TextArea]
    private string _itemStatistics;
    [SerializeField]
    [TextArea]
    private string _itemDescription;

    public void OnPointerEnter(PointerEventData eventData) {
        DescriptionHandler.DescriptionHead.text = 
            string.Format("{0} - {1}", _itemName, _itemCategory.ToString());
        DescriptionHandler.DescriptionItemStats.text = _itemStatistics;
        DescriptionHandler.DescriptionItemText.text = _itemDescription;
    }

    public void OnPointerExit(PointerEventData eventData) {
        DescriptionHandler.DescriptionHead.text = "";
        DescriptionHandler.DescriptionItemStats.text = "";
        DescriptionHandler.DescriptionItemText.text = "";
    }
}
Zusätzlich benötigen wir noch einen DesciptionHandler, welcher sich darum kümmert, dass die Texte auch angezeigt werden. Dieser DescriptionHandler kann auf verschiedene Arten implementiert werden. Ich habe der Einfachheit halber ein Monobehaviour mit statischen Variablen und einem Singleton  ausgewählt (eine mögl. Alternative wäre zB. Dependency Injection) und wie folgt implementiert:

using UnityEngine;
using UnityEngine.UI;

public class DescriptionHandler : MonoBehaviour {

    public static Text DescriptionHead;
    public static Text DescriptionItemText;
    public static Text DescriptionItemStats;

    private static DescriptionHandler _instance;

    void Awake() {
        _instance = GameObject.FindObjectOfType<DescriptionHandler>();

        if (_instance == null) {
            GameObject container = new GameObject("DescriptionPanel");
            _instance = container.AddComponent<DescriptionHandler>();
        }

        // this is just for testing the item description.
        // there are a lot of better ways implementing this.
        DescriptionHead = _instance.transform.GetChild(0).GetComponent<Text>();
        DescriptionItemText = _instance.transform.GetChild(1).GetComponent<Text>();
        DescriptionItemStats = _instance.transform.GetChild(2).GetComponent<Text>();

        // reset values
        DescriptionHandler.DescriptionHead.text = "";
        DescriptionHandler.DescriptionItemStats.text = "";
        DescriptionHandler.DescriptionItemText.text = "";
    }    
}
Der DescriptionHandler wird nun dem DescriptionPanel hinzugefügt, der HoverItemHandler jedem Item. Wie nun die Iteminformationen nun gespeichert und geladen werden, bleibt jedem selbst überlassen. Ich habe bei 3 Items die Informationen einfach per Hand im Inspektor eingetragen. Jetzt ist ein guter Zeitpunkt um aus dem ItemSlot und dem Item Prefabs zu generieren und diese im Prefab Ordner abzulegen. Dadurch kann ich später weitere Slots und Items einfach per Code erzeugen. Am folgenden Bsp. sieht man, wie so etwas mit mehreren Items und angezeigter Beschreibung aussehen kann.

Nun haben wir Items, Beschreibungen und die Möglichkeit Items per Drag and Drop zu verschieben. Fehlt nur noch das EquipmentPanel, um Items ausrüsten zu können. Dazu fügen wir dem EquipmentParentPanel einen Text (Rechtsklick - UI - Text) und ein weiteres Panel, das EquipmentPanel, (Rechtsklick - UI - Panel) als Child Objekte hinzu. Ferner erhält das EquipmentPanel ein Hintergrundbild (Rechtsklick - UI - Image) um die Position und Verwendung der einzelnen Ausrüstungsgegenstände zu verdeutlichen.
Nun können wir, ähnlich wie beim InventoryPanel ItemSlots hinzufügen. Die Unterschiede zu den ItemSlots zuvor sind die Position, welche an das Hintergrundbild angepasst wurde, die Farbe (in diesem Fall grün, zur besseren Unterscheidung) und eine Kontrolle welche Items wo abgelegt werden können. Warum sollte man einen Helm bspw. am Fuß tragen? ;) Für die Kontrolle wird der bereits vorhandene DropItemHandler durch den DropEquipmentItemHandler ersetzt.
 
using UnityEngine;
using UnityEngine.EventSystems;

public class DropEquipmentItemHandler : DropItemHandler {

    [SerializeField]
    private ItemCategoryModel.ItemCategory _itemCategory;

    public override void OnDrop(PointerEventData eventData) {
        // check if there is already an item at the slot and if the category is correct
        if ((transform.childCount == 0) && 
            (eventData.pointerDrag.GetComponent<HoverItemHandler >().ItemCategory == _itemCategory)) {

            // set new item position
            eventData.pointerDrag.transform.SetParent(this.transform);
            eventData.pointerDrag.transform.localPosition = Vector3.zero;

            Debug.Log(eventData.pointerDrag.name + " dropped at " + this.gameObject.name);
        }
    }
}
Der DropEquipmentItemHandler wird nun jedem EquipmentItemSlot hinzugefügt. Wird das Prefab für ItemSlot verwendet, muss die Komponente DropItemHandler natürlich vorher entfernt werden. Für jeden EquipmentItemSlot muss jetzt noch eingestellt werden, welche Items von welcher Equipment Kategorie abgelegt werden können. Bei sieht es folgendermaßen aus:
 Somit ist unser Inventarsystem fertig und wenn wir nun alles korrekt eingestellt, Informationen für Items hinterlegt und die UI Elemente fertig poliert haben, könnten die Items wie folgend verwaltet werden :)