Skip to main content
Improved grammar, removed noise about bad english and asking if links are okay
Source Link

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:

(pleasePlease note, that this IS NOT very good code, so I will accept any editionsedits)

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

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

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

(thisThis 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 needneeds 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 singletonesingleton 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);
        }
    }
}

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.

PNow, try to run it.S When you're right-clicking the object, the context menu should appear, populated with list that we made. sorry for my EnglishPressing buttons will print into the console some text, and the context menu will be destroyed.

(I don't know is it allowed to post links like that, so dear Moderator, delete it if you wish)

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)

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

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

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

(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);
        }
    }
}

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.

(I don't know is it allowed to post links like that, so dear Moderator, delete it if you wish)

So, Mike Hunt, your question 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 this IS NOT very good code, so I will accept any edits)

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

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

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

(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 needs 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 singleton 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);
        }
    }
}

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

added link to sample project
Source Link
Exerion
  • 675
  • 1
  • 7
  • 17

Sample project (Unity Personal 5.2.0, VisualStudio Plugin): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing

(I don't know is it allowed to post links like that, so dear Moderator, delete it if you wish)

Sample project (Unity Personal 5.2.0, VisualStudio Plugin): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing

(I don't know is it allowed to post links like that, so dear Moderator, delete it if you wish)

Source Link
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