Skip to main content
1 of 3
Exerion
  • 675
  • 1
  • 7
  • 17

So, Mike Hunt, your question is so interested me, I decided to implement a full solution. After three hours of trying different things, I ended up with this step-by-step solution:

(please note, that IS NOT very good code, so I will accept any editions)

Creating Content Panel

(this panel will be a container for our context-menu buttons)

  • Create new UI Panel
  • Set anchor to bottom-left
  • Set width to 300 (as you wish)
  • Add to a panel a new component Vertical Layout Group and set Child Alignment to upper-center, Child Force Expand to width (not height)
  • Add to a panel a new component Content Size Fitter and set Vertical Fit to Min Size
  • Save it as prefab

(at this point our Panel will shrink down to line. It's normal. This panel will accept buttons as childs, align them vertically and stretch to summary content height)

Creating Sample Button

(this button will be instantiated and customized to show context-menu items)

  • Create new UI Button
  • Set anchor to top-left
  • Add to a button a new component Layout Element, set Min Height to 30, Preferred Height to 30
  • Save it as prefab

Creating ContextMenu.cs script

(this script has a method that creates and shows Context Menu)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[System.Serializable]
public class ContextMenuItem
{
    // this class - just a box to some data

    public string text;             // text to display on button
    public Button button;           // sample button prefab
    public Action<Image> action;    // delegate to method, that need to be executed when button is clicked

    public ContextMenuItem(string text, Button button, Action<Image> action)
    {
        this.text = text;
        this.button = button;
        this.action = action;
    }
}

public class ContextMenu : MonoBehaviour
{
    public Image contentPanel;              // content panel prefab
    public Canvas canvas;                   // link to main canvas, where will be Context Menu

    private static ContextMenu instance;    // some kind of singletone here

    public static ContextMenu Instance
    {
        get
        {
            if(instance == null)
            {
                instance = FindObjectOfType(typeof(ContextMenu)) as ContextMenu;
                if(instance == null)
                {
                    instance = new ContextMenu();
                }
            }
            return instance;
        }
    }

    public void CreateContextMenu(List<ContextMenuItem> items, Vector2 position)
    {
        // here we are creating and displaying Context Menu

        Image panel = Instantiate(contentPanel, new Vector3(position.x, position.y, 0), Quaternion.identity) as Image;
        panel.transform.SetParent(canvas.transform);
        panel.transform.SetAsLastSibling();
        panel.rectTransform.anchoredPosition = position;

        foreach(var item in items)
        {
            ContextMenuItem tempReference = item;
            Button button = Instantiate(item.button) as Button;
            Text buttonText = button.GetComponentInChildren(typeof(Text)) as Text;
            buttonText.text = item.text;
            button.onClick.AddListener(delegate { tempReference.action(panel); });
            button.transform.SetParent(panel.transform);
        }
    }
}
  • Attach this script to a Canvas and populate fields. Drag-n-drop ContentPanel prefab to corresponding slot, and drag Canvas itself to slot Canvas.

Creating ItemController.cs script

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ItemController : MonoBehaviour
{
    public Button sampleButton;                         // sample button prefab
    private List<ContextMenuItem> contextMenuItems;     // list of items in menu

    void Awake()
    {
        // Here we are creating and populating our future Context Menu.
        // I do it in Awake once, but as you can see, 
        // it can be edited at runtime anywhere and anytime.

        contextMenuItems = new List<ContextMenuItem>();
        Action<Image> equip = new Action<Image>(EquipAction);
        Action<Image> use = new Action<Image>(UseAction);
        Action<Image> drop = new Action<Image>(DropAction);

        contextMenuItems.Add(new ContextMenuItem("Equip", sampleButton, equip));
        contextMenuItems.Add(new ContextMenuItem("Use", sampleButton, use));
        contextMenuItems.Add(new ContextMenuItem("Drop", sampleButton, drop));
    }

    void OnMouseOver()
    {
        if(Input.GetMouseButtonDown(1))
        {
            Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
            ContextMenu.Instance.CreateContextMenu(contextMenuItems, new Vector2(pos.x, pos.y));
        }

    }

    void EquipAction(Image contextPanel)
    {
        Debug.Log("Equipped");
        Destroy(contextPanel.gameObject);
    }

    void UseAction(Image contextPanel)
    {
        Debug.Log("Used");
        Destroy(contextPanel.gameObject);
    }

    void DropAction(Image contextPanel)
    {
        Debug.Log("Dropped");
        Destroy(contextPanel.gameObject);
    }
}
  • Create sample object in the scene (i.e Cube), place it to be visible to camera and attach this script to it. Drag-n-drop sampleButton prefab to corresponding slot.

Now, try to run it. When you right-clicking object, context menu should appear, populated with list that we made. Pressing buttons will print into console some text, and context menu will be destroyed.

P.S. sorry for my English.

Possible improvements:

  • even more generic!
  • better memory management (dirty links, not destroying panel, disabling)
  • some fancy stuff
Exerion
  • 675
  • 1
  • 7
  • 17