VR/3D 项目中 ,输入设备 不一样 不是鼠标因此 鼠标交互都不能用了,取而代之的可能是一支笔,一个手柄, 头盔,
这个时候,需要改写 UI 交互方式, 有很多种 交互方式, 可以借助Unity中的 碰撞检测,用物理碰撞检测 也是 OK的 用个Cube 缩放 很小, 这种方式本人看来比较Low。 可能就碰撞到 一个物体就 设置它的 宽度 Transform 里Scale属性, 有个物体就是 当 有多个 碰撞体的 时候 不是 会有 穿透现象吗?
因此 呢 解决了一时而已, 还是用RayCast射线交互 比较实在。也不 会有很多问题。
上次写了一个视线交互按钮后每次 需要用到按钮的时候 都需要去 添加RayButton组件, 很不方便, 因此就萌生 写编辑器扩展的念头, 其实 也挺简单的。
看了源码之后发现 在一个类里MenuOptions 编辑器的菜单显示层,具体 生产 由 DefaultControls 负责;
DefaultControls CreateButton 方法 是把 所有小的组件 组合 加工成 Button ;
我这里是利用预制体,因为预制体已经保存了 很多信息了,不需要我们再 一个 一个小小的组件去添加。
创建的时候要 找到一个Canvas作为它的父物体;下面是我的做法
public class RayButtonEditor : Editor
{
static GameObject rayButton = Resources.Load<GameObject>(FolderName.PrefabFolder + "UI/RayBtn");
public static Vector2 CanvasSize = new Vector2(1920, 1080);
[MenuItem("GameObject/UI/RayButton", false, 2029)]
static public void AddButton(MenuCommand menuCommand)
{
GameObject parent = menuCommand.context as GameObject;
if (rayButton == null)
{
Debug.LogError("The Path is Wrong");
}
rayButton.name = "RayButton";
if (parent == null)
{
Canvas ca = GameObject.FindObjectOfType<Canvas>();
if (ca == null)
{
//Create Canvas
parent = CreatecCanvas(CanvasSize);
}
else
{
parent = ca.gameObject;
}
}else if (parent.GetComponentInParent<Canvas>() == null&&parent.GetComponentInChildren<Canvas>()==null)
{
Canvas ca = GameObject.FindObjectOfType<Canvas>();
if (ca == null)
{
//Create Canvas
parent = CreatecCanvas(CanvasSize);
}
else
{
parent = ca.gameObject;
}
}
else
{
if (parent.GetComponentInParent<Canvas>() != null)
{
parent = parent.GetComponentInParent<Canvas>().transform.gameObject;
}
if (parent.GetComponentInChildren<Canvas>() != null)
{
parent = parent.GetComponentInChildren<Canvas>().transform.gameObject;
}
}
GameObject go = Instantiate(rayButton, parent.transform);
Selection.activeGameObject = go;
go.name = rayButton.name;
go.transform.localPosition = Vector3.zero;
Undo.RegisterCreatedObjectUndo(go, "Create go");
}
static GameObject CreatecCanvas(Vector2 size)
{
GameObject root = new GameObject("Canvas");
root.layer = LayerMask.NameToLayer("UI");
Canvas canvas = root.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
root.GetComponent<RectTransform>().sizeDelta =size;
root.transform.position = Vector3.zero;
Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);
return root;
}
}
可以利用 Untiy的预制体, 这里最好是写个资源工厂管理, 我这里是直接通过Resources加载, 很粗暴;
官方源码呢 是用DefaultControls 里的CreateButton方法 去 new出来 再去组合的。
复用性比较低;
Undo.RegisterCreatedObjectUndo(go, "Create go");
GameObject parent = menuCommand.context as GameObject; 这个是获取
就是把 创建出来的物体 注册 到撤销 Undo里。
考虑到用Resource 加载会添加一些资源 负担, 因此 有了下面的版本
private const float kWidth = 160f;
private const float kThickHeight = 30f;
// static GameObject rayButton = Resources.Load<GameObject>("Prefabs/"+ "UI/RayBtn");
public static Vector2 CanvasSize = new Vector2(1920, 1080);
[MenuItem("GameObject/UI/RayButton", false, 2029)]
static public void AddButton(MenuCommand menuCommand)
{
GameObject parent = menuCommand.context as GameObject;
GameObject buttonRoot = CreateUIElementRoot("Button", new Vector2(kWidth, kThickHeight));
GameObject childText = new GameObject("Text");
childText.AddComponent<RectTransform>();
SetParentAndAlign(childText, buttonRoot);
Image image = buttonRoot.AddComponent<Image>();
image.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
image.type = Image.Type.Sliced;
image.color = new Color(1f, 1f, 1f, 1f);
Button bt = buttonRoot.AddComponent<RayButton>();
SetDefaultColorTransitionValues(bt);
Text text = childText.AddComponent<Text>();
text.text = "RayButton";
text.alignment = TextAnchor.MiddleCenter;
SetDefaultTextValues(text);
RectTransform textRectTransform = childText.GetComponent<RectTransform>();
textRectTransform.anchorMin = Vector2.zero;
textRectTransform.anchorMax = Vector2.one;
textRectTransform.sizeDelta = Vector2.zero;
//if (rayButton == null)
//{
// // Debug.LogError("The Path is Wrong");
//}
//rayButton.name = "RayButton";
if (parent == null)
{
Canvas ca = GameObject.FindObjectOfType<Canvas>();
if (ca == null)
{
//Create Canvas
parent = CreatecCanvas(CanvasSize);
}
else
{
parent = ca.gameObject;
}
}else if (parent.GetComponentInParent<Canvas>() == null&&parent.GetComponentInChildren<Canvas>()==null)
{
Canvas ca = GameObject.FindObjectOfType<Canvas>();
if (ca == null)
{
//Create Canvas
parent = CreatecCanvas(CanvasSize);
}
else
{
parent = ca.gameObject;
}
}
else
{
if (parent.GetComponentInParent<Canvas>() != null)
{
parent = parent.GetComponentInParent<Canvas>().transform.gameObject;
}
if (parent.GetComponentInChildren<Canvas>() != null)
{
parent = parent.GetComponentInChildren<Canvas>().transform.gameObject;
}
}
GameObject go = buttonRoot; //Instantiate(rayButton, parent.transform);
go.transform.SetParent(parent.transform);
Selection.activeGameObject = go;
go.name = buttonRoot.name;
go.transform.localPosition = Vector3.zero;
Undo.RegisterCreatedObjectUndo(go, "Create go");
}
static GameObject CreatecCanvas(Vector2 size)
{
GameObject root = new GameObject("Canvas");
root.layer = LayerMask.NameToLayer("UI");
Canvas canvas = root.AddComponent<Canvas>();
#if XRMODE
canvas.renderMode = RenderMode.WorldSpace;
#else
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
UnityEngine.EventSystems.EventSystem tempSystem =
GameObject.FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
if (tempSystem==null)
{
GameObject go = new GameObject("EventSystem");
tempSystem = go.AddComponent<UnityEngine.EventSystems.EventSystem>();
Undo.AddComponent<StandaloneInputModule>(go);
}
#endif
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
root.GetComponent<RectTransform>().sizeDelta =size;
root.transform.position = Vector3.zero;
Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);
return root;
}
#region Tool's method
private static void SetDefaultTextValues(Text lbl)
{
// Set text values we want across UI elements in default controls.
// Don't set values which are the same as the default values for the Text component,
// since there's no point in that, and it's good to keep them as consistent as possible.
lbl.color =Color.black;
// Reset() is not called when playing. We still want the default font to be assigned
lbl.font= Resources.GetBuiltinResource<Font>("Arial.ttf");
}
private static void SetDefaultColorTransitionValues(Selectable slider)
{
ColorBlock colors = slider.colors;
colors.highlightedColor = new Color(0.882f, 0.882f, 0.882f);
colors.pressedColor = new Color(0.698f, 0.698f, 0.698f);
colors.disabledColor = new Color(0.521f, 0.521f, 0.521f);
}
private static GameObject CreateUIElementRoot(string name, Vector2 size)
{
GameObject child = new GameObject(name);
RectTransform rectTransform = child.AddComponent<RectTransform>();
rectTransform.sizeDelta = size;
return child;
}
private static void SetParentAndAlign(GameObject child, GameObject parent)
{
if (parent == null)
return;
child.transform.SetParent(parent.transform, false);
SetLayerRecursively(child, parent.layer);
}
private static void SetLayerRecursively(GameObject go, int layer)
{
go.layer = layer;
Transform t = go.transform;
for (int i = 0; i < t.childCount; i++)
SetLayerRecursively(t.GetChild(i).gameObject, layer);
}
#endregion