一、前言
最近做AR和VR的项目,经常需要用到3D的UI,特意将最近自己捣鼓的这个UI的东西写下来。效果如图所示:主要的动画和素材也是借鉴了
第三方的插件“HoloUIExample”,本文主要在此资源和动画的基础上,添加了自定义的几种交互事件,并且事件采用了反射功能做到类似于unity自带的EventTrigger一样可以自由动态绑定事件接收的对象和方法。如图所示为EventTrigger的绑定事件的接收方法在面板上的操作示意图
本文当前阶段事件动态绑定的界面操作示意图如下图所示
EventTrigger主要是针对Unity自带的鼠标操作事件的处理,本文的3DUI采用了自己封装的一套鼠标交互形式,当然还是使用了射线去检测,不过其拓展性可见一斑,我们可以自己将射线定义到任何物体上,比如AR或VR的手柄上,从而进行自定义的UI交互。
二、实现过程
1、鼠标射线处理的代码如下:该脚本中处理鼠标光标悬浮进入物体和退出悬浮物体、鼠标左键单击选中以及鼠标左键双击的处理逻辑。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Global_EventSystem : MonoBehaviour {
private UI3D_BtnEventInterface curHoverObj;
private UI3D_BtnEventInterface curSlectedObj;
private float doubleClickTime;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
EditorTestHoverAndSelectedObj();
}
private void EditorTestHoverAndSelectedObj()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo = new RaycastHit();
UI3D_BtnEventInterface tempObj = null;
只检测生成的战斗元素所在的层级
//int layerMask = 1 << LayerMask.NameToLayer("TransparentFX");
if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity))
{
tempObj = hitInfo.collider.GetComponent<UI3D_BtnEventInterface>();
//鼠标选中的物体要是当前类型的角色创建的物体
}
if (null != tempObj)
{
tempObj.Hover();
curHoverObj = tempObj;
}
else
{
if (null != curHoverObj)
{
curHoverObj.UnHover();
curHoverObj = null;
}
}
if (Input.GetKeyDown(KeyCode.Mouse0))
{
if (null != curHoverObj)
{
if (curHoverObj != curSlectedObj)
{
curHoverObj.Selected();
if (null != curSlectedObj)
{
curSlectedObj.UnSelected();
}
}
else
{
curHoverObj.ToggleSelected();
}
curSlectedObj = curHoverObj;
//当第二次点击鼠标,且时间间隔满足要求时双击鼠标
if (Time.time - doubleClickTime <= 0.5f)
{
curSlectedObj.DoubleClick();
}
doubleClickTime = Time.time;
}
}
}
}
所有的事件处理的逻辑的代码都是通过接口“UI3D_BtnEventInterface”来完成,采用多态的形式,将继承自该“UI3D_BtnEventInterface”接口的方法进行调用。基本逻辑就是先射线检测是否是继承了“UI3D_BtnEventInterface”接口,然后调用该结构中的方法,如Hover、UnHover等等
2、下一步实现“UI3D_BtnEventInterface”的具体实现,新建一个“UI3D_BtnEvent”脚本,代码如下:
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using System;
public class UI3D_BtnEvent : MonoBehaviour ,UI3D_BtnEventInterface{
[SerializeField]
private bool isEventHover;
[SerializeField]
private bool isEventUnHover;
/// <summary>
/// 悬浮事件的接收方法名
/// </summary>
[SerializeField]
private string hoverEMN = string.Empty;
/// <summary>
/// 退出悬浮的接收方法名
/// </summary>
[SerializeField]
private string unHoverEMN = string.Empty;
[SerializeField]
/// 事件处理方法的接收对象
/// </summary>
private GameObject eventReceiveObj;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void TestEventHover()
{
// Debug.Log("TestEventHover");
}
public void Hover()
{
if (null != eventReceiveObj && hoverEMN != null)
{
eventReceiveObj.SendMessage(hoverEMN);
}
}
public void UnHover()
{
if (null != eventReceiveObj && hoverEMN != null)
{
eventReceiveObj.SendMessage(unHoverEMN);
}
}
public void PressDown()
{
}
public void PressUp()
{
}
public void TogglePress()
{
}
public void DoubleClick()
{
}
public void Selected()
{
}
public void UnSelected()
{
}
public void ToggleSelected()
{
}
}
public enum E_EventType
{
None,
Hover,
UnHover,
}
这里采用了“SendMessage”的方法,将动态绑定的对象中定义的公有方法的名称来调用具体的对象中的方法。另外,几个变量都要采用序列化,方便在编辑代码中调用和修改。
3、接下来就是在编辑器中的逻辑代码,这个会稍微复杂一点。代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(UI3D_BtnEvent))]
public class UI3DBtnEventEditor : Editor
{
private SerializedProperty nameHoverMethod;
private SerializedProperty nameUnHoverMethod;
private SerializedProperty isEventHover;
private SerializedProperty isEventUnHover;
private SerializedProperty eventReceiveObj;
private UnityEngine.Object priEventReceiveObj;
private Component[] tempComs;
private Enum tempEnumClassHover;
private Enum tempEnumClassUnHover;
private Enum tempComponentEnumName;
private string priComonentName = string.Empty;
private List<string> tempListComsName = new List<string>();
private void OnEnable()
{
nameHoverMethod = serializedObject.FindProperty("hoverEMN");
nameUnHoverMethod = serializedObject.FindProperty("unHoverEMN");
eventReceiveObj = serializedObject.FindProperty("eventReceiveObj");
priEventReceiveObj = eventReceiveObj.objectReferenceValue;
isEventHover =serializedObject.FindProperty("isEventHover");
isEventUnHover =serializedObject.FindProperty("isEventUnHover");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
UI3D_BtnEvent tempTargetUBE = (UI3D_BtnEvent)target;
eventReceiveObj.objectReferenceValue = EditorGUILayout.ObjectField("悬浮事件接收对象", eventReceiveObj.objectReferenceValue, typeof(UnityEngine.Object), true);
if (null != eventReceiveObj.objectReferenceValue && priEventReceiveObj != eventReceiveObj.objectReferenceValue)
{
tempListComsName.Clear();
priEventReceiveObj = eventReceiveObj.objectReferenceValue;
GameObject tempObj = (GameObject)eventReceiveObj.objectReferenceValue;
if (null != tempObj)
{
//先选择组件,注意如果有脚本挂在上面然后删掉了还是会获得并且是null
tempComs = tempObj.GetComponents<Component>();
for (int i = 0; i < tempComs.Length; i++)
{
Component tempComponent = tempComs[i];
if (null == tempComponent) continue;
Type tempType = tempComponent.GetType();
tempListComsName.Add(tempType.ToString());
}
tempComponentEnumName = CreateEnum(tempListComsName);
}
}
//选择绑定对象
if (null != tempComponentEnumName)
{
tempComponentEnumName = EditorGUILayout.EnumPopup("选择绑定对象", tempComponentEnumName);
//根据选择对象不一样选择对象里面的方法,刷新方法的选择
if (tempComponentEnumName.ToString() != priComonentName && null != tempComs && tempComs.Length == tempListComsName.Count)
{
priComonentName = tempComponentEnumName.ToString();
int tempIndexEnumName = tempListComsName.FindIndex(p => p == tempComponentEnumName.ToString());
if (-1 != tempIndexEnumName)
{
priComonentName = tempComponentEnumName.ToString();
//Debug.Log(tempIndexEnumName);
//再选择组件里面的方法
Type tempType = tempComs[tempIndexEnumName].GetType();
//使用反射将方法全部反射出来
MethodInfo[] methods = tempType.GetMethods();
List<string> tempListStrEnuMethods = new List<string>();
for (int i = 0; i < methods.Length; i++)
{
MethodBase tempMB = methods[i];
//如果是该类定义的方法就装载进来
if (tempMB.DeclaringType.Name == tempType.Name && tempMB.IsPublic && !tempMB.IsSpecialName)
{
tempListStrEnuMethods.Add(methods[i].Name);
}
}
tempEnumClassHover = CreateEnum(tempListStrEnuMethods);
tempEnumClassUnHover = CreateEnum(tempListStrEnuMethods);
}
}
}
isEventHover.boolValue = EditorGUILayout.Toggle("悬浮事件", isEventHover.boolValue);
if (isEventHover.boolValue&&null!= tempEnumClassHover)
{
tempEnumClassHover = EditorGUILayout.EnumPopup("绑定Hover的方法", tempEnumClassHover);
nameHoverMethod.stringValue = tempEnumClassHover.ToString();
}
isEventUnHover.boolValue = EditorGUILayout.Toggle("退出悬浮事件", isEventUnHover.boolValue);
if (isEventUnHover.boolValue&&null!=tempEnumClassUnHover)
{
tempEnumClassUnHover = EditorGUILayout.EnumPopup("绑定UnHover的方法", tempEnumClassUnHover);
nameUnHoverMethod.stringValue = tempEnumClassUnHover.ToString();
}
serializedObject.ApplyModifiedProperties();
//将原本脚本中序列化的也画出来
DrawDefaultInspector();
}
//private void GetMethod(GameObject obj)
//{
// UI3D_PanelInfo tempComponent = obj.GetComponent<UI3D_PanelInfo>();
// if (null != tempComponent)
// {
// Type tempType = tempComponent.GetType();
// //使用反射将方法全部反射出来
// MethodInfo[] methods = tempType.GetMethods();
// List<string> tempListStrEnuMethods = new List<string>();
// for (int i = 0; i < methods.Length; i++)
// {
// //如果是该类定义的方法就装载进来
// if (methods[i].DeclaringType.Name == tempType.Name)
// {
// tempListStrEnuMethods.Add(methods[i].ToString());
// }
// }
// if (null == tempEnumClass)
// {
// tempEnumClass = CreateEnum(tempListStrEnuMethods);
// }
// tempEnumClass = EditorGUILayout.EnumPopup("动态创建的枚举", tempEnumClass);
// int tempIndex = tempListStrEnuMethods.FindIndex(p => p == tempEnumClass.ToString());
// if (-1 != tempIndex)
// {
// Debug.Log(tempListStrEnuMethods[tempIndex]);
// }
// }
//}
private List<string> GetComponentsName(GameObject tempObj)
{
List<string> tempListName = new List<string>();
if (null != tempObj)
{
//先选择组件,注意如果有脚本挂在上面然后删掉了还是会获得并且是null
Component[] tempComs = tempObj.GetComponents<Component>();
List<string> tempListComsName = new List<string>();
for (int i = 0; i < tempComs.Length; i++)
{
Component tempComponent = tempComs[i];
if (null == tempComponent) continue;
Type tempType = tempComponent.GetType();
tempListComsName.Add(tempType.ToString());
}
}
return tempListName;
}
private Enum CreateEnum(List<string> al)
{
AppDomain domain = Thread.GetDomain();
AssemblyName name = new AssemblyName();
name.Name = "EnumAssembly";
AssemblyBuilder asmBuilder = domain.DefineDynamicAssembly(
name, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder =
asmBuilder.DefineDynamicModule("EnumModule");
EnumBuilder enumBuilder = modBuilder.DefineEnum("Language",
TypeAttributes.Public,
typeof(System.Int32));
//string[] al = { "en-UK", "ar-SA", "da-DK", "French", "Cantonese" };
for (int i = 0; i < al.Count; i++)
{
// here al is an array list with a list of string values
enumBuilder.DefineLiteral(al[i].ToString(), i);
}
Type enumType = enumBuilder.CreateType();
//using debug mode, watch ty, it's type is Language. only has one value "en-UK"
//also in debug mode, watch "Language", it's an existent enum, type is int.
Enum enumObj = (Enum)Activator.CreateInstance(enumType);
return enumObj;
}
}
三、总结
1、基本实现的Hover和UnHover的功能,以及动态绑定的自定义编辑功能
2、还有很多功能需要进一步拓展,敬请关注后续
3、现在编辑界面的逻辑也不太完善,需要进一步完善
4、工程下载地址Unity工程HoloUIEffect下载地址