PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Assets/SteamVR_Unity_Toolkit/Scripts/Controls/2D/RadialMenu/VRTK_IndependentRadialMenuController.cs

https://gitlab.com/github-cloud-corporation/SteamVR_Unity_Toolkit
C# | 350 lines | 292 code | 52 blank | 6 comment | 62 complexity | 7d2dd847838509f9ae622da909ed6376 MD5 | raw file
  1. namespace VRTK
  2. {
  3. using UnityEngine;
  4. using System.Collections.Generic;
  5. using System.Collections;
  6. public class VRTK_IndependentRadialMenuController : RadialMenuController
  7. {
  8. #region Variables
  9. public VRTK_InteractableObject eventsManager;
  10. public bool addMenuCollider = true;
  11. [Range (0, 10)]
  12. public float colliderRadiusMultiplier = 1.2f;
  13. public bool hideAfterExecution = true;
  14. [Range(-10, 10)]
  15. public float offsetMultiplier = 1.1f;
  16. public GameObject rotateTowards;
  17. private List<GameObject> interactingObjects; // Objects (controllers) that are either colliding with the menu or clicking the menu
  18. private List<GameObject> collidingObjects; // Just objects that are currently colliding with the menu or its parent
  19. private SphereCollider menuCollider;
  20. private Coroutine disableCoroutine;
  21. private Vector3 desiredColliderCenter;
  22. private Quaternion initialRotation;
  23. private bool isClicked = false;
  24. private bool waitingToDisableCollider = false;
  25. private int counter = 2;
  26. #endregion Variables
  27. #region Init and Unity Methods
  28. public void UpdateEventsManager()
  29. {
  30. VRTK_InteractableObject newEventsManager = transform.GetComponentInParent<VRTK_InteractableObject>();
  31. if (newEventsManager == null)
  32. {
  33. Debug.LogError("The radial menu must be a child of an interactable object or be set in the inspector!");
  34. return;
  35. }
  36. else if (newEventsManager != eventsManager) // Changed managers
  37. {
  38. if (eventsManager != null)
  39. { // Unsubscribe from the old events
  40. OnDisable ();
  41. }
  42. eventsManager = newEventsManager;
  43. // Subscribe to new events
  44. OnEnable ();
  45. Object.Destroy (menuCollider);
  46. // Reset to initial state
  47. Initialize();
  48. }
  49. }
  50. protected override void Initialize()
  51. {
  52. if (eventsManager == null)
  53. {
  54. initialRotation = transform.localRotation;
  55. UpdateEventsManager ();
  56. return; // If all goes well in updateEventsManager, it will then call Initialize again, skipping this if statement
  57. }
  58. // Reset variables
  59. interactingObjects = new List<GameObject>();
  60. collidingObjects = new List<GameObject>();
  61. if (disableCoroutine != null)
  62. {
  63. StopCoroutine(disableCoroutine);
  64. disableCoroutine = null;
  65. }
  66. isClicked = false;
  67. waitingToDisableCollider = false;
  68. counter = 2;
  69. if (transform.childCount == 0) // This means things haven't been properly initialized yet, will cause problems.
  70. {
  71. return;
  72. }
  73. float radius = (transform.GetChild(0).GetComponent<RectTransform>().rect.width / 2) * offsetMultiplier;
  74. transform.localPosition = new Vector3(0, 0, radius);
  75. if (addMenuCollider)
  76. {
  77. gameObject.SetActive(false); // Just be sure it doesn't briefly flash
  78. transform.localScale = Vector3.one; // If this were left at zero it would ruin the transformations below
  79. Quaternion startingRot = transform.rotation;
  80. transform.rotation = Quaternion.Euler (new Vector3 (0, 0, 0)); // Rotation can mess up the calculations below
  81. SphereCollider collider = eventsManager.gameObject.AddComponent<SphereCollider>();
  82. // All of the transformVector's are to account for the scaling of the radial menu's 'panel' and the scaling of the eventsManager parent object
  83. collider.radius = (transform.GetChild(0).GetComponent<RectTransform>().rect.width / 2) * colliderRadiusMultiplier * eventsManager.transform.InverseTransformVector(transform.GetChild (0).TransformVector(Vector3.one)).x;
  84. collider.center = eventsManager.transform.InverseTransformVector(transform.position - eventsManager.transform.position);
  85. collider.isTrigger = true;
  86. collider.enabled = false; // Want this to only activate when the menu is showing
  87. menuCollider = collider;
  88. desiredColliderCenter = collider.center;
  89. transform.rotation = startingRot;
  90. }
  91. if (!menu.isShown)
  92. {
  93. transform.localScale = Vector3.zero;
  94. }
  95. gameObject.SetActive(true);
  96. }
  97. protected override void OnEnable ()
  98. {
  99. if (eventsManager != null)
  100. {
  101. eventsManager.InteractableObjectUsed += ObjectClicked;
  102. eventsManager.InteractableObjectUnused += ObjectUnClicked;
  103. eventsManager.InteractableObjectTouched += ObjectTouched;
  104. eventsManager.InteractableObjectUntouched += ObjectUntouched;
  105. menu.FireHapticPulse += AttemptHapticPulse;
  106. }
  107. else
  108. {
  109. Initialize ();
  110. }
  111. }
  112. protected override void OnDisable ()
  113. {
  114. if (eventsManager != null)
  115. {
  116. eventsManager.InteractableObjectUsed -= ObjectClicked;
  117. eventsManager.InteractableObjectUnused -= ObjectUnClicked;
  118. eventsManager.InteractableObjectTouched -= ObjectTouched;
  119. eventsManager.InteractableObjectUntouched -= ObjectUntouched;
  120. menu.FireHapticPulse -= AttemptHapticPulse;
  121. }
  122. }
  123. #endregion Init
  124. #region Event Listeners
  125. protected virtual void ObjectClicked(object sender, InteractableObjectEventArgs e)
  126. {
  127. base.DoClickButton(sender);
  128. isClicked = true;
  129. if (hideAfterExecution && !menu.executeOnUnclick)
  130. {
  131. ImmediatelyHideMenu(e);
  132. }
  133. }
  134. protected virtual void ObjectUnClicked(object sender, InteractableObjectEventArgs e)
  135. {
  136. base.DoUnClickButton(sender);
  137. isClicked = false;
  138. if ((hideAfterExecution || (collidingObjects.Count == 0 && menu.hideOnRelease)) && menu.executeOnUnclick)
  139. {
  140. ImmediatelyHideMenu(e);
  141. }
  142. }
  143. protected virtual void ObjectTouched(object sender, InteractableObjectEventArgs e)
  144. {
  145. base.DoShowMenu(CalculateAngle(e.interactingObject), sender);
  146. collidingObjects.Add(e.interactingObject);
  147. interactingObjects.Add(e.interactingObject);
  148. if (addMenuCollider && menuCollider != null)
  149. {
  150. SetColliderState(true, e);
  151. if (disableCoroutine != null)
  152. {
  153. StopCoroutine(disableCoroutine);
  154. }
  155. }
  156. }
  157. protected virtual void ObjectUntouched(object sender, InteractableObjectEventArgs e)
  158. {
  159. collidingObjects.Remove(e.interactingObject);
  160. if (((!menu.executeOnUnclick || !isClicked) && menu.hideOnRelease) || (Object)sender == this)
  161. {
  162. base.DoHideMenu(hideAfterExecution ,sender);
  163. interactingObjects.Remove(e.interactingObject);
  164. if (addMenuCollider && menuCollider != null)
  165. {
  166. // In case there's any gap between the normal collider and the menuCollider, delay a bit. Cancelled if collider is re-entered
  167. disableCoroutine = StartCoroutine(DelayedSetColliderEnabled(false, 0.25f, e));
  168. }
  169. }
  170. }
  171. protected override void AttemptHapticPulse (ushort strength)
  172. {
  173. if (interactingObjects.Count > 0)
  174. {
  175. SteamVR_Controller.Input ((int)interactingObjects[0].GetComponent<SteamVR_TrackedObject> ().index).TriggerHapticPulse (strength);
  176. }
  177. }
  178. #endregion Event Listeners
  179. #region Helpers
  180. protected float CalculateAngle(GameObject interactingObject)
  181. {
  182. Vector3 controllerPosition = interactingObject.transform.position;
  183. Vector3 toController = controllerPosition - transform.position;
  184. Vector3 projection = transform.position + Vector3.ProjectOnPlane(toController, transform.forward);
  185. float angle = 0;
  186. angle = Utilities.AngleSigned(transform.right * -1, projection - transform.position, transform.forward);
  187. // Ensure angle is positive
  188. if (angle < 0)
  189. {
  190. angle += 360.0f;
  191. }
  192. return angle;
  193. }
  194. private void ImmediatelyHideMenu(InteractableObjectEventArgs e)
  195. {
  196. ObjectUntouched(this, e);
  197. if (disableCoroutine != null)
  198. {
  199. StopCoroutine(disableCoroutine);
  200. }
  201. SetColliderState(false, e); // Don't want to wait for this
  202. }
  203. private void SetColliderState(bool state, InteractableObjectEventArgs e)
  204. {
  205. if (addMenuCollider && menuCollider != null)
  206. {
  207. if (state)
  208. {
  209. menuCollider.enabled = true;
  210. menuCollider.center = desiredColliderCenter;
  211. }
  212. else
  213. {
  214. bool should = true;
  215. Collider[] colliders = eventsManager.GetComponents<Collider>();
  216. Collider[] controllerColliders = e.interactingObject.GetComponent<VRTK_InteractTouch>().ControllerColliders();
  217. foreach (var collider in colliders)
  218. {
  219. if (collider != menuCollider)
  220. {
  221. foreach(var controllerCollider in controllerColliders)
  222. {
  223. if (controllerCollider.bounds.Intersects(collider.bounds))
  224. {
  225. should = false;
  226. }
  227. }
  228. }
  229. }
  230. if (should)
  231. {
  232. menuCollider.center = new Vector3(100000000.0f, 100000000.0f, 100000000.0f); // This needs to be done to get OnTriggerExit() to fire, unfortunately
  233. waitingToDisableCollider = true; // Need to give other things time to realize that they're not colliding with this anymore, so do it a couple FixedUpdates
  234. }
  235. else
  236. {
  237. menuCollider.enabled = false;
  238. }
  239. }
  240. }
  241. }
  242. IEnumerator DelayedSetColliderEnabled(bool enabled, float delay, InteractableObjectEventArgs e)
  243. {
  244. yield return new WaitForSeconds(delay);
  245. SetColliderState(enabled, e);
  246. StopCoroutine("delayedSetColliderEnabled");
  247. }
  248. #endregion Helpers
  249. #region Unity Methods
  250. private void Awake()
  251. {
  252. menu = GetComponent<RadialMenu> ();
  253. }
  254. private void Start()
  255. {
  256. Initialize ();
  257. }
  258. private void Update()
  259. {
  260. if (rotateTowards == null) // Backup
  261. {
  262. rotateTowards = GameObject.Find ("Camera (eye)");
  263. if (rotateTowards == null)
  264. {
  265. Debug.LogWarning ("The IndependentRadialMenu could not automatically find an object to rotate towards.");
  266. }
  267. }
  268. if (menu.isShown)
  269. {
  270. if (interactingObjects.Count > 0) // There's not really an event for the controller moving, so just update the position every frame
  271. {
  272. base.DoChangeAngle (CalculateAngle (interactingObjects[0]), this);
  273. }
  274. if (rotateTowards != null)
  275. {
  276. transform.rotation = Quaternion.LookRotation ((rotateTowards.transform.position - transform.position) * -1, Vector3.up) * initialRotation; // Face the target, but maintain initial rotation
  277. }
  278. }
  279. }
  280. private void FixedUpdate()
  281. {
  282. if (waitingToDisableCollider)
  283. {
  284. if (counter == 0)
  285. {
  286. menuCollider.enabled = false;
  287. waitingToDisableCollider = false;
  288. counter = 2;
  289. }
  290. else
  291. {
  292. counter--;
  293. }
  294. }
  295. }
  296. #endregion Unity Methods
  297. }
  298. }