# Inventory Context Menu Support

{% hint style="danger" %}
The documentation has moved to: <https://mitschmr-studios.io/documentation/api-guides/inventorycontextmenusupport.html>

This version will no longer be updated and maintained.&#x20;
{% endhint %}

In this tutorial, I want to show you how to modify the Game Creator inventory to support a context menu when clicking the right mouse button and do the most common things ((Un-)Equip, Consume, Drop).&#x20;

![](https://3625548948-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6E9D__lMnHqqxqKxIn%2F-MEXnKkKafBKQHaMdzT4%2F-MEXoCOe0AtnGwJKwtDa%2Fimage.png?alt=media\&token=4945feb9-bf87-4178-b62a-9476868ca740)

### Prerequisites

* Install Unity
* Install IDE (I use Visual Studio 2019)
* Import Game Creator
* Import and enable the Inventory module

### General

Many games (like Zelda: Breath of the Wilds) use a context menu with buttons to do stuff, like equipping weapons, dropping items etc. The Game Creator Inventory doesn't support this out of the box. There are several changes that need to be done in order to support a context menu:&#x20;

* Modify the GC UI Button script to differentiate between right and left mouse clicks
* Replace the Button component in the item prefab with the GC UI Button
* Add a prefab for the context menu with buttons
* Add a script to the prefab which stores references to the buttons (prefab and script attached in zip)
* Modify the GC ItemUI script to add the necessary functionality for a context menu
* Assign the variables in the modified ItemUI script

{% hint style="warning" %}
Make sure to have a backup of your project before doing anything of this, so you can revert changes made to the scripts!
{% endhint %}

### Adding Mouse Right Click Support

There is currently no differentation between a left and a right mouse click on a button in Unity. Game Creator has its own UI button with additional functionality to the basic Unity UI Button. Open the `ButtonActions` script, you can find it under Assets --> Plugins --> GameCreator --> Core --> Mono --> UI --> ButtonActions.cs.&#x20;

#### Using statements

&#x20;Add the following two using statements (above the Unity Editor directive):&#x20;

```csharp
using UnityEngine.EventSystems; // Already exists, only for clarity where to put it
using UnityEngine.Serialization;
using static UnityEngine.UI.Button;
```

#### Add right click button event

Add the following content after `public Actions actions = null;` and before `public ButtonActionsEvent onClick...`in the **PROPERTIES** section:&#x20;

```csharp
public Actions actions = null; // Already exists

[FormerlySerializedAs("onClickRight")]
[SerializeField]
public ButtonClickedEvent onClickRight = new ButtonClickedEvent();

public ButtonActionsEvent onClick = new ButtonActionsEvent(); // Already exists
```

#### Modify the OnPointerClick() method

Modify the method `OnPointerClick(PointerEventData eventData)` in the **INTERFACES** section:&#x20;

```csharp
public virtual void OnPointerClick(PointerEventData eventData)
{
    if (eventData.button == PointerEventData.InputButton.Left)
    {
        this.Press();
    }
    else if (eventData.button == PointerEventData.InputButton.Right)
    {
        this.PressRight();
    }
}
```

#### Add the PressRight() method

Add the method `PressRight()` in the **PRIVATE METHODS** section:&#x20;

```csharp
private void PressRight()
{
    if (!IsActive() || !IsInteractable()) return;
    if (this.onClickRight != null) this.onClickRight.Invoke();
}
```

The **||** you see in the first if statement is a so called conditional logical OR operator: [Reference](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators#conditional-logical-or-operator-)

### Change the look of the Button Inspector

#### Add a serialized property

The GC UI Button layout is being replaced by a custom one. I have a tutorial on this too ([Custom Editor Windows - Part 2](https://docs.mitschmr-studios.io/gc-api-guides/custom-editor-windows-part-2)). Open Assets --> Plugins --> GameCreator --> Core --> Editor --> UI --> ButtonActionsEditor.cs.&#x20;

Add a Serialized Property for the OnClickRight event:&#x20;

```csharp
private ActionsEditor editorActions; // Already exists
SerializedProperty spOnClickRight;
```

#### Assign the added serialized property

Add this line of code to the `OnEnable()` method after `base.OnEnable()` and before `SerializedProperty spActions...` :

```csharp
spOnClickRight = serializedObject.FindProperty("onClickRight");
```

#### Modify OnInspectorGUI()

Modify the `OnInspectorGUI()` method to look like this:&#x20;

```csharp
public override void OnInspectorGUI()
{
    base.OnInspectorGUI();
    EditorGUILayout.Space();

    serializedObject.Update();

    EditorGUILayout.LabelField("Left click");
    if (this.editorActions != null)
    {
        this.editorActions.OnInspectorGUI();
    }

    EditorGUILayout.Space();

    EditorGUILayout.LabelField("Right click");
    EditorGUILayout.PropertyField(spOnClickRight);
    serializedObject.ApplyModifiedProperties();
}
```

### Replace the button component

Now open an item prefab like the ItemRPG or ItemAdventure (depending on what skin you want to use), delete the button component on the root object and add the GC UI Button that we just modified. Add the action **Call Method** to the left click and add a new entry to right click. It should then look like this:&#x20;

![](https://3625548948-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6E9D__lMnHqqxqKxIn%2F-MEWteinkCokrDCjPqNd%2F-MEX-YmrILaI4xlU_Ghz%2Fimage.png?alt=media\&token=a571f5a3-f6fd-4a5e-a9dc-9832133dd205)

You don't have `ItemUI.OnClickRight()` as of now, that changes in one of the next sections. Don't forget to set this when we add it.&#x20;

### Import ContextMenu Prefab And Script

I have prepared a script and a prefab for this. Download this zip file, extract it, import first the script then the prefab package into your project and you should be good:&#x20;

{% file src="<https://3625548948-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6E9D__lMnHqqxqKxIn%2F-MEX4NzQ0W1nF9UuaOTQ%2F-MEX5-q-6E1SJFZT-o56%2FItemContextMenuUI.zip?alt=media&token=596caae2-7018-405f-baef-5a86294efa0b>" %}

The prefab is fully customizable, the script is necessary to hold a reference to the buttons. Make sure that the buttons are assigned to the variables:&#x20;

![](https://3625548948-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6E9D__lMnHqqxqKxIn%2F-MEX4NzQ0W1nF9UuaOTQ%2F-MEX5fjgM5-9VhArOpv-%2Fimage.png?alt=media\&token=7497b532-6a36-418e-b456-fec4221f717e)

{% hint style="info" %}
In order to make sure that the references are correct and not somehow corrupted, clear them all (set to none) and readd them.&#x20;
{% endhint %}

### Add Context Menu Functionality

#### Add a property to the InventoryManager

In order for the context menu to work correctly, we need to add a variable to hold a reference of it to the InventoryManager script. Open Assets --> Plugins --> GameCreator --> Inventory --> Mono --> Utilities --> InventoryManager.cs and add the following line to the **PROPERTIES** section:&#x20;

```csharp
[HideInInspector] public GameObject itemContextMenu;
```

#### Add Properties to the ItemUI script

Now let's add the functionality for the context menu. Open the item prefab you chose above and edit the ItemUI script. Add the following code below `public GameObject equipped;` in the **PROPERTIES** section:&#x20;

```csharp
[Space]
public RectTransform itemTransform;
public GameObject prefabContextMenu;

[HideInInspector]
public GameObject itemContextMenu;
```

#### Add Methods to the ItemUI script

Add the System namespace as a using statement: `using System;` Add the following line below `InventoryManager.Instance.ConsumeItem(this.item.uuid);`  in `public virtual void OnClick()` to disable the context menu when you click on an item:&#x20;

```csharp
if (InventoryManager.Instance.itemContextMenu != null) HideItemContextMenu();
```

Then add all of the following code after `public virtual void OnClick()` and before `public virtual void OnDragBegin()` . The explanation to every method and its functionality is below the code block.&#x20;

```csharp
public virtual void OnClickRight()
{
    Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
    ShowItemContextMenu(this.item.uuid, this.item.itemTypes, prefabContextMenu, itemTransform);
}

public virtual void ShowItemContextMenu(int uuid, int itemTypes, GameObject prefabContextMenu, RectTransform itemTransform)
{
    if (this.itemContextMenu == null && InventoryManager.Instance.itemContextMenu == null)
    {
        this.itemContextMenu = Instantiate(prefabContextMenu, itemTransform);
        this.itemContextMenu.transform.localScale = Vector3.one;

        ItemContextMenuUI itemContextMenuUI = this.itemContextMenu.GetComponent<ItemContextMenuUI>();

        AddButtonListeners(itemContextMenuUI, uuid, itemTypes);

        SetButtonVisibility(uuid, itemContextMenuUI);

        InventoryManager.Instance.itemContextMenu = this.itemContextMenu;
    }
    else if (this.itemContextMenu == null && InventoryManager.Instance.itemContextMenu != null)
    {
        HideItemContextMenu();
        this.itemContextMenu = Instantiate(prefabContextMenu, itemTransform);
        this.itemContextMenu.transform.localScale = Vector3.one;

        ItemContextMenuUI itemContextMenuUI = this.itemContextMenu.GetComponent<ItemContextMenuUI>();

        AddButtonListeners(itemContextMenuUI, uuid, itemTypes);

        SetButtonVisibility(uuid, itemContextMenuUI);

        InventoryManager.Instance.itemContextMenu = this.itemContextMenu;
    }
    else
    {
        HideItemContextMenu();
        this.itemContextMenu.SetActive(true);

        ItemContextMenuUI itemContextMenuUI = this.itemContextMenu.GetComponent<ItemContextMenuUI>();

        SetButtonVisibility(uuid, itemContextMenuUI);

        InventoryManager.Instance.itemContextMenu = this.itemContextMenu;
    }
}

public virtual void HideItemContextMenu()
{
    try
    {
        InventoryManager.Instance.itemContextMenu.SetActive(false);
    }
    catch
    {
        InventoryManager.Instance.itemContextMenu = null;
    }
}

public virtual void AddButtonListeners (ItemContextMenuUI itemContextMenuUI, int uuid, int itemTypes)
{
    Button btnEquip = itemContextMenuUI.buttonEquip.GetComponent<Button>();
    Button btnUnequip = itemContextMenuUI.buttonUnequip.GetComponent<Button>();
    Button btnConsume = itemContextMenuUI.buttonConsume.GetComponent<Button>();
    Button btnDrop = itemContextMenuUI.buttonDrop.GetComponent<Button>();

    btnEquip.onClick.AddListener(() => itemEquip(uuid, itemTypes));
    btnUnequip.onClick.AddListener(() => itemUnequip(uuid));
    btnConsume.onClick.AddListener(() => itemConsume(uuid));
    btnDrop.onClick.AddListener(() => itemDrop(uuid, itemTypes));
}

public virtual void SetButtonVisibility(int uuid, ItemContextMenuUI itemContextMenuUI)
{
    Item item = InventoryManager.Instance.itemsCatalogue[uuid];
    Item itemEquipped = null;
            
    if (item.itemTypes >= 0)
    {
        itemEquipped = InventoryManager.Instance.GetEquip(HookPlayer.Instance.gameObject, Convert.ToInt32(Math.Log(item.itemTypes, 2)));
    }

    GameObject buttonEquip = itemContextMenuUI.buttonEquip;
    GameObject buttonUnequip = itemContextMenuUI.buttonUnequip;
    GameObject buttonConsume = itemContextMenuUI.buttonConsume;
    GameObject buttonDrop = itemContextMenuUI.buttonDrop;

    if (item.equipable && itemEquipped == null)
    {
        buttonEquip.SetActive(true);
        buttonUnequip.SetActive(false);
        buttonDrop.SetActive(true);
    }
    else if (item.equipable && itemEquipped.uuid != item.uuid)
    {
        buttonEquip.SetActive(true);
        buttonUnequip.SetActive(false);
        buttonDrop.SetActive(true);
    }
    else if (item.equipable && itemEquipped.uuid == item.uuid)
    {
        buttonEquip.SetActive(false);
        buttonUnequip.SetActive(true);
        buttonDrop.SetActive(false);
    }
    else
    {
        buttonEquip.SetActive(false);
        buttonUnequip.SetActive(false);
    }
                
    if (item.consumeItem)
    {
        buttonConsume.SetActive(true);
    }
    else
    {
        buttonConsume.SetActive(false);
    }
}

public virtual void itemEquip(int uuid, int itemTypes)
{
    InventoryManager.Instance.Equip(HookPlayer.Instance.gameObject, uuid, Convert.ToInt32(Math.Log(itemTypes, 2)));

    HideItemContextMenu();
}

public virtual void itemUnequip(int uuid)
{
    InventoryManager.Instance.Unequip(HookPlayer.Instance.gameObject, uuid);

    HideItemContextMenu();
}

public virtual void itemConsume(int uuid)
{
    InventoryManager.Instance.ConsumeItem(uuid, HookPlayer.Instance.gameObject);

    HideItemContextMenu();
}

public virtual void itemDrop(int uuid, int itemTypes)
{
    Vector3 position = HookPlayer.Instance.transform.TransformPoint(Vector3.forward * 1f);

    Instantiate(this.item.prefab, position, Quaternion.identity);
    InventoryManager.Instance.SubstractItemFromInventory(this.item.uuid, 1);

    HideItemContextMenu();
}
```

| Method                  | Functionality                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OnClickRight()`        | Gets executed when a right click on an item is registered.                                                                                                                                                                                                                                                                                                                                                                    |
| `ShowItemContextMenu()` | Checks if a context menu is already added to the item and if a context menu was already opened, if not, then it instantiates one and adds it as a reference to the InventoryManager. This is necessary to close the context menus when you click on any item or open a context menu on another item.                                                                                                                          |
| `HideItemContextMenu()` | Tries to set the current context menu to inactive. This results in an error when you remove the last item of an item from the inventory. If this happens, it then sets the variable to null.                                                                                                                                                                                                                                  |
| `AddButtonListeners()`  | This adds a non-persistent listener for each button in the context menu of an item. The strange way of writing a listener `<variableName>.AddListener(() => Method(argument1, argument2))` allows you to add arguments to a method that gets executed when the event is called. Otherwise you just write `<variableName>.AddListener(MethodName)`. Note that you don't use the () brackets after the method name in this way. |
| `SetButtonVisibility()` | Gets the current selected item and compares different properties of it to display the suitable buttons. You don't want the button **Equip** when you already have equipped this item.                                                                                                                                                                                                                                         |
| `itemEquip()`           | Gets called when the button **Equip** is pressed. Equips the item.                                                                                                                                                                                                                                                                                                                                                            |
| `itemUnequip()`         | Gets called when the button **Unequip** is pressed. Unequips the item.                                                                                                                                                                                                                                                                                                                                                        |
| `itemConsume()`         | Gets called when the button **Consume** is pressed. Consumes the item.                                                                                                                                                                                                                                                                                                                                                        |
| `itemDrop()`            | Gets called when the button **Drop** is pressed. Instantiates the item in front of the player with a distance of 1 and subtracts one. The distance value is hardcoded, but you can change it to any value you want.                                                                                                                                                                                                           |

{% hint style="info" %}
Don't forget to assign the `OnClickRight()` method to the right click event field of the GC UI Button in the item prefab!
{% endhint %}

### Assign Variables In The Item Prefab

The only thing left to do is add a value for the variables `itemPrefab` and `prefabContextMenu` in your chosen item prefab:&#x20;

![](https://3625548948-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6E9D__lMnHqqxqKxIn%2F-MEXAR6PX6pF0kj3rGiu%2F-MEXBHOFLBAzJ42nqUd0%2Fimage.png?alt=media\&token=9bd9facd-6735-4290-8feb-503fc0ef838e)

### Test the functionality

Test if everything is working as intended:&#x20;

* Context Menu appears when clicking the right mouse button on an item
* Context Menu disappears when clicking the right mouse button on another item and a new context menu appears on the other item
* Context Menu disappears when clicking the left mouse button on any item
* (Un-)Equipping, consuming and dropping is working

It is not perfect, i.e. the context menu remains active when you close and reopen the inventory window. In order to iron out this behavior, further customization is needed, which I won't cover in this tutorial. It is possible that there will be a tutorial on how to do this in the future, but not now.&#x20;
