因此,迈克·亨特(Mike Hunt),您的问题让我很感兴趣,因此我决定实施一个完整的解决方案。经过三个小时的尝试,我最终得到了以下逐步解决方案:
(请注意,这不是很好的代码,因此我将接受任何修改)
创建内容面板
(此面板将成为上下文菜单按钮的容器)
- 创建新的
UI Panel
- 设置
anchor
为左下
- 设置
width
为300(根据需要)
- 将一个新组件添加到面板中,
Vertical Layout Group
并设置Child Alignment
为上居中,Child Force Expand
宽度(而非高度)
- 向面板添加新组件
Content Size Fitter
并设置Vertical Fit
为“最小大小”
- 将其另存为预制件
(这时我们的面板会缩小到一行。这是正常的。该面板会将按钮作为子代接受,将它们垂直对齐并拉伸到摘要内容的高度)
创建样本按钮
(此按钮将被实例化和自定义以显示上下文菜单项)
- 创建新的UI按钮
- 组
anchor
左上
- 在按钮中添加一个新组件
Layout Element
,设置Min Height
为30,Preferred Height
到30
- 将其另存为预制件
创建ContextMenu.cs脚本
(此脚本具有创建和显示“上下文菜单”的方法)
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);
}
}
}
- 将此脚本附加到“画布”并填充字段。将
ContentPanel
预制件拖放到相应的插槽,然后将Canvas自身拖动到slot Canvas
。
创建ItemController.cs脚本
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);
}
}
- 在场景中创建示例对象(即
Cube
),将其放置在相机可见的位置,并将此脚本附加到该对象上。将sampleButton
预制件拖放到相应的插槽中。
现在,尝试运行它。右键单击对象时,将显示上下文菜单,其中包含我们创建的列表。按下按钮将在控制台中打印一些文本,上下文菜单将被破坏。
可能的改进:
- 更通用!
- 更好的内存管理(脏链接,不破坏面板,禁用)
- 一些花哨的东西
示例项目(Unity Personal 5.2.0,VisualStudio插件): https ://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp
=sharing