ARInteractorSpawnTrigger.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #if AR_FOUNDATION_PRESENT
  2. using UnityEngine.Events;
  3. using UnityEngine.EventSystems;
  4. using UnityEngine.XR.ARFoundation;
  5. using UnityEngine.XR.ARSubsystems;
  6. using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;
  7. using UnityEngine.XR.Interaction.Toolkit.Interactors;
  8. namespace UnityEngine.XR.Interaction.Toolkit.Samples.ARStarterAssets
  9. {
  10. /// <summary>
  11. /// Spawns an object at an <see cref="IARInteractor"/>'s raycast hit position when a trigger is activated.
  12. /// </summary>
  13. public class ARInteractorSpawnTrigger : MonoBehaviour
  14. {
  15. /// <summary>
  16. /// The type of trigger to use to spawn an object.
  17. /// </summary>
  18. public enum SpawnTriggerType
  19. {
  20. /// <summary>
  21. /// Spawn an object when the interactor activates its select input
  22. /// but no selection actually occurs.
  23. /// </summary>
  24. SelectAttempt,
  25. /// <summary>
  26. /// Spawn an object when an input is performed.
  27. /// </summary>
  28. InputAction,
  29. }
  30. [SerializeField]
  31. [Tooltip("The AR ray interactor that determines where to spawn the object.")]
  32. XRRayInteractor m_ARInteractor;
  33. /// <summary>
  34. /// The AR ray interactor that determines where to spawn the object.
  35. /// </summary>
  36. public XRRayInteractor arInteractor
  37. {
  38. get => m_ARInteractor;
  39. set => m_ARInteractor = value;
  40. }
  41. [SerializeField]
  42. [Tooltip("Whether to require that the AR Interactor hits an AR Plane with a horizontal up alignment in order to spawn anything.")]
  43. bool m_RequireHorizontalUpSurface;
  44. /// <summary>
  45. /// Whether to require that the <see cref="IARInteractor"/> hits an <see cref="ARPlane"/> with an alignment of
  46. /// <see cref="PlaneAlignment.HorizontalUp"/> in order to spawn anything.
  47. /// </summary>
  48. public bool requireHorizontalUpSurface
  49. {
  50. get => m_RequireHorizontalUpSurface;
  51. set => m_RequireHorizontalUpSurface = value;
  52. }
  53. [SerializeField]
  54. [Tooltip("The type of trigger to use to spawn an object, either when the Interactor's select action occurs or " +
  55. "when a button input is performed.")]
  56. SpawnTriggerType m_SpawnTriggerType;
  57. /// <summary>
  58. /// The type of trigger to use to spawn an object.
  59. /// </summary>
  60. public SpawnTriggerType spawnTriggerType
  61. {
  62. get => m_SpawnTriggerType;
  63. set => m_SpawnTriggerType = value;
  64. }
  65. [SerializeField]
  66. XRInputButtonReader m_SpawnObjectInput = new XRInputButtonReader("Spawn Object");
  67. /// <summary>
  68. /// The input used to trigger spawn, if <see cref="spawnTriggerType"/> is set to <see cref="SpawnTriggerType.InputAction"/>.
  69. /// </summary>
  70. public XRInputButtonReader spawnObjectInput
  71. {
  72. get => m_SpawnObjectInput;
  73. set => XRInputReaderUtility.SetInputProperty(ref m_SpawnObjectInput, value, this);
  74. }
  75. [SerializeField]
  76. [Tooltip("When enabled, spawn will not be triggered if an object is currently selected.")]
  77. bool m_BlockSpawnWhenInteractorHasSelection = true;
  78. /// <summary>
  79. /// When enabled, spawn will not be triggered if an object is currently selected.
  80. /// </summary>
  81. public bool blockSpawnWhenInteractorHasSelection
  82. {
  83. get => m_BlockSpawnWhenInteractorHasSelection;
  84. set => m_BlockSpawnWhenInteractorHasSelection = value;
  85. }
  86. /// <summary>
  87. /// Calls the methods in its invocation list when an object is spawned.
  88. /// </summary>
  89. /// <remarks>
  90. /// The first event parameter corresponds to the spawn position in world space
  91. /// and the second event parameter corresponds to the vector normal to the surface.
  92. /// </remarks>
  93. public UnityEvent<Vector3, Vector3> objectSpawnTriggered
  94. {
  95. get => m_ObjectSpawnTriggered;
  96. set => m_ObjectSpawnTriggered = value;
  97. }
  98. [Header("Events")]
  99. [SerializeField]
  100. [Tooltip("Calls the methods in its invocation list when an object is spawned.")]
  101. UnityEvent<Vector3, Vector3> m_ObjectSpawnTriggered = new UnityEvent<Vector3, Vector3>();
  102. bool m_AttemptSpawn;
  103. bool m_EverHadSelection;
  104. /// <summary>
  105. /// See <see cref="MonoBehaviour"/>.
  106. /// </summary>
  107. void OnEnable()
  108. {
  109. m_SpawnObjectInput.EnableDirectActionIfModeUsed();
  110. }
  111. /// <summary>
  112. /// See <see cref="MonoBehaviour"/>.
  113. /// </summary>
  114. void OnDisable()
  115. {
  116. m_SpawnObjectInput.DisableDirectActionIfModeUsed();
  117. }
  118. /// <summary>
  119. /// See <see cref="MonoBehaviour"/>.
  120. /// </summary>
  121. void Start()
  122. {
  123. if (m_ARInteractor == null)
  124. {
  125. Debug.LogError("Missing AR Interactor reference, disabling component.", this);
  126. enabled = false;
  127. }
  128. }
  129. /// <summary>
  130. /// See <see cref="MonoBehaviour"/>.
  131. /// </summary>
  132. void Update()
  133. {
  134. // Wait a frame after the Spawn Object input is triggered to actually cast against AR planes and spawn
  135. // in order to ensure the touchscreen gestures have finished processing to allow the ray pose driver
  136. // to update the pose based on the touch position of the gestures.
  137. if (m_AttemptSpawn)
  138. {
  139. m_AttemptSpawn = false;
  140. // Cancel the spawn if the select was delayed until the frame after the spawn trigger.
  141. // This can happen if the select action uses a different input source than the spawn trigger.
  142. if (m_ARInteractor.hasSelection)
  143. return;
  144. // Don't spawn the object if the tap was over screen space UI.
  145. var isPointerOverUI = EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(-1);
  146. if (!isPointerOverUI && m_ARInteractor.TryGetCurrentARRaycastHit(out var arRaycastHit))
  147. {
  148. if (!(arRaycastHit.trackable is ARPlane arPlane))
  149. return;
  150. if (m_RequireHorizontalUpSurface && arPlane.alignment != PlaneAlignment.HorizontalUp)
  151. return;
  152. m_ObjectSpawnTriggered.Invoke(arRaycastHit.pose.position, arPlane.normal);
  153. }
  154. return;
  155. }
  156. var selectState = m_ARInteractor.logicalSelectState;
  157. if (m_BlockSpawnWhenInteractorHasSelection)
  158. {
  159. if (selectState.wasPerformedThisFrame)
  160. m_EverHadSelection = m_ARInteractor.hasSelection;
  161. else if (selectState.active)
  162. m_EverHadSelection |= m_ARInteractor.hasSelection;
  163. }
  164. m_AttemptSpawn = false;
  165. switch (m_SpawnTriggerType)
  166. {
  167. case SpawnTriggerType.SelectAttempt:
  168. if (selectState.wasCompletedThisFrame)
  169. m_AttemptSpawn = !m_ARInteractor.hasSelection && !m_EverHadSelection;
  170. break;
  171. case SpawnTriggerType.InputAction:
  172. if (m_SpawnObjectInput.ReadWasPerformedThisFrame())
  173. m_AttemptSpawn = !m_ARInteractor.hasSelection && !m_EverHadSelection;
  174. break;
  175. }
  176. }
  177. }
  178. }
  179. #endif