Inventory Context Menu Support

Right click --> Unequip, right click --> Drop... Wait, I wanted to consume you!

The documentation has moved to: https://mitschmr-studios.io/documentation/api-guides/inventorycontextmenusupport.html

This version will no longer be updated and maintained.

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).

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:

  • 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

Make sure to have a backup of your project before doing anything of this, so you can revert changes made to the scripts!

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.

Using statements

Add the following two using statements (above the Unity Editor directive):

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:

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:

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:

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

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). Open Assets --> Plugins --> GameCreator --> Core --> Editor --> UI --> ButtonActionsEditor.cs.

Add a Serialized Property for the OnClickRight event:

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... :

spOnClickRight = serializedObject.FindProperty("onClickRight");

Modify OnInspectorGUI()

Modify the OnInspectorGUI() method to look like this:

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:

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.

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:

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:

In order to make sure that the references are correct and not somehow corrupted, clear them all (set to none) and readd them.

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:

[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:

[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:

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.

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.

Don't forget to assign the OnClickRight() method to the right click event field of the GC UI Button in the item prefab!

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:

Test the functionality

Test if everything is working as intended:

  • 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.

Last updated