SpatialButton.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using System;
  2. using System.CodeDom;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.Events;
  7. using UnityEngine.UI;
  8. using EZXR.Glass.Inputs;
  9. using UnityEditor;
  10. using EZXR.Glass.Core;
  11. using UnityEngine.Serialization;
  12. using EZXR.Glass.Inputs;
  13. namespace EZXR.Glass.UI
  14. {
  15. [ExecuteInEditMode]
  16. public class SpatialButton : SpatialSelectable
  17. {
  18. #region InternalUse
  19. public BoxCollider _collider;
  20. #endregion
  21. [Tooltip("按钮前置触发区的厚度(单位:m),用户手指进入这个区域将会被认为将要与此Button进行交互,触发OnPointerPotentialInteractChanged事件")]
  22. /// <summary>
  23. /// 按钮前置触发区的厚度(单位:m),用户手指进入这个区域将会被认为将要与此Button进行交互,触发OnPointerPotentialInteractChanged事件
  24. /// </summary>
  25. public float preTriggerZoneThickness = 0.15f;
  26. [Tooltip("按钮后置触发区的厚度(单位:m),决定了用户手指按过(穿过)按钮后还保持按下状态的距离,当手指离开这个后置触发区的时候按钮将会弹起")]
  27. /// <summary>
  28. /// 按钮后置触发区的厚度(单位:m),决定了用户手指按过(穿过)按钮后还保持按下状态的距离,当手指离开这个后置触发区的时候按钮将会弹起
  29. /// </summary>
  30. public float rearTriggerZoneThickness = 0.05f;
  31. /// <summary>
  32. /// 按钮被按下
  33. /// </summary>
  34. public bool pressed;
  35. /// <summary>
  36. /// 0是未按下,1是按下但是没按到底,2是按下且按到底
  37. /// </summary>
  38. public byte state;
  39. /// <summary>
  40. /// 按键高度(单位:m,必须大于heightMin),从这个高度开始响应按键事件
  41. /// </summary>
  42. public float heightMax;
  43. /// <summary>
  44. /// 按键按到底的时候的厚度
  45. /// </summary>
  46. public float heightMin = 0.001f;
  47. /// <summary>
  48. /// 当前是哪个指尖点点击到了按键,用于计算按下的z距离
  49. /// </summary>
  50. Transform other;
  51. /// <summary>
  52. /// 当前被点击的Button
  53. /// </summary>
  54. SpatialButton curButton;
  55. /// <summary>
  56. /// 指尖点当前在按键坐标系下的坐标
  57. /// </summary>
  58. Vector3 curPos;
  59. /// <summary>
  60. /// 当前指尖点距离按键底面的z距离
  61. /// </summary>
  62. public float length;
  63. public float clampLength;
  64. float lastLength;
  65. /// <summary>
  66. /// 是否开始点击,只要有指尖点进入触发区就开始点击判断流程
  67. /// </summary>
  68. bool start = false;
  69. //bool rayStart = false;
  70. ///// <summary>
  71. ///// 用于播放按键按下和弹起的音效
  72. ///// </summary>
  73. //AudioSource audioSource;
  74. /// <summary>
  75. /// 0是按键开始按下的音效,1是按键按下的音效,2是按键弹起的音效
  76. /// </summary>
  77. static AudioClip[] clips;
  78. InputInfoBase handInfo;
  79. /// <summary>
  80. /// Button是否响应虚拟键盘的回车键
  81. /// </summary>
  82. [SerializeField]
  83. private bool useEnterKey;
  84. #region Event
  85. public enum SpatialButtonEventType
  86. {
  87. OnPointerHoverEnter,
  88. OnPointerStartPress,
  89. OnPointerPressing,
  90. OnPointerDown,
  91. OnPointerUp,
  92. OnPointerEndPress,
  93. OnPointerClicked,
  94. OnPointerHoverExit,
  95. }
  96. private static Dictionary<Collider, SpatialObject> All = new Dictionary<Collider, SpatialObject>();
  97. [Serializable]
  98. public class SpatialButtonEvent : UnityEvent
  99. { }
  100. [Serializable]
  101. public class Entry
  102. {
  103. public SpatialButtonEventType eventID = SpatialButtonEventType.OnPointerHoverEnter;
  104. public SpatialButtonEvent callback = new SpatialButtonEvent();
  105. }
  106. [FormerlySerializedAs("delegates")]
  107. [SerializeField]
  108. private List<Entry> m_Delegates;
  109. public List<Entry> triggers
  110. {
  111. get
  112. {
  113. if (m_Delegates == null)
  114. m_Delegates = new List<Entry>();
  115. return m_Delegates;
  116. }
  117. set { m_Delegates = value; }
  118. }
  119. private void Execute(SpatialButtonEventType id)
  120. {
  121. var triggerCount = triggers.Count;
  122. for (int i = 0, imax = triggers.Count; i < imax; ++i)
  123. {
  124. var ent = triggers[i];
  125. if (ent.eventID == id && ent.callback != null)
  126. ent.callback.Invoke();
  127. }
  128. }
  129. public void AddEvent(SpatialButtonEventType spatialButtonEventType, UnityAction unityAction)
  130. {
  131. var triggerCount = triggers.Count;
  132. for (int i = 0, imax = triggers.Count; i < imax; ++i)
  133. {
  134. var ent = triggers[i];
  135. if (ent.eventID == spatialButtonEventType && ent.callback != null)
  136. {
  137. ent.callback.AddListener(unityAction);
  138. return;
  139. }
  140. }
  141. Entry entry = new Entry();
  142. entry.eventID = spatialButtonEventType;
  143. entry.callback.AddListener(unityAction);
  144. triggers.Add(entry);
  145. }
  146. #endregion
  147. protected override void Awake()
  148. {
  149. base.Awake();
  150. //将自身注册到ARUIEventSystem中,以在被射线碰到的时候被回调
  151. SpatialUIEventSystem.RegisterCallBack(GetComponent<Collider>(), this);
  152. }
  153. protected override void OnEnable()
  154. {
  155. base.OnEnable();
  156. if (useEnterKey)
  157. {
  158. KeyBoard.OnEnterClicked += OnEnterClicked;
  159. }
  160. }
  161. protected override void OnDisable()
  162. {
  163. base.OnDisable();
  164. if (useEnterKey)
  165. {
  166. KeyBoard.OnEnterClicked -= OnEnterClicked;
  167. }
  168. }
  169. private void OnEnterClicked()
  170. {
  171. if (isActiveAndEnabled)
  172. {
  173. ButtonClick();
  174. }
  175. }
  176. // Start is called before the first frame update
  177. protected override void Start()
  178. {
  179. base.Start();
  180. if (Application.isPlaying)
  181. {
  182. //_collider = GetComponent<BoxCollider>();
  183. heightMax = size.z > heightMin ? size.z : heightMin;
  184. //material = _visual.GetChild(0).GetComponent<Renderer>().material;
  185. //audioSource = GetComponent<AudioSource>();
  186. clips = new AudioClip[3];
  187. clips[0] = ResourcesManager.Load<AudioClip>("Sounds/ButtonStartPress");
  188. clips[1] = ResourcesManager.Load<AudioClip>("Sounds/ButtonDown");
  189. clips[2] = ResourcesManager.Load<AudioClip>("Sounds/ButtonUp");
  190. }
  191. }
  192. // Update is called once per frame
  193. protected override void Update()
  194. {
  195. base.Update();
  196. if (Application.isPlaying)
  197. {
  198. //射线交互
  199. if (handInfo != null)
  200. {
  201. //如果当前射线射到的物体不再是此按钮
  202. if (handInfo.CurRayContactingTarget == null || handInfo.CurRayContactingTarget.gameObject != gameObject)
  203. {
  204. handInfo = null;
  205. }
  206. else
  207. {
  208. if (handInfo.isPinching)
  209. {
  210. ButtonDown(true);
  211. }
  212. else
  213. {
  214. ButtonUp(true);
  215. }
  216. }
  217. }
  218. else//近距离
  219. {
  220. if (start)//直接近距离按压
  221. {
  222. if (other == null || !other.gameObject.activeInHierarchy)//手丢失了的话直接ButtonUp
  223. {
  224. ButtonUp(false);
  225. start = false;
  226. curPos = new Vector3(9999, 9999, 9999);
  227. return;
  228. }
  229. else//手正常存在的话实时算出指尖在Button局部坐标系下的位置
  230. {
  231. //先算出指尖点当前在按键坐标系下的坐标
  232. curPos = transform.InverseTransformPoint(other.position);
  233. }
  234. //然后算出当前指尖点距离按键底面的z距离,这个0.005是指尖点的半径长度
  235. length = curPos.z - 0.005f;
  236. clampLength = Mathf.Clamp(length, heightMin, heightMax);
  237. //更新按钮状态
  238. if (clampLength == heightMax)//按钮已经弹起来
  239. {
  240. ButtonUp(false);
  241. }
  242. else if (clampLength == heightMin)//按钮按到底
  243. {
  244. ButtonDown(false);
  245. }
  246. else if (clampLength < heightMax)//按钮按下但没到底
  247. {
  248. ButtonPressing();
  249. }
  250. lastLength = length;
  251. }
  252. }
  253. }
  254. else
  255. {
  256. #if UNITY_EDITOR
  257. //_mesh.localScale = size;
  258. _collider.size = new Vector3(size.x, size.y, /*size.z + */preTriggerZoneThickness + rearTriggerZoneThickness);
  259. _collider.center = new Vector3(0, 0, preTriggerZoneThickness - _collider.size.z / 2.0f);
  260. #endif
  261. }
  262. }
  263. private void OnTriggerStay(Collider other)
  264. {
  265. if (Application.isPlaying)
  266. {
  267. if (!start)
  268. {
  269. //对方必须具备rigidbody才会触发ARButton的Tigger
  270. if (other.name.Contains("Index_4"))
  271. {
  272. if (_collider.bounds.Contains(other.transform.position) && (transform.InverseTransformPoint(other.transform.position).z - 0.005f > heightMin /*heightMax*/))//此处用于确保开始触发按钮事件一定是从Hover开始,避免从按钮底部向上移动指尖造成的异常
  273. {
  274. curButton = this;
  275. this.other = other.transform;
  276. start = true;
  277. OnPointerHoverEnter(Spatial.InteractiveMode.Touch);
  278. }
  279. }
  280. }
  281. }
  282. }
  283. private void OnTriggerExit(Collider other)
  284. {
  285. if (Application.isPlaying)
  286. {
  287. if (other.name.Contains("Index_4"))
  288. {
  289. //只有按钮正在被按的状态下,指尖离开触发区才会触发ButtonUp
  290. if (state != 0)
  291. {
  292. ButtonUp(false);
  293. }
  294. start = false;
  295. curButton = null;
  296. this.other = null;
  297. OnPointerHoverExit();
  298. }
  299. }
  300. }
  301. /// <summary>
  302. /// handInfo如果为null表示没有手在与此Button交互
  303. /// </summary>
  304. /// <param name="handInfo"></param>
  305. public override void OnRayCastHit(InputInfoBase handInfo)
  306. {
  307. if (this.handInfo != handInfo)
  308. {
  309. if (handInfo == null)
  310. {
  311. OnPointerHoverExit();
  312. }
  313. else
  314. {
  315. OnPointerHoverEnter(Spatial.InteractiveMode.Raycast);
  316. }
  317. this.handInfo = handInfo;
  318. }
  319. }
  320. /// <summary>
  321. /// 按钮按到底的时候调用,播放按键按下的音效,并更新此按键的pressed状态为true
  322. /// </summary>
  323. /// <param name="isRay">是否是射线交互</param>
  324. void ButtonDown(bool isRay)
  325. {
  326. if (state != 2)
  327. {
  328. if (isRay)
  329. {
  330. OnPointerStartPress();
  331. }
  332. }
  333. //按到底也属于Pressing的一种情况,所以应该调用OnPointerPressing
  334. OnPointerPressing();
  335. if (state != 2)
  336. {
  337. state = 2;
  338. pressed = true;
  339. //播放按键按下的音效
  340. AudioSource.PlayClipAtPoint(clips[/*isRay ? 0 : 1*/0], Vector3.zero);
  341. OnPointerDown();
  342. }
  343. }
  344. /// <summary>
  345. /// 按钮被按住的时候调用
  346. /// </summary>
  347. void ButtonPressing()
  348. {
  349. if (state == 0)
  350. {
  351. ////播放按键开始按下的音效
  352. //AudioSource.PlayClipAtPoint(clips[0], Vector3.zero);
  353. OnPointerStartPress();
  354. }
  355. state = 1;
  356. OnPointerPressing();
  357. }
  358. /// <summary>
  359. /// 按键完全弹起的时候调用,播放按键弹起的音效,并更新此按键的pressed状态为false
  360. /// </summary>
  361. void ButtonUp(bool isRay)
  362. {
  363. if (state != 0)
  364. {
  365. if (isRay)
  366. {
  367. //播放按键弹起的音效
  368. AudioSource.PlayClipAtPoint(clips[2], Vector3.zero);
  369. }
  370. OnPointerUp();
  371. OnPointerEndPress();
  372. }
  373. if (pressed)//按到底然后弹起来才算是一次点击
  374. {
  375. ButtonClick();
  376. }
  377. state = 0;
  378. pressed = false;
  379. }
  380. void ButtonClick()
  381. {
  382. OnPointerClicked();
  383. }
  384. #region Pointer Event
  385. /// <inheritdoc/>
  386. public override void OnPointerHoverEnter(Spatial.InteractiveMode interactiveMode = Spatial.InteractiveMode.None)
  387. {
  388. base.OnPointerHoverEnter(interactiveMode);
  389. Execute(SpatialButtonEventType.OnPointerHoverEnter);
  390. }
  391. /// <inheritdoc/>
  392. public override void OnPointerStartPress()
  393. {
  394. base.OnPointerStartPress();
  395. Execute(SpatialButtonEventType.OnPointerStartPress);
  396. }
  397. /// <inheritdoc/>
  398. public override void OnPointerPressing()
  399. {
  400. base.OnPointerPressing();
  401. Execute(SpatialButtonEventType.OnPointerPressing);
  402. }
  403. /// <inheritdoc/>
  404. public override void OnPointerDown()
  405. {
  406. base.OnPointerDown();
  407. Execute(SpatialButtonEventType.OnPointerDown);
  408. }
  409. /// <inheritdoc/>
  410. public override void OnPointerUp()
  411. {
  412. base.OnPointerUp();
  413. Execute(SpatialButtonEventType.OnPointerUp);
  414. }
  415. /// <inheritdoc/>
  416. public override void OnPointerEndPress()
  417. {
  418. base.OnPointerEndPress();
  419. Execute(SpatialButtonEventType.OnPointerEndPress);
  420. }
  421. /// <inheritdoc/>
  422. public override void OnPointerClicked()
  423. {
  424. base.OnPointerClicked();
  425. Execute(SpatialButtonEventType.OnPointerClicked);
  426. }
  427. /// <inheritdoc/>
  428. public override void OnPointerHoverExit()
  429. {
  430. base.OnPointerHoverExit();
  431. Execute(SpatialButtonEventType.OnPointerHoverExit);
  432. }
  433. #endregion
  434. }
  435. }