using System; using System.CodeDom; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using EZXR.Glass.Inputs; using UnityEditor; using EZXR.Glass.Core; using UnityEngine.Serialization; using EZXR.Glass.Inputs; namespace EZXR.Glass.UI { [ExecuteInEditMode] public class SpatialButton : SpatialSelectable { #region InternalUse public BoxCollider _collider; #endregion [Tooltip("按钮前置触发区的厚度(单位:m),用户手指进入这个区域将会被认为将要与此Button进行交互,触发OnPointerPotentialInteractChanged事件")] /// /// 按钮前置触发区的厚度(单位:m),用户手指进入这个区域将会被认为将要与此Button进行交互,触发OnPointerPotentialInteractChanged事件 /// public float preTriggerZoneThickness = 0.15f; [Tooltip("按钮后置触发区的厚度(单位:m),决定了用户手指按过(穿过)按钮后还保持按下状态的距离,当手指离开这个后置触发区的时候按钮将会弹起")] /// /// 按钮后置触发区的厚度(单位:m),决定了用户手指按过(穿过)按钮后还保持按下状态的距离,当手指离开这个后置触发区的时候按钮将会弹起 /// public float rearTriggerZoneThickness = 0.05f; /// /// 按钮被按下 /// public bool pressed; /// /// 0是未按下,1是按下但是没按到底,2是按下且按到底 /// public byte state; /// /// 按键高度(单位:m,必须大于heightMin),从这个高度开始响应按键事件 /// public float heightMax; /// /// 按键按到底的时候的厚度 /// public float heightMin = 0.001f; /// /// 当前是哪个指尖点点击到了按键,用于计算按下的z距离 /// Transform other; /// /// 当前被点击的Button /// SpatialButton curButton; /// /// 指尖点当前在按键坐标系下的坐标 /// Vector3 curPos; /// /// 当前指尖点距离按键底面的z距离 /// public float length; public float clampLength; float lastLength; /// /// 是否开始点击,只要有指尖点进入触发区就开始点击判断流程 /// bool start = false; //bool rayStart = false; ///// ///// 用于播放按键按下和弹起的音效 ///// //AudioSource audioSource; /// /// 0是按键开始按下的音效,1是按键按下的音效,2是按键弹起的音效 /// static AudioClip[] clips; InputInfoBase handInfo; /// /// Button是否响应虚拟键盘的回车键 /// [SerializeField] private bool useEnterKey; #region Event public enum SpatialButtonEventType { OnPointerHoverEnter, OnPointerStartPress, OnPointerPressing, OnPointerDown, OnPointerUp, OnPointerEndPress, OnPointerClicked, OnPointerHoverExit, } private static Dictionary All = new Dictionary(); [Serializable] public class SpatialButtonEvent : UnityEvent { } [Serializable] public class Entry { public SpatialButtonEventType eventID = SpatialButtonEventType.OnPointerHoverEnter; public SpatialButtonEvent callback = new SpatialButtonEvent(); } [FormerlySerializedAs("delegates")] [SerializeField] private List m_Delegates; public List triggers { get { if (m_Delegates == null) m_Delegates = new List(); return m_Delegates; } set { m_Delegates = value; } } private void Execute(SpatialButtonEventType id) { var triggerCount = triggers.Count; for (int i = 0, imax = triggers.Count; i < imax; ++i) { var ent = triggers[i]; if (ent.eventID == id && ent.callback != null) ent.callback.Invoke(); } } public void AddEvent(SpatialButtonEventType spatialButtonEventType, UnityAction unityAction) { var triggerCount = triggers.Count; for (int i = 0, imax = triggers.Count; i < imax; ++i) { var ent = triggers[i]; if (ent.eventID == spatialButtonEventType && ent.callback != null) { ent.callback.AddListener(unityAction); return; } } Entry entry = new Entry(); entry.eventID = spatialButtonEventType; entry.callback.AddListener(unityAction); triggers.Add(entry); } #endregion protected override void Awake() { base.Awake(); //将自身注册到ARUIEventSystem中,以在被射线碰到的时候被回调 SpatialUIEventSystem.RegisterCallBack(GetComponent(), this); } protected override void OnEnable() { base.OnEnable(); if (useEnterKey) { KeyBoard.OnEnterClicked += OnEnterClicked; } } protected override void OnDisable() { base.OnDisable(); if (useEnterKey) { KeyBoard.OnEnterClicked -= OnEnterClicked; } } private void OnEnterClicked() { if (isActiveAndEnabled) { ButtonClick(); } } // Start is called before the first frame update protected override void Start() { base.Start(); if (Application.isPlaying) { //_collider = GetComponent(); heightMax = size.z > heightMin ? size.z : heightMin; //material = _visual.GetChild(0).GetComponent().material; //audioSource = GetComponent(); clips = new AudioClip[3]; clips[0] = ResourcesManager.Load("Sounds/ButtonStartPress"); clips[1] = ResourcesManager.Load("Sounds/ButtonDown"); clips[2] = ResourcesManager.Load("Sounds/ButtonUp"); } } // Update is called once per frame protected override void Update() { base.Update(); if (Application.isPlaying) { //射线交互 if (handInfo != null) { //如果当前射线射到的物体不再是此按钮 if (handInfo.CurRayContactingTarget == null || handInfo.CurRayContactingTarget.gameObject != gameObject) { handInfo = null; } else { if (handInfo.isPinching) { ButtonDown(true); } else { ButtonUp(true); } } } else//近距离 { if (start)//直接近距离按压 { if (other == null || !other.gameObject.activeInHierarchy)//手丢失了的话直接ButtonUp { ButtonUp(false); start = false; curPos = new Vector3(9999, 9999, 9999); return; } else//手正常存在的话实时算出指尖在Button局部坐标系下的位置 { //先算出指尖点当前在按键坐标系下的坐标 curPos = transform.InverseTransformPoint(other.position); } //然后算出当前指尖点距离按键底面的z距离,这个0.005是指尖点的半径长度 length = curPos.z - 0.005f; clampLength = Mathf.Clamp(length, heightMin, heightMax); //更新按钮状态 if (clampLength == heightMax)//按钮已经弹起来 { ButtonUp(false); } else if (clampLength == heightMin)//按钮按到底 { ButtonDown(false); } else if (clampLength < heightMax)//按钮按下但没到底 { ButtonPressing(); } lastLength = length; } } } else { #if UNITY_EDITOR //_mesh.localScale = size; _collider.size = new Vector3(size.x, size.y, /*size.z + */preTriggerZoneThickness + rearTriggerZoneThickness); _collider.center = new Vector3(0, 0, preTriggerZoneThickness - _collider.size.z / 2.0f); #endif } } private void OnTriggerStay(Collider other) { if (Application.isPlaying) { if (!start) { //对方必须具备rigidbody才会触发ARButton的Tigger if (other.name.Contains("Index_4")) { if (_collider.bounds.Contains(other.transform.position) && (transform.InverseTransformPoint(other.transform.position).z - 0.005f > heightMin /*heightMax*/))//此处用于确保开始触发按钮事件一定是从Hover开始,避免从按钮底部向上移动指尖造成的异常 { curButton = this; this.other = other.transform; start = true; OnPointerHoverEnter(Spatial.InteractiveMode.Touch); } } } } } private void OnTriggerExit(Collider other) { if (Application.isPlaying) { if (other.name.Contains("Index_4")) { //只有按钮正在被按的状态下,指尖离开触发区才会触发ButtonUp if (state != 0) { ButtonUp(false); } start = false; curButton = null; this.other = null; OnPointerHoverExit(); } } } /// /// handInfo如果为null表示没有手在与此Button交互 /// /// public override void OnRayCastHit(InputInfoBase handInfo) { if (this.handInfo != handInfo) { if (handInfo == null) { OnPointerHoverExit(); } else { OnPointerHoverEnter(Spatial.InteractiveMode.Raycast); } this.handInfo = handInfo; } } /// /// 按钮按到底的时候调用,播放按键按下的音效,并更新此按键的pressed状态为true /// /// 是否是射线交互 void ButtonDown(bool isRay) { if (state != 2) { if (isRay) { OnPointerStartPress(); } } //按到底也属于Pressing的一种情况,所以应该调用OnPointerPressing OnPointerPressing(); if (state != 2) { state = 2; pressed = true; //播放按键按下的音效 AudioSource.PlayClipAtPoint(clips[/*isRay ? 0 : 1*/0], Vector3.zero); OnPointerDown(); } } /// /// 按钮被按住的时候调用 /// void ButtonPressing() { if (state == 0) { ////播放按键开始按下的音效 //AudioSource.PlayClipAtPoint(clips[0], Vector3.zero); OnPointerStartPress(); } state = 1; OnPointerPressing(); } /// /// 按键完全弹起的时候调用,播放按键弹起的音效,并更新此按键的pressed状态为false /// void ButtonUp(bool isRay) { if (state != 0) { if (isRay) { //播放按键弹起的音效 AudioSource.PlayClipAtPoint(clips[2], Vector3.zero); } OnPointerUp(); OnPointerEndPress(); } if (pressed)//按到底然后弹起来才算是一次点击 { ButtonClick(); } state = 0; pressed = false; } void ButtonClick() { OnPointerClicked(); } #region Pointer Event /// public override void OnPointerHoverEnter(Spatial.InteractiveMode interactiveMode = Spatial.InteractiveMode.None) { base.OnPointerHoverEnter(interactiveMode); Execute(SpatialButtonEventType.OnPointerHoverEnter); } /// public override void OnPointerStartPress() { base.OnPointerStartPress(); Execute(SpatialButtonEventType.OnPointerStartPress); } /// public override void OnPointerPressing() { base.OnPointerPressing(); Execute(SpatialButtonEventType.OnPointerPressing); } /// public override void OnPointerDown() { base.OnPointerDown(); Execute(SpatialButtonEventType.OnPointerDown); } /// public override void OnPointerUp() { base.OnPointerUp(); Execute(SpatialButtonEventType.OnPointerUp); } /// public override void OnPointerEndPress() { base.OnPointerEndPress(); Execute(SpatialButtonEventType.OnPointerEndPress); } /// public override void OnPointerClicked() { base.OnPointerClicked(); Execute(SpatialButtonEventType.OnPointerClicked); } /// public override void OnPointerHoverExit() { base.OnPointerHoverExit(); Execute(SpatialButtonEventType.OnPointerHoverExit); } #endregion } }